nginx架构与基础概念

1       Nginx架构

Nginx 高性能,与其架构有关。html

Nginx架构: nginx运行时,在unix系统中以daemon形式在后台运行,后台进程包含一个master进程和多个worker进程。Nginx以多进程形式工做,也支持多线程方式,丹nginx默认采用多进程方式,也是主流方式。nginx

1.1      Nginx多进程模式

多进程模式,会有一个master进程和多个worker进程。web

Master进程管理worker进程,包括:算法

接收来自外界的信号;数据库

向各worker进程发送信号;编程

监控work进程状态;windows

当worker退出后(异常状况下),自动从新启动新worker进程。后端

 

多个worker进程之间对等,竞争来自客户端的请求,一个请求,只会在一个worker中处理,一个worker进程不会处理其余进程的请求。设计模式

Worker进程个数的设置,通常设置与机器cpu核数一致。数组

 

进程模式的好处:

每一个worker进程相互独立,无需加锁,节省锁开销;

采用独立的进程,不会相互影响,一个进程退出,其余进程服务不会中断;

Worker异常退出,会致使当前worker上的全部请求失败,不过不会影响全部请求,下降了风险。

 

多进程模式对并发的支持

每一个worker只有一个主线程,采用异步非阻塞方式来处理请求,使得nginx能够同时处理成千上万个请求。相比Apache,每一个请求会独占一个工做线程,并发上千时,就同时有几千的线程在处理请求,线程带来的内存占用很大,线程的上下午切换带来的cpu开销也大,性能就上不去了。

异步非阻塞是什么呢?

一个请求的完整过程:请求过来,创建链接,而后接收数据,接收数据后,再发送数据。

具体到系统底层,就是读写事件,当读写时间没有准备好时,若是不用非阻塞的方式来调用,就得阻塞调用了,事件没准备好,就只能等,等事件准备好再继续。阻塞调用会进入内核等待,让出cpu,对单线程的worker来讲,显然不合适,当网络事件越多时,等待不少,cpu利用率上不去。非阻塞就是,事件没有准备好,立刻返回eagain,表示事件还没准备好,过会儿再来,过一会,再来检查一下事件,直到事件准备好为止,在这期间,你能够先去作其余事情,而后再来看看事件好了没。这时,虽不阻塞了,可是还得不时来检查事件的状态,带来的开销也不小。因此有了异步非阻塞的事件处理机制,具体到系统调用就是像 select/poll/epoll/kquene这样的系统调用。提供一种机制,让你能够同时监控多个事件,调用他们是阻塞的,可是能够设置超时时间,在超时时间以内,若是有事件准备好了就返回。这种机制解决了上面的两个问题,以epoll为例,当事假没准备好时,放到epoll里,事件准备好了,就去读写,当读写返回eagain时,将它再次加入epoll,这样,只要有事件准备好了,就去处理它,只有当全部事件都没有准备好时,才在epoll里等着。这样,就能够支持大量的并发,这里的并发请求,是指未处理完的请求,线程只有一个,同时处理的请求只有一个,只是在请求间不断切换,切换是由于异步事件未准备好,主动让出的。这里的切换没有什么代价,能够理解为在循环处理多个准备好的事件,事实上也是。与多线程相比,这种事件处理方式有很大优点,不需建立线程,每一个请求占用的内存也不多,没有上下文切换,事件处理很是轻量级,没有上下文切换的开销,更多并发,只会占更多的内存而已。如今的网络服务器基本都采用这种方式,也是nginx性能高效的主要缘由

 

         推荐设置worker数与cpu的核数一致,由于更多的worker,会致使进程竞争cpu资源,从而带来没必要要的上下文切换。

1.2      操做nginx

怎样操做运行的nignx呢?master进程会接收来自外界发来的信号,所以要控制nginx,经过kill向master进程发送信号就能够了。如 kill –HUP pid,重启nginx,或从新加载配置,而不中断服务。Master进程在接到这个信号后,会先从新加载配置文件,而后再启动新的worker进程,并向全部老的worker进程发信号,再也不接收新的请求,而且在处理完全部未处理完的请求后,退出。新的worker启动后,就开始接收新的请求。

 

直接给master发信号,是比较老的操做方法,在nginx0.8版本后,可使用命令行参数,方便管理,如./nginx –s reload ,重启nginx; ./nginx –s stop,中止nginx。这种方式的内部原理是,执行命令时,会启动一个新的nginx进程,该进程在解析到reload参数后,知道目标是控制nginx从新加载配置文件,它会向master进程发送信号,接下来的处理,和直接向master进程发送信号同样。

1.3      Nginx 处理请求

Worker进程是怎么处理请求的呢?

一个链接请求过来,每一个进程都有可能处理这个链接。Worker进程是从master进程fork出来的,在master进程里,先创建好须要listen的socket(listenfd)后,而后再fork出多个worker进程。全部worker进程的listenfd会在新链接到来时变得可读,为了保证只有一个进程处理该链接,全部worker进程在注册listenfd读事件前抢accept_mutex,抢到互斥锁的那个进程注册listenfd读事件,在读事件里调用accept接受该链接。当一个worker进程在accept这个链接以后,开始读取请求,解析请求,产生数据后,再返回给客户端,最后才断开链接,这就是一个完整的请求处理。一个请求,彻底由worker处理,且只在一个worker里处理。

 

2       Nginx基础概念

2.1      Connection

Nginx中connection是对tcp链接的封装,包括链接的socket,读事件,写事件。

Nginx怎么处理一个链接的呢?nginx在启动时,会解析配置文件,获得须要监听的端口与ip,而后在nginx的master进程里,先初始化这个监控的socket,而后再fork出多个子进程,子进程竞争accept新的链接。此时,客户端就能够像nginx发起链接了,当客户端与服务器经过三次握手创建好一个链接,nginx的某一个子进程会accept成功,获得这个socket,而后建立nginx对链接的封装,接着,设置读写事件处理函数并添加读写事件来与客户端进行数据的交换。最后,nginx或客户端主动关掉链接。

 

         Nginx也能够做为客户端来请求其余server的数据,此时,与其它server建立的链接,也封装在ngx_connection中。

 

         Nginx中,每一个进程会有一个链接数的最大上限,这个上限与系统对fd的限制不同。操做系统中,使用ulimit -n,能够获得一个进程所能打开的fd的最大数,即nofile,由于每一个socket会占用一个fd,因此这个会限制进程的最大链接数,fd用完后,再建立socket,就会失败。Nginx经过设置worker_connections来设置每一个进程支持的最大链接数,若是该值大于nofile,那么实际的最大链接数是nofile,nginx会有警告。Nginx在实现时,是经过一个链接池来管理的,每一个worker进程都有一个独立的链接池,链接池大小是worker_connections。这里链接池里面保存的其实不是真实的链接,只是一个worker_connections大小的ngx_connection_t结构的数组。Nginx经过一个链表free_connections来保存全部的空闲ngx_connection_t.每次获取一个链接时,就从空闲链接链表中获取一个,用完后,再放回空闲链接链表里面。

 

         Worker_connections,表示每一个worker所能创建链接的最大值,一个nginx能创建的最大链接数是:worker_connections * worker_processes.所以对于HTTP请求本地资源,最大并发能够是 worker_connections * worker_processes.而若是是HTTP做为反向代理来讲,最大并发数是 worker_connections * worker_processes/2.由于做为反向代理服务器,每一个并发会创建与客户端的链接和与后端服务器的链接,占用2个链接。

 

         如何保证worker进程竞争处理链接的公平呢?

         若是某个进程获得accept的机会比较多,它的空闲链接会很快用完,若是不提早作一些控制,当accept到一个新的tcp链接后,由于没法获得空闲链接,并且没法将此链接转交其余进程,最终致使此tcp链接得不处处理。而其余进程有空余链接,却没有处理机会。如何解决这个问题呢?

         Nginx的处理得先打开accept_mutex,此时只有得到了accept_mutex的进程才会去添加accept事件,nginx会控制进程是否添加accept事件。Nginx使用一个叫ngx_accept_disabled变量控制是否竞争accept_mutex锁。这个变量与worker进程的剩余链接数有关,当该变量大于0时,就不去尝试获取锁,等于让出获取链接的机会。这样就能够控制多进程间链接的平衡了。

2.2      Keep alive

http请求是请求应答式的,若是咱们知道每一个请求头与相应体的长度,那么咱们能够在一个链接上面执行多个请求。即长链接。若是当前请求须要有body,那么nginx就须要客户端在请求头中指定content-length来表面body的大小,不然返回400错误。那么响应体的长度呢?http协议中关于响应body长度的肯定:

1 对于http1.0 协议来讲,若是响应头中有content-length头,则以content-length的长度就能够知道body的长度,客户端在接收body时,能够依照这个长度接收数据,接收完后,就表示该请求完成。若是没有content-length,客户端会一直接收数据,直到服务端主动端口链接,才表示body接收完

2 对于http1.1 协议,若是响应头中transfer-encoding为chunked传输,表示body是流式输出,body被分红多个块,每块的开始会标示出当前块的长度,此时,body不须要指定长度。若是是非chunked传输,并且有Content-length,则按照content-length来接收数据。不然,非chunked且没有content-length,则客户端接收数据,知道服务器主动断开。

 

         客户端请求头中connection为close,表示客户端要关掉长链接,若是是keep-alive,则客户端须要打开长链接。客户端的请求中没有connection这个头,根据协议,若是是http1.0,默认是close,若是是http1.1,默认是keep-alive。若是要keep-alive,nginx在输出完响应体后,会设置当前链接的keepalive属性,而后等待客户端下一次请求,nginx设置了keepalive的等待最大时间。通常来讲,当客户端须要屡次访问同一个server时,打开keepalive的优点很是大。

2.3      Pipe

http1.1中引入Pipeline,就是流水线做业,能够看作是keepalive的升华。Pipeline也是基于长链接的。目前就是利用一个链接作屡次请求,若是客户端要提交多个请求,对于keepalive,第二个请求,必需要等到第一个请求的响应接收完后,才能发起。获得两个响应的时间至少是2*RTT。而对于pipeline,客户端没必要等到第一个请求处理完,就能够发起第二个请求。获得两个响应的时间可能可以达到1*RTT。Nginx是直接支持pipeline的。Nginx对pipeline中的多个请求的处理不是并行的,而是一个接一个的处理,只是在处理第一个请求的时候,客户端就能够发起第二个请求。这样,nginx利用pipeline能够减小从处理完一个请求后到等待第二个请求的请求头数据的时间。

 

3       Nginx怎么用(安装与配置)

具体参见http://seanlook.com/2015/05/17/nginx-install-and-config/

或者http://blog.csdn.net/guodongxiaren/article/details/40950249

 

安装nginx

yum install nginx-1.6.3

3.1      Nginx.conf 配置

Nginx配置文件主要有4部分,main(全局设置)、server(主机设置)、upstream(上游服务器设置,主要为反向代理,负载均衡相关配置)和location(url匹配特定位置的设置),每部分包含若干指令。

Main部分的设置影响其余全部部分的设置;

Server部分主要用于指定虚拟机主机域名,ip和端口;

Upstream的指令用于设置一系列的后端服务器,设置反向代理及后端服务器的负载均衡;

Location部分用于匹配网页位置(如,跟目录“/”,”/images”等)。

它们之间的关系是,server继承main,location继承server,upstream既不会继承指令也不会被继承。

4.1 为何高并发重要

 

  和十年前相比,目前的互联网已经不可思议的普遍应用和普及。从NCSA用Apache搭的web服务器提供的可点击的文本HTML,已然进化成超过20亿人在线的通讯媒介。随着永久在线的我的电脑,移动终端以及平板电脑的增多,互联网在快速变化,经济系统也彻底数字有线化。提供实时可用信息和娱乐的在线服务变得更加复杂精巧。在线业务的安全需求也急剧变化。网站比从前更加复杂,须要在工程上作的更具备健壮性和可伸缩性。

  并发老是网站架构最大的挑战之一。因为web服务的兴起,并发的数量级在不断增加。热门网站为几十万甚至几百万的同时在线用户提供服务并不寻常。十年前,并发的主要缘由是因为客户端接入速度慢--用户使用ADSL或者拨号商务。如今,并发是由移动终端和新应用架构所带来,这些应用一般基于持久链接来为客户端提供新闻,微博,通知等服务。另外一个重要的因素就是现代浏览器行为变了,他们浏览网站的时候会同时打开4到6个链接来加快页面加载速度。

  举例说明一下慢客户端的问题,假设一个Apache网站产生小于100KB的响应--包含文本或图片的网页。生成这个页面可能须要1秒钟,可是若是网速只有80kbps(10KB/s),须要花10秒才能把这个页面发送到客户端。基本上,web服务器相对快速的推送100KB数据,而后须要等待10秒发送数据以后才能关闭链接。那么如今若是有1000个同时链接的客户端请求相同的页面,那么若是为每一个客户端分配1MB内存,就须要1000MB内存来为这1000个客户端提供这个页面。实际上,一个典型的基于Apache的web服务器一般为每一个链接分配1MB内存,而移动通讯的有效速度也一般是几十kbps。虽然借助于增长操做系统内核socket缓冲区大小,能够优化发送数据给慢客户端的场景,可是这并非一个常规的解决方案,而且会带来没法预料的反作用。

  随着持久链接的使用,并发处理的问题更加明显。为了不新建HTTP链接所带来的延时,客户端须要保持链接,这样web服务器就须要为每一个链接上的客户端分配必定数量的内存。

  所以,为了处理持续增加的用户带来的负载和更高量级的并发,网站须要大量高效的组件。而另外一方面,web服务器软件运行在诸如硬件(CPU,内存,磁盘),网络带宽,应用和数据存储架构等之上,这些基础设施显然也很重要。于是,随着同时在线数和每秒请求数的增加,web服务器性能也应该可以非线性扩展。

 

Apache再也不适用?

 

  Apache web服务器软件发源于1990年代,目前在互联网网站上占有率第一。Apache的架构适合当时的操做系统和硬件,而且也符合当时的互联网情况:一个网站一般使用一台物理服务器运行一个Apache实例。2000年以后,显然这种单服务器模型已经没法简单扩展来知足日益增加的web服务需求。虽然Apache为新功能开发提供了坚实的基础,但他为每一个新链接派生一个进程的作法(译注:Apache从2.4版本起已经支持事件模型),不适合网站的非线性扩展。最终,Apache成为一个通用的web服务器软件,聚焦于功能多样化,第三方扩展开发,以及web应用开发的通用性。然而,当硬件成本愈来愈低,每一个链接消耗的CPU和内存愈来愈多,使用这样功能繁多的单一软件再也不具备可伸缩性。

  于是,当服务器硬件、操做系统和网络设施再也不成为网站增加的主要限制因素时,网站开发者开始寻求更高效的手段来架设web服务器。大约十年前,著名软件工程师Daniel Kegel提出:“是时候让web服务器支持同时处理10000客户端了”,而且预言了如今称为云服务的技术。Kegel的C10K设想明显推进了许多人尝试解决这个问题--经过优化web服务器软件来支持大规模客户端链接的并发处理,nginx是其中作的最成功者之一。

  为了解决10000个并发链接的C10K问题,nginx基于一个彻底不一样的架构—更适合每秒同时链接数和请求数非线性增加。Nginx基于事件模型,而没有模仿Apache为每一个请求派生新进程或线程的作法。最终结果就是即便负载增长了,内存和CPU使用事件始终保持可预期。Nginx使用普通的硬件就能在一个服务器上处理数万的并发链接。

  Nginx的第一个版本发布以后,通常被用来同Apache一同部署,HTML、CSS、JavaScript脚本和图片等静态内容由nginx处理,来下降Apache应用服务器的并发和延时。随着开发演进的过程,nginx增长了FastCGI、uswge和SCGI等协议的支持,以及对分布式内存对象缓存系统如memcached的支持。也增长了其余有用的功能,例如支持负载均衡和缓存的反向代理。这些附加功能使nginx成为一个高效的工具集,用于构建可伸缩的web基础设施。

  2012年2月,Apache 2.4.x版本发布。虽然增长了新的并发处理核心模块和代理模块,用于增强可伸缩性和性能,但要说性能、并发能力和资源利用率是否能遇上或超过纯事件驱动模型的web服务器还为时尚早。Apache新版本具备了更好的性能值得高兴,对于nginx+Apache的web网站架构,虽然这可以缓解后端潜在的瓶颈,但并不能解决所有问题。

 

nginx有更多的优势吗?

 

  部署nginx最关键的好处就是可以高性能高效的处理高并发。同时,还有更多有意思的好处。

  最近几年,web架构拥抱解耦的理念而且将应用层设施从web服务器中分离。虽然如今仅仅是将原先基于LAMP(Linux, Apache, MySQL, PHP, Python or Perl)所构建的网站,变为基于LEMP(E表示Engine x)的。可是,愈来愈多的实践是将web服务器推入基础设施的边缘,而且用不一样的方法整合这些相同或更新的应用和数据库工具集。

  Nginx很适合作这些工做。他提供了必要的关键功能用于方便将下列功能从应用层剥离到更高效的边缘web服务器层:并发、长链接处理、SSL,静态内容、压缩和缓存、链接和请求限速,以及HTTP媒体流等。Nginx同时也容许直接整合memcached、Redis或者其余的NoSQL解决方案,加强为处理大规模并发用户的性能。

  随着现代编程语言和开发包普遍使用,愈来愈多的公司改变了应用开发和部署的方式。Nginx已经成为这些改变范例之中的最重要的部件之一,而且已经帮助许多公司在预算内快速启动和开发他们的web服务。

  Nginx开发始于2002年,2004年基于2-clause BSD受权正式对外发布。自发布起,Nginx用户就在不断增加,而且贡献提议,提交bug报告、建议和评测报告,这极大的帮助和促进了整个社区的发展。

  Nginx代码彻底用C语言从头写成,已经移植到许多体系结构和操做系统,包括:Linux、FreeBSD、Solaris、Mac OS X、AIX以及Microsoft Windows。Nginx有本身的函数库,而且除了zlib、PCRE和OpenSSL以外,标准模块只使用系统C库函数。并且,若是不须要或者考虑到潜在的受权冲突,能够不使用这些第三方库。

  谈谈关于Windows版本nginx。当nginx在Windows环境下工做时,Windows版本的nginx更像是概念验证版本,而不是全功能移植。这是因为目前nginx和Windows内核架构之间交互的某些限制致使。Windows版本ngnix已知的问题包括:低并发链接数、性能下降、不支持缓存和带宽策略。将来Windows版本的nginx的功能会更接近主流版本。

 

4.2 Nginx架构综览

 

  传统基于进程或线程的模型使用单独的进程或线程处理并发链接,于是会阻塞于网络或I/O操做。根据不一样的应用,就内存和CPU而言,这是很是低效的。派生进程或线程须要准备新的运行环境,包括在内存上分配堆和栈、生成一个新的运行上下文。建立这些东西还须要额外的CPU时间,并且过分的上下文切换引发的线程抖动最终会致使性能低下。全部这些复杂性在如Apache web服务器的老架构上一览无遗。在提供丰富的通用应用功能和优化服务器资源使用之间须要作一个权衡。

  最先的时候,nginx但愿为动态增加的网站得到更好的性能,而且密集高效的使用服务器资源,因此其使用了另一个模型。受不断发展的在不一样操做系统上开发基于事件模型的技术驱动,最终一个模块化,事件驱动,异步,单线程,非阻塞架构成为nginx代码的基础。

  Nginx大量使用多路复用和事件通知,而且给不一样的进程分配不一样的任务。数量有限的工做进程(Worker)使用高效的单线程循环处理链接。每一个worker进程每秒能够处理数千个并发链接、请求。

 

代码结构

 

  Nginx worker的代码包含核心和功能模块。核心负责维护一个紧凑的事件处理循环,而且在请求处理的每一个阶段执行对应的模块代码段。模块完成了大部分展示和应用层功能。包括从网络和存储设备读取、写入,转换内容,进行输出过滤,SSI(server-side include)处理,或者若是启用代理则转发请求给后端服务器。

  nginx模块化的架构容许开发者扩展web服务器的功能,而不须要修改nginx核心。Nginx模块可分为:核心、事件模块,阶段处理器,协议、变量处理器,过滤器,上游和负载均衡器等。目前,nginx不支持动态加载模块,即模块代码是和nginx核心代码一块儿编译的。模块动态加载和ABI已经计划在未来的某个版本开发。更多关于不一样模块角色的详细信息可在14.4章找到。

  Nginx在BSD、Linux和Solaris系统上使用kqueue、epoll和event ports等技术,经过事件通知机制来处理网络链接和内容获取,包括接受、处理和管理链接,而且大大加强了磁盘IO性能。目的在于尽量的提供操做系统建议的手段,用于从网络进出流量,磁盘操做,套接字读取和写入,超时等事件中及时异步地获取反馈。Nginx为每一个基于Unix的操做系统大量优化了这些多路复用和高级I/O操做的方法。

  图14.1展现了nginx架构的高层设计。

  前面提到过,nginx不为每一个链接派生进程或线程,而是由worker进程经过监听共享套接字接受新请求,而且使用高效的循环来处理数千个链接。Nginx不使用仲裁器或分发器来分发链接,这个工做由操做系统内核机制完成。监听套接字在启动时就完成初始化,worker进程经过这些套接字接受、读取请求和输出响应。

  事件处理循环是nginx worker代码中最复杂的部分,它包含复杂的内部调用,而且严重依赖异步任务处理的思想。异步操做经过模块化、事件通知、大量回调函数以及微调定时器等实现。总的来讲,基本原则就是尽量作到非阻塞。Nginx worker进程惟一会被阻塞的情形是磁盘性能不足。

  因为nginx不为每一个链接派生进程或线程,因此内存使用在大多数状况下是很节约而且高效的。同时因为不用频繁的生成和销毁进程或线程,因此nginx也很节省CPU时间。Nginx所作的就是检查网络和存储的状态,初始化新链接并添加到主循环,异步处理直到请求结束才从主循环中释放并删除。兼具精心设计的系统调用和诸如内存池等支持接口的精确实现,nginx在极端负载的状况下一般能作到中低CPU使用率。

  nginx派生多个worker进程处理链接,因此可以很好的利用多核CPU。一般一个单独的worker进程使用一个处理器核,这样能彻底利用多核体系结构,而且避免线程抖动和锁。在一个单线程的worker进程内部不存在资源匮乏,而且资源控制机制是隔离的。这个模型也容许在物理存储设备之间进行扩展,提升磁盘利用率以免磁盘I/O致使的阻塞。将工做负载分布到多个worker进程上最终能使服务器资源被更高效的利用。

  针对某些磁盘使用和CPU负载的模式,nginx worker进程数应该进行调整。这里的规则比较基本,系统管理员应根据负载多尝试几种配置。一般推荐:若是负载模式是CPU密集型,例如处理大量TCP/IP协议,使用SSL,或者压缩数据等,nginx worker进程应该和CPU核心数相匹配;若是是磁盘密集型,例如从存储中提供多种内容服务,或者是大量的代理服务,worker的进程数应该是1.5到2倍的CPU核心数。一些工程师基于独立存储单元的数目来决定worker进程数,虽然这个方法的有效性取决于磁盘存储配置的类型,。

  Nginx开发者在下个版本中要解决的一个主要问题是怎么避免磁盘I/O引发的阻塞。目前,若是没有足够的存储性能为一个worker进程的磁盘操做提供服务,这个进程就会阻塞在磁盘读写操做上。一些机制和配置指令用于缓解这个磁盘I/O阻塞的场景,最显著的是sendfile和AIO指令,这一般能够大幅提高磁盘性能。应该根据数据集(data set),可用内存数,以及底层存储架构等来规划安装nginx。

  当前的worker模型的另外一个问题是对嵌入脚本的支持有限。举例来讲,标准的nginx发布版只支持Perl做为嵌入脚本语言。这个缘由很简单:嵌入脚本极可能会在任何操做上阻塞或者异常退出,这两个行为都会致使worker进程挂住而同时影响数千个链接。将脚本更简单,更可靠地嵌入nginx,而且更适合普遍应用的工做已经列入计划。

 

nginx 进程角色

 

  Nginx在内存中运行多个进程,一个master进程和多个worker进程。同时还有一些特殊用途的进程,例如缓存加载和缓存管理进程。在nginx 1.x版本,全部进程都是单线程的,使用共享内存做为进程间通讯机制。Master进程使用root用户权限运行,其余进程使用非特权用户权限运行。

  master进程负责下列工做:

  • 读取和校验配置文件
  • 建立、绑定、关闭套接字
  • 启动、终止、维护所配置的worker进程数目
  • 不中断服务刷新配置文件
  • 不中断服务升级程序(启动新程序或在须要时回滚)
  • 从新打开日志文件
  • 编译嵌入Perl脚本

  Worker进程接受、处理来自客户端的链接,提供反向代理和过滤功能以及其余nginx所具备的全部功能。因为worker进程是web服务器每日操做的实际执行者,因此对于监控nginx实例行为,系统管理员应该保持关注worker进程。

  缓存加载进程负责检查磁盘上的缓存数据而且在内存中维护缓存元数据的数据库。基本上,缓存加载进程使用特定分配好的目录结构来管理已经存储在磁盘上的文件,为nginx提供准备,它会遍历目录,检查缓存内容元数据,当全部数据可用时就更新相关的共享内存项。

  缓存管理进程主要负责缓存过时和失效。它在nginx正常工做时常驻内存中,当有异常则由master进程重启。

 

Nginx缓存简介

 

  Nginx在文件系统上使用分层数据存储实现缓存。缓存主键可配置,而且可以使用不一样特定请求参数来控制缓存内容。缓存主键和元数据存储在共享内存段中,缓存加载进程、缓存管理进程和worker进程都能访问。目前不支持在内存中缓存文件,但能够用操做系统的虚拟文件系统机制进行优化。每一个缓存的响应存储到文件系统上的不一样文件,Nginx配置指令控制存储的层级(分几级和命名方式)。若是响应须要缓存到缓存目录,就从URL的MD5哈希值中获取缓存的路径和文件名。

  将响应内容缓存到磁盘的过程以下:当nginx从后端服务器读取响应时,响应内容先写到缓存目录以外的一个临时文件。nginx完成请求处理后,就将这个临时文件重命名并移到缓存目录。若是用于代理功能的临时目录位于另一个文件系统,则临时文件会被拷贝一次,因此建议将临时目录和缓存目录放到同一个文件系统上。若是须要清除缓存目录,也能够很安全地删除文件。一些第三方扩展能够远程控制缓存内容,并且整合这些功能到主发布版的工做已经列入计划。

 

4.3 Nginx配置文件

 

  Nginx配置系统来自于Igor Sysoev使用Apache的经验。他认为可扩展的配置系统是web服务器的基础。当维护庞大复杂的包括大量的虚拟服务器、目录、位置和数据集等配置时,会遇到可伸缩性问题。对于一个相对大点的网站,系统管理员若是没有在应用层进行恰当的配置,那么这将会是一个噩梦。

  因此,nginx配置为简化平常维护而设计,而且提供了简单的手段用于web服务器未来的扩展。

  配置文件是一些文本文件,一般位于/usr/local/etc/nginx/etc/nginx。主配置文件一般命名为nginx.conf为了保持整洁,部分配置能够放到单独的文件中,再自动地被包含到主配置文件。但应该注意的是,nginx目前不支持Apache风格的分布式配置文件(如.htaccess文件),全部和nginx行为相关的配置都应该位于一个集中的配置文件目录中。

  Master进程启动时读取和校验这些配置文件。因为worker进程是从master进程派生的,因此可使用一份编译好、只读的配置信息。配置信息结构经过常见的虚拟内存管理机制自动共享。

  Nginx配置具备多个不一样的上下文,如:main, http, server, upstream, location (以及用于邮件代理的 mail ) 等指令块。这些上下文不重叠,例如,一个location 指令块是不能放入main指令块中。而且,为了不没必要要的歧义,不存在一个相似于“全局web服务器”的配置。Nginx配置特地作的整洁和富有逻辑性,容许用户能够创建包含上千个指令的复杂的配置文件。在一次私人谈话中,Sysoev说:“全局服务器配置中的位置、目录和其余一些指令是Apache中我所不喜欢的特性,因此这就是不在nginx实现这些的缘由。”

  配置语法、格式和定义遵循一个所谓的C风格协定。这种构建配置文件的方法在开源软件和商业软件中有普遍的应用。经过设计,C风格配置很适合嵌套描述,富有逻辑性,易于建立、读取和维护,深受广大工程师喜欢。同时nginx的C风格配置也易于自动化。

  虽然一些nginx配置指令看起来像Apahce配置的一部分,可是设置一个nginx实例是彻底不一样的体验。例如,虽然nginx支持重写规则,可是系统管理员要手工的转换Apache重写配置使之适合nginx风格。一样,重写引擎的实现也是不同的。

  一般来讲,nginx设置也提供了几种原始机制的支持,对于高效的web服务器配置颇有帮助。有必要简单了解下变量和try_files指令,这些差很少是nginx所独有的。Nginx开发了变量用于提供附加的更强大的机制来控制运行时的web服务器配置。变量为快速赋值作了优化,而且在内部预编译为索引。赋值是按需计算的,例如,变量的值一般只在这个请求的生命周期中计算一次,然后缓存起来。变量可在不一样的配置指令中使用,为描述条件请求处理行为提供了更多弹性。

  try_files指令对于用更适当的方式逐渐替换if 条件配置语句是很重要的,而且它设计用来快速高效的尝试不一样的URI与内容之间的映射。总的来讲,try_files指令很好用,而且及其高效和有用。推荐读者完整的看看这个指令,并在任何能用的地方用上它。

 

4.4 深刻nginx

 

  前面提到过,nginx代码包含核心和其余模块。核心负责提供web服务器的基础,web和邮件反向代理功能;实现底层网络协议,构建必要的运行环境,而且保证不一样模块之间的无缝交互。可是,大部分协议相关以及应用相关的特性是由其余模块完成,而不是核心模块。

  在内部,nginx经过模块流水线或模块链处理链接。换言之,每一个操做都有一个模块作对应的工做。例如:压缩,修改内容,执行SSI,经过FastCGI或uwsgi协议同后端应用服务器通讯,以及同memcached通讯等。

  在核心和实际功能模块之间,有两个模块http和mail。这两个模块在核心和底层组件之间提供了附加抽象层。这些模块处理同各自应用层协议相关的事件序列,如实现HTTP、SMTP或IMAP。与核心一块儿,这些上层模块负责以正确的次序调用各自的功能模块。虽然目前HTTP协议是做为http模块的一部分实现的,但未来计划将其独立为一个功能模块,以支持其余协议,如SPDY(参考“SPDY: An experimental protocol for a faster web”)。

  功能模块能够分为事件模块,阶段处理器,输出过滤器,变量处理器,协议模块,上游和负载均衡器等类型。虽然事件模块和协议也用于mail模块,可是这些模块大部分用于补充nginx的HTTP功能。事件模块提供了基于操做系统的事件通知机制,如kqueue 或 epoll,这些取决于操做系统的能力和构建配置。协议模块容许nginx经过HTTPS, TLS/SSL, SMTP, POP3 和 IMAP等协议通讯。

  一个典型的HTTP请求处理周期以下:1. 客户端发送HTTP请求。2. nginx核心从配置文件查找匹配该请求的位置,根据这个位置信息选择适当的阶段处理器。3. 若是配置为反向代理,负载均衡器挑选一个上游服务器用于转发请求。4. 阶段处理器完成工做,而且传递每一个输出缓冲区给第一个过滤器。5. 第一个过滤器传递输出给第二个过滤器。6. 第二个过滤器传递输出给第三个等等。7. 最终响应发送给客户端。

  Nginx模块是高度可定制化的。它经过一系列指向可执行函数的回调指针来工做。于是,带来的反作用就是为第三方开发者加剧了负担,由于他们必须精确的定义模块应怎么运行和什么时候运行。Nginx的API和开发者文档都通过优化使之更具备可用性来减轻开发难度。

  一些在nginx中插入模块的例子:

  • 配置文件读取和处理以前
  • Location和server的每一个配置指令生效时
  • Main配置初始化时
  • Server配置初始化时
  • Server配置合并到main配置时
  • Location配置初始化或者合并到上级server配置时
  • Master进程启动或退出时
  • 新的worker进程启动或退出时
  • 处理请求时
  • 过滤响应头和响应体时
  • 挑选,初始化和从新初始化上游服务器时
  • 处理上游服务器响应时
  • 完成与上游服务器的交互时

  在Worker内部,生成响应的过程以下:

  1. 开始ngx_worker_process_cycle()
  2. 经过操做系统的机制处理事件(如 epoll 或 kqueue)。
  3. 接受事件并调用对应的动做。
  4. 处理或转发请求头和请求体。
  5. 生成响应内容,并流式发送给客户端。
  6. 完成请求处理。
  7. 从新初始化定时器和事件。

  事件循环自身(步骤5和6)确保增量产生响应而且流式发送给客户端。

  更详细的处理HTTP请求过程以下:

  1. 初始化请求处理
  2. 处理请求头
  3. 处理请求体
  4. 调用对应的处理器
  5. 执行全部的处理阶段

  当nginx处理一个HTTP请求时,会通过多个处理阶段。每一个阶段都调用对应的处理器。一般,阶段处理器处理一个请求后产生对应的输出,阶段处理器在配置文件的location中定义。

  阶段处理器通常作四件事情:获取location配置,产生适当的响应,发送响应头,发送响应体。处理器函数有一个参数:描述请求的结构体。请求结构体有许多关于客户端请求的有用信息,例如:请求方法类型,URI和请求头等。

  当读取完HTTP请求头以后,nginx查找相关的虚拟服务器配置,若是找到虚拟服务器,请求会通过下面六个阶段:

  1. server rewrite phase
  2. location phase
  3. location rewrite phase (which can bring the request back to the previous phase能够将请求带回到前面的阶段)
  4. access control phase
  5. try_files phase
  6. log phase

  为了给请求生成必要的响应内容,nginx传递请求给匹配的内容处理器。根据location配置,nginx会先尝试无条件处理器,如perl,proxy_pass,flv,mp4等。若是这个请求不匹配这几个内容处理器,将会按下面顺序挑选一个处理器:random index,index,autoindex,gzip_static,static

  Nginx文档中有Index模块的详细内容,这个模块只处理结尾为斜杠的请求。若是不匹配mp4autoindex模块,则认为响应内容是磁盘上的一个文件或目录(即静态的),这由static内容处理器完成服务。若是是目录,将自动重写URI保证结尾是一个斜杠(从而发起一个HTTP重定向)。

  内容处理器产生的内容则被传递到过滤器。过滤器也同location相关,一个location可配置多个过滤器。过滤器加工处理器产生的输出。处理器的执行顺序在编译时决定,对于原生过滤器,顺序是已经定义好的,对于第三方过滤器,能够在编译阶段设置前后顺序。当前的nginx实现中,过滤器只能修改输出的数据,还不能编写修改输入的数据的过滤器。输入过滤器将在未来的版本提供。

  过滤器遵循一个特定的设计模式。过滤器被调用后开始工做,调用下一个过滤器直到过滤器链中的最后一个。完成以后,nginx结束响应。过滤器不用等待前面的过滤器结束。一旦前一个过滤器提供的输入已经可用,下一个过滤器即可以启动本身的工做(很像Unix中的管道)。于是,在从上游服务器接收到全部的响应以前,所生成的输出响应已经被发送给客户端。

  过滤器有header filter和body filter,nginx将响应的header和body分别发送给相关的过滤器。

  Header filter包含3个基本步骤:

  1. 决定是否处理这个响应
  2. 处理响应
  3. 调用下一个过滤器

  body filter转换所生成的内容。body filter的一些例子:

  • SSI
  • XSLT过滤
  • 图片过滤(例如调整图片大小)
  • 字符集转换
  • Gzip压缩
  • Chunked编码

  通过过滤器链以后,响应被发送到writer。有两个额外的具备特定功能的过滤器与writer相关,copy filter和postpone filter。Copy filter负责将相关的响应内容填充到内存缓冲区,这些响应内容有可能存储在反向代理的临时目录。Postpone filter用于子请求处理。

  子请求是一个处理请求、响应很重要的机制,同时也是nginx最强大的功能之一。经过子请求,Nginx能够返回另外一个URL的响应,这个URL与客户端最初请求的URL不一样。一些web框架称之为内部跳转,但nginx功能更强,不只能运行多个子请求并将这些子请求的响应合并成一个,并且还能嵌套和分级。子请求能够产生子-子请求,子-子请求能产生子-子-子请求。子请求能够映射到磁盘文件,其余处理,或者上游服务器。子请求在根据原始响应数据插入附加内容时颇有用。例如,SSI模块使用一个过滤器解析返回文档的内容,而后用指定URL的内容来替换include指令。或者作一个过滤器,可以在一个URL产生的响应内容以后附加一些新的文档内容。

  上游(upstream)和负载均衡器一样也值得简单介绍一下。上游用于实现反向代理处理器(proxy_pass处理器)。上游模块组装好请求发送给上游服务器(或称为“后端”),而后接收上游服务器返回的响应。这个过程不调用输出过滤器。上游模块仅仅设置回调函数,用于当上游服务器可读或可写时调用。回调函数实现下列功能:

  • 准备请求缓冲区(或缓冲区链),用于发送给上游服务器
  • 从新初始化、重置与上游服务器之间的链接(应在从新发起请求以前)
  • 处理上游服务器响应的首字节,而且保存响应内容的指针
  • 放弃请求(当客户端过早关闭链接时)
  • 结束请求(当nginx完成读取上游服务器响应时)
  • 修整响应体内容(例如除去空白)

  若是上游服务器大于一个,负载均衡器模块可附加在proxy_pass处理器上,用于提供选择上游服务器的能力。负载均衡器注册了一个配置文件指令,提供附加的上游服务器初始化功能(经过DNS解析上游服务器名字等),初始化链接结构体,决定如何路由请求,而且更新状态信息。目前,nginx支持两种标准的上游服务器负载均衡规则:轮询和ip哈希。

  上游模块和负载均衡处理机制的算法能检测上游服务器异常,并将新请求从新路由到可用的上游服务器,还有更多的工做计划增强这个功能。总之,负载均衡器的改进计划更多些,下个版本的nginx将大幅度提高在不一样上游服务器之间分发负载和健康检测的机制。

还有一些有意思的模块在配置文件中提供了额外的变量供使用。这些变量经过不一样的模块生成和更新,有两个模块彻底用于变量:geomapgeo模块用于更方便的基于IP地址追踪客户端地址,这个模块能够根据客户端IP地址生成任意变量。另外一个map模块容许从一个变量生成另外一个变量,提供将主机名和其余变量方便的进行映射的基本能力。这类模块称为变量处理器。

  nginx worker进程实现的内存分配机制从某方面来讲来自于Apache。Nginx内存管理的高层描述:对于每一个链接,必要的内存缓冲区是动态分配的,用于存储或操纵请求、响应的头和体,当链接关闭时释放。很重要的一点是nginx尽量的去避免在内存中拷贝数据,大部分的数据经过指针进行传递,而不是调用memcpy。

  更深刻一点,当一个模块产生响应时,这些响应内容放入内存缓冲区,并被添加到一个缓冲区链。这个缓冲区链一样适用于子请求处理。因为根据模块类型不一样存在多个处理场景,因此nginx中的缓冲区链至关复杂。例如,在实现body filter模块时,精确的管理缓冲区是很棘手的。这个模块同一时间只能处理缓冲区链中的一个缓冲区,它必须决定是否覆盖输入缓冲区,是否用新分配的缓冲区替换这个缓冲区,或者在这个缓冲区以前或以后插入一个新缓冲区。更复杂的状况,有时一个模块收到的数据须要多个缓冲区存储,所以它必须处理一个不完整的缓冲区链。可是因为目前nginx仅提供了底层API用于操纵缓冲区链,因此开发者应该真正掌握nginx这一晦涩难懂的部分以后,再去开发第三方模块。

  上面提到的内容中须要注意的一点,内存缓冲区是为链接的整个生命周期分配的,因此对于长链接须要消耗额外的内存。同时,对于空闲的keep alive链接,nginx仅消耗550字节内存。未来的nginx版本可能进行优化以使长链接重用和共用内存缓冲区。

  内存分配管理的任务由nginx内存池分配器完成。共享内存区用于存放接受互斥锁(accept mutex),缓存元数据,SSL会话缓存,以及和带宽策略管理(限速)相关的信息。Nginx实现了slab分配器用于管理共享内存,提供了一系列锁机制(互斥锁和信号量),以容许安全地并发使用共享内存。为了组织复杂的数据结构,nginx也提供了红黑树的实现。红黑树用于在内存中保存缓存元数据,查找非正则location定义,以及其余一些任务。

  不幸的是,上述内容从未以一致而且简单的方式介绍过,以至开发第三方模块的工做至关复杂。虽然有一些nginx内部实现的好文档,例如,Evan Miller写的,可是这些文档须要作不少还原工做,nginx模块的开发仍是像变魔术同样。

  虽然开发第三方模块是如此困难,nginx社区最近仍是涌现大量有用的第三方模块。例如,将Lua解释器嵌入nginx,负载均衡附加模块,完整的Web DAV支持,高级缓存控制,以及其余本章做者所鼓励和未来支持的有趣的第三方工做。

 

4.5 优秀实践

 

  Igor Sysoev开始编写nginx时,大部分构建互联网的软件都已经存在,这些软件的架构通常遵循传统服务器和网络硬件、操做系统、以及过去互联网架构的定义。可是这并未阻止Igor考虑在web服务器领域作进一步的工做。因此,显然第一个优秀实践是:总有提高空间。

  带着开发更好web软件的想法,Igor花了不少时间开发原始代码结构,并研究在多个操做系统下优化代码的不一样手段。十年后,考虑到1.0版本已经通过十年活跃开发,Igor开发了2.0版本原型。很明显,这个新架构的初始原型和代码结构,对于软件的后续开发及其重要。

  另外值得提到的一点是聚焦开发。Nginx 的windows版本是个好例子,说明不管在开发者的核心技能或应用目标上避免稀释开发工做是值得的。一样努力增强nginx重写引擎对现存遗留配置的后向兼容能力,也是值得的。

  最后特别值得提到的是,尽管nginx开发者社区并不大,nginx的第三方模块和扩展仍是成为nginx受欢迎的一个很重要的因素。Nginx用户社区和做者们很感谢Evan Miller, Piotr Sikora, Valery Kholodkov, Zhang Yichun (agentzh)以及其余优秀软件工程师所作的工做。


 

英文原文:http://www.aosabook.org/en/nginx.html

中文参考:http://www.ituring.com.cn/article/4436

相关文章
相关标签/搜索