模块化结构的思想是一个很久的概念,但也正是成熟的思想造就了Nginx的巨大优越性。
我们知道Nginx从总体上来讲是有许多个模块构成的。习惯将Nginx分为5大模块分别为:核心模块,标准HTTP模块,可选HTTP模块,邮件服务模块和第三方模块。
这5个模块由上到下重要性一次递减。
(1)核心模块;
核心模块是Nginx服务器正常运行必不可少的模块,如同操作系统的内核。它提供了Nginx最基本的核心服务。像进程管理、权限控制、错误日志记录等;
(2)标准HTTP模块;
标准HTTP模块支持标准的HTTP的功能;
(3)可选HTTP模块;
可选HTTP模块主要用于扩展标准的HTTP功能,让Nginx能处理一些特殊的服务;
(4)邮件服务模块;
邮件服务模块主要用于支持Nginx的邮件服务;
(5)第三方模块;
第三方模块是为了扩展Nginx服务器应用,完成开发者想要的功能;
*******Nginx中的模块命名有自己的习惯*********
一般以Ngx_作为前缀,——module作为后缀,中间使用一个或者多个英文单词描述模块的工能,例如Ngx_core_module表示该模块提供Nginx的核心功能等;
具体各个模块中包含哪些模块可以自己去源码中查询,这里略过;
从架构设计上说,Nginx服务器是与众不同的。其一在于它的模块化设计;其二也是更重要的一点在于它对与客户端请求的处理机制上;
web服务器和客户端是一对多的关系,Web服务器必须有能力同时为多个客户端提供服务。一般来说完成并行处理请求工作有三种方式:
1.多进程方式;
2.多线程方式;
3.异步方式;
这里简单说明一下这三种方式:
(1)多进程方式
多进程方式指,服务器每当收到一个客户端时。就有服务器主进程生成一个子进程出来和客户端建立连接进行交互。指导连接断开。该子进程就结束了。
多进程方式的优点是设计简单,各个子进程相对独立,处理客户端请求时彼此不受干扰;缺点是操作系统生成一个子进程需要进行内存复制等操作,在资源和时间上会产生一定的开销;当有大量请求时,会导致系统性能下降;
(2)多线程方式
多线程方式指每当服务器接收到一个请求后,会由服务器主进程派生出一个线程出来和客户端进行交互。由于操作系统产生出一个线程的开销远远小于一个进程的开销。故多线程方式在很大程度上减轻了Web服务器对系统资源的要求。但同时由于多个线程位于一个进程内,可以访问同样的内存空间。所以需要开发者自己对内存进程管理,增大了难度。
(3)异步方式
异步方式适合多进程和多线程完全不同的一种处理客户端请求的方式。这里有几个概念我们需要熟悉一下: 同步,异步,阻塞,非阻塞 ;
在网络通信中同步和异步是描述通信模式的概念。
同步:发送方发送完请求后,需要等待接收到接收方发回的响应,才能发送下一个请求;所有请求在服务端得到同步,发送方和接收方的步调是一致的;
异步 :和同步机制相反,在异步机制中,发送方发出一个请求后,不等接收方响应这个请求,就继续发送下一个请求;所有来自发送方的请求形成一个队列,接收方处理完成后通知发送方;
在进程处理调度方式上用阻塞与非阻塞。在网络通信中主要指套接字socket的阻塞和非阻塞,而socket的实质就是IO操作。
阻塞 :调用结果返回之前,当前线程从运行状态被挂起,一直等到调用结果返回之后,才进入就绪状态,获取CPU后继续执行。
非阻塞 :和阻塞方式正好相反,如果调用结果不能马上返回,当前线程也不会马上返回,而是立即返回执行下一个调用。
因此就衍生出4中方式:同步阻塞,同步非阻塞,异步阻塞,异步非阻塞
这里简单解释一下异步非阻塞:发送方向接收方发送请求后,不用等待响应,可以继续其他工作;接收方处理请求时进行的IO操作如果不能马上得到结果,也不必等待,而是马上返回去去做其他事情。当IO操作完成以后,将完成状态和结果通知接收方,接收方再响应发送方。
与此同时Nginx服务器处理请求是怎样的呢???
Nginx服务器的一个显著的优势就是能够同时处理大量的并发请求。它结合多进程机制和异步机制。异步机制使用的是异步非阻塞方式。(Master-Worker)。
每个工作进程使用异步非阻塞方式,可以处理多个客户端请求。当某个工作进程接收到客户端的请求以后,调用IO进行处理,如果不能立即得到结果,就去处理其他的请求;而客户端在此期间也无需等待响应,可以去处理其他事情;当IO返回时,就会通知此工作进程;该进程得到通知,暂时挂起当前处理的失误去响应客户端请求。
也就是:
Nginx采用异步非阻塞方式来处理请求,处理请求具体到系统底层就是读写事件(所谓阻塞调用方式即请求事件还没准备好,线程只能一直去等,等事件准备好了再处理;而非阻塞即事件没准备好,马上返回ENGAIN,告诉你事件还没准准备好,而在这期间可以先去做其他事,再回头看看事件准备好了吗,时不时会看,需要的开销也是不小的)
异步可以理解为循环处理多个准备好的事件,不会导致无谓的资源浪费,当有更多的并发数只会占用更多的内存而已
从上面我们可以知道,Nginx服务器的工作进程调用IO后,就取进行其他工作了;当IO调用返回后,会通知工作进程。 但IO调用时如何把自己的状态通知给工作进程的呢??
一般解决这个问题有两种方法:
(1)让工作进程在进行其他工作的过程中间隔一段时间就去检查一下IO的状态,如果完成就响应客户端,如果未完成,继续工作。
(2)IO调用在完成后能主动通知工作进程。
当然最好的就是用第二种方法了;像select/poll/epoll等这样的系统调用就是用来支持第二种解决方案的。这些系统调用也常被称为事件驱动模型。他们提供了一种机制就只让进程同时处理多个并发请求,不用关心IO调用的具体状态。IO调用完全由事件驱动模型来管理。
Nginx中的事件驱动模型
就是用事件驱动处理库(多路IO复用),最常用的就是select模型,poll模型,epoll模型。
关于这三个模型的详解在这里可以看到:https://segmentfault.com/a/1190000003063859
通过这个上面的简单讲解,再加上服务器的架构的了解,可以对Nginx有一个简单的了解,希望对之后的源码剖析有帮助。
大致上Nginx的架构就是这样:
1.Nginx启动后,会产生一个主进程,主进程执行一系列的工作后会产生一个或者多个工作进程;
2.在客户端请求动态站点的过程中,Nginx服务器还涉及和后端服务器的通信。Nginx将接收到的Web请求通过代理转发到后端服务器,由后端服务器进行数据处理和组织;
3.Nginx为了提高对请求的响应效率,降低网络压力,采用了缓存机制,将 历史 应答数据缓存到本地。保障对缓存文件的快速访问;
##工作进程##
工作进程的主要工作有以下几项:
接收客户端请求;
将请求一次送入各个功能模块进行过滤处理;
IO调用,获取响应数据;
与后端服务器通信,接收后端服务器处理结果;
数据缓存
响应客户端请求;
##进程交互##
Nginx服务器在使用Master-Worker模型时,会涉及到主进程和工作进程的交互和工作进程之间的交互。这两类交互都依赖于管道机制。
1.Master-Worker交互
这条管道与普通的管道不同,它是由主进程指向工作进程的单向管道,包含主进程向工作进程发出的指令,工作进程ID等;同时主进程与外界通过信号通信;
2.worker-worker交互
这种交互是和Master-Worker交互是基本一致的。但是会通过主进程。工作进程之间是相互隔离的,所以当工作进程W1需要向工作进程W2发指令时,首先找到W2的进程ID,然后将正确的指令写入指向W2的通道。W2收到信号采取相应的措施。
Nginx的每个模块都基本符合单一职责原则
一般来说,Web服务器完成并行处理请求工作的三种方式有:多进程方式、多进程方式和异步方式
多进程方式是指,服务器每当接收到一个客户端时,就由服务器主进程生成一个子进程出来和该客户端建立连接进行交互,直到连接断开,该子进程就结束了
多进程方式的优点在于,设计和实现相对简单,各个子进程之间相对独立,处理客户端请求的过程彼此不受到干扰,并且当一个子进程产生问题时,不容易将影响蔓延到其他进程中,这保证了提供服务的稳定性。当子线程退出时,其占用资源会被操作系统回收,也不会留下任何垃圾。
其缺点是操作系统中生成一个子进程需要进行大量内存复制等操作,在资源和时间上会产生一定的额外开销。因此,如果Web服务器接收大量并发请求,就会对系统资源造成压力,导致系统性能下降。
Apache采用“预生成进程”方式,它将生成子进程的时机提前,在客户端请求还没有到来之前就预先生成好,当请求到来时,主进程分配一个子进程和该客户端进行交互,交互完成之后,该进程也不结束,而被主进程管理起来等待下一个客户端请求的到来
多线程方式是指当服务器每当接收到一个客户端时,会有服务器主进程派生一个线程出来和该客户端进行交互
由于操作系统产生一个线程的开销远远小于产生一个进程的开销,所以多线程方式在很大程度上减轻了Web服务器对系统资源的要求。该方式使用线程进行任务调度,开发方面可以遵循一定的标准,这相对来说比较规范和有利于协作。
多个线程位于同一进程内,可以访问同样的内存空间,彼此之间相互影响;同时,在开发过程中不可避免地要由开发者自己对内存进行管理,其增加了出错的风险
IIS服务器使用了多线程方式对外提供服务
同步机制:发送方发送请求之后,需要等待接收到接收方发回的响应后,才接着发送下一个请求
异步机制:发送方发送请求只有,不等待接收方响应这个请求,就继续发送下一个请求。
在同步机制中,所有的请求在服务器端得到同步,发送方和接收方对请求的处理步调是一致的;在异步机制中,所有来自发送方的请求形成一个队列,接收方处理完成后通知发送方
阻塞和非阻塞用来描述进程处理调用的方式,在网络通信中,主要指网络套接字Socket的阻塞和非阻塞方式,而Socket的实质也就是IO操作。
Socket的阻塞调用方式为,调用结果返回之前,当前线程从运行状态被挂起,一直等到调用结果返回之前,才进入就绪状态,获取CPU继续执行
Socket的非阻塞调用方式为,如果调用结果不能马上返回,当前线程也不会被挂起,而是立即返回执行下一个调用
Nginx结合多进程机制和异步机制对外提供服务。异步机制使用的是异步非阻塞方式
Nginx服务器启动后产生一个主进程和多个工作进程(可在配置文件中配置)。Ngnix服务器的所有工作进程都用于接收和处理客户端的请求。每个工作进程使用异步非阻塞方式,可以处理多个客户端的请求。当某个工作进程接收到客户端的请求以后,调用IO进行处理,如果不能立即得到返回,就去处理其他的请求;而客户端再次期间也无须等待响应,可以去处理其他的事情;当IO调用返回结果时,就会通知此工作进程;该进程得到通知,暂时挂起当前处理的事务,去响应客户端的请求
IO调用把状态通知给工作进程的两种方式:
select/poll/epoll/kqueue等这样的系统调用就是支撑第二种方案的。这种系统调用,也称为事件模型。IO调用完全由事件驱动模型来管理,事件准备好之后就通知工作进程事件已经就绪
事件驱动就是在持续事务管理过程中,由当前时间点上出现的事件引发的调动可用资源执行相关任务,解决不断出现的问题,防止事务堆积的一种策略
事件驱动模型一般由事件收集器、事件发送器和事件处理器三部分基本单元组成
事件收集器 专门负责收集所有的事件,包括来自用户的(鼠标、键盘事件等)、来自硬件的(时钟事件等)和来自软件的(操作系统、应用程序自身)。 事件发送器 负责将收集器收集到的事件分发到目标对象中。目标对象就是事件处理器所处的位置。 事件处理器 主要负责具体事件的响应工作,它往往要到实现阶段才完全确定
目标对象中事件处理器的几种方式:
大部分网络服务器都采用第三种方式,形成了事件驱动库。事件驱动库又被称为多路IO复用方法,最常见的伪:select、poll、epoll。Nginx服务器还支持rtsig、kqueue、dev/poll和eventport
各个版本Linux和Windows平台都支持的基本事件驱动模型
使用select库的一般步骤:
如果没有指定其他事件驱动模型,Nginx自动编译该库。
使用--with-select_module和--without-select_module强制Nginx是否编译该库
Linux平台的事件驱动模型,Windows不支持。
poll和select的基本使用方式是相同的,区别在于:select需要为读事件、写事件和异常事件都分别创建一个描述符集合,因此在最后轮询的时候,需要分别轮训这三个集合。而poss库只需要创建一个集合,在每个描述符对应的结构上分别设置读事件、写事件或异常事件,最后轮询的时候,可以同时检查这三种事件是否发生。poll库是select库的优化实现
如果没有指定其他事件驱动模型,Nginx自动编译该库。
使用--with-poll_module和--without-poll_module强制Nginx是否编译该库
epoll属于poll库的一个变种,最大的区别在于效率
epoll库通过相关调用通知内核创建一个有N个描述符的事件列表;然后,给这些描述符设置所关注的事件,并将它添加到内核的事件列表中。
完成设置之后,epoll库就开始等待内核通知事件发生了。某一事件发生后,内核将发生事件的描述符列表上报给epoll库。得到列表事件的epoll库,就可以进行事件处理了
epoll库是Linux平台上最高效的。它支持一个进程打开大数目的事件描述符,上限是系统可以打开文件的最大数目。同时,epoll库的IO效率不随描述符数目增加而线性下降,因为它只会对内核上报的“活跃”的描述符进行操作
使用rtsig模型时,工作进程会通过系统内核建立一个rtsig队列用于存放标记事件发生(在Nginx服务器应用中特指客户端请求发生)的信号。每一个事件发生时,系统内核就会发生一个信号存放到rtsig队列中等待工作进程的处理。
rtsig队列有长度限制,如果超过该长度就会发生溢出。默认情况下,Linux系统事件信号队列的最大长度设置为1024。在Liunx2.6.6-mm2之后的版本之前,通过修改内核参数/proc/sys/kernel/rtsig-max来自定义该长度设置。在Liunx2.6.6-mm2之后的版本中,该参数被取消,系统各个进程分别拥有各自的事件信号队列,这个队列的大小由Linux系统的RLIMIT_SIGPENDING参数定义,在执行setrlimit()系统调用时确定该大小。Linux提供了worker+rlimit_sigpending参数用于调节这种情况下的事件信号队列长度
当rtsig队列发生溢出时,Nginx将暂停使用rtsig模型,而调用poll库处理未处理的事件,直到rtsig信号队列全部清空,然后再次启动rtsig模型,以防止新的溢出发生
编译Nginx服务器时,使用-with-rtsig_module配置选项启用rtsig模型的编译
kqueue模型,主要用于FreeBSD4.1及以上版本、OpenBSD2.9及以上版本、NetBSD2.0及以上版本以及Mac OS X平台上。该模型也是poll库的一个变种,其和poll库的处理方式没有本质上的区别。该模型同时支持条件触发(只要满足条件就触发一个事件)和边缘触发(当状态发生改变触发一个事件)。在这些平台下,使用该模型用于请求处理,提高Nginx服务器性能
/dev/poll模型,主要用于Solaris7 11/99及以上版本、HP/US 11.22及以上版本、IRIX6.5.15及以上版本和Tru 64 UNIX 5.1A及以上版本。它使用了虚拟的/dev/poll设备,开发人员可以将要监视的文件描述符加入这个设备,然后通过ioctl()调用来获取事件通知。在以上平台中推荐使用
eventport模型,用于支持Solaris 10及以上版本。它可以有效防止内核崩溃等情况的发生
根据不同的部署平台,选择不同的事件驱动模型以提升Nginx服务器的处理性能
Nginx服务器启动后,产生一个主进程,主进程执行一系列工作后产生一个或者多个工作进程。
主进程主要进行Nginx配置文件解析、数据结构初始化、模块配置和注册、信号处理、网络监听生成、工作进程生成和管理等工作;
工作进程主要进行进程初始化、模块调用和请求处理等工作,是Nginx服务器提供服务的主体
Nginx服务器将接收到的Web请求通过代理转发到后端服务器,由后端服务器进行数据处理和页面组织,然后将结果返回。
Nginx服务器为了提高对请求的响应效率,进一步降低网络压力,采用了缓存机制,将历史应答数据缓存到本地。在每次Nginx服务器启动后的一段时间内,会启动专门的进程进行对本地缓存的内容重建索引,保证对缓存文件的快速访问
依赖于管道机制,交互的准备工作都是在工作进程生成时完成的
Run Loops,指的是进程内部用来不停地调配工作,对事件进行循环处理的一种模型。
该模型是一个集合,集合中的每一个元素称为一个Run-Loop。每个Run-Loop可运行在不同的模式下,其中可以包含它所监听的输入事件源、定时器以及在事件发生时需要通知的Run-Loop监听器。为了监听特定的事件,可以在Run Loops中添加相应的Run-Loop监听器。当被监听的事件发生时,Run-Loop会产生一个消息,被Run-Loop监听器捕获,从而执行预定的动作
Nginx服务器在工作进程中实现了Run-Loop事件处理循环的使用,用来处理客户端发送的请求事件
欢迎分享,转载请注明来源:夏雨云
评论列表(0条)