Nginx服务器启动后,产生一个主进程(master process),主进程执行一系列工作后产生一个或者多个工作进程(worker processes)。主进程主要进行Nginx配置文件解析、数据结构初始化、模块配置和注册、信号处理、网络监听生成、工作进程生成和管理等工作;工作进程主要进行进程初始化、模块调用和请求处理等工作,是Nginx服务器提供服务的主体。
在客户端请求动态站点的过程中,Nginx服务器还涉及和后端服务器的通信。Nginx服务器将接收到的Web请求通过代理转发到后端服务器,由后端服务器进行数据处理和页面组织,然后将结果返回。
另外,Nginx服务器为了提高对请求的响应效率,进一步降低网络压力,采用了缓存机制,将历史应答数据缓存到本地。在每次Nginx服务器启动后的一段时间内,会启动专门的进程对本地缓存的内容重建索引,保证对缓存文件的快速访问。
根据上面的分析,我们可以将Nginx服务器的结构大致分为主进程、工作进程、后端服务器和缓存等部分
在该示意图中,有几个方面的内容我们需要重点阐述,包括Nginx服务器的进程、进程交互和Run-Loop事件处理循环机制等。
Nginx服务器的进程
到目前为止,我们一共提到Nginx服务器的三大类进程:一类是主进程,另一类是由主进程生成的工作进程,还有刚才提到的用于为缓存文件建立索引的进程。
主进程(Master Process)
Nginx服务器启动时运行的主要进程。它的主要功能是与外界通信和对内部其他进程进行管理,具体来说有以下几点:
- 读取Nginx配置文件并验证其有效性和正确性。
- 建立、绑定和关闭Socket。
- 按照配置生成、管理和结束工作进程。
- 接收外界指令,比如重启、升级及退出服务器等指令。
- 不中断服务,实现平滑重启,应用新配置。
- 不中断服务,实现平滑升级,升级失败进行回滚处理。
- 开启日志文件,获取文件描述符。
- 编译和处理Perl脚本。
工作进程(Worker Process)
由主进程生成,生成数量可以通过Nginx配置文件指定,正常情况下生存于主进程的整个生命周期。该进程的主要工作有以下几项:
- 接收客户端请求。
- 将请求依次送入各个功能模块进行过滤处理。
- IO调用,获取响应数据。
- 与后端服务器通信,接收后端服务器处理结果。
- 数据缓存,访问缓存索引、查询和调用缓存数据。
- 发送请求结果,响应客户端请求。
- 接收主程序指令,比如重启、升级和退出等指令。
工作进程完成的工作还有很多,我们在这里列出了主要的几项。从这些工作中可以看到,该进程是Nginx服务器提供Web服务、处理客户端请求的主要进程,完成了Nginx服务器的主体工作。因此,在实际使用中,作为服务器管理者,我们应该重点监视工作进程的运行状态,保证Nginx服务器对外提供稳定的Web服务。
缓存索引重建及管理进程(Cache Loader & Cache Manager)
上图中的Cache模块,主要由缓存索引重建(Cache Loader)和缓存索引管理(Cache Manager)两类进程完成工作。缓存索引重建进程是在Nginx服务启动一段时间之后(默认是1分钟)由主进程生成,在缓存元数据重建完成后就自动退出;缓存索引管理进程一般存在于主进程的整个生命周期,负责对缓存索引进行管理。
缓存索引重建进程完成的主要工作是,根据本地磁盘上的缓存文件在内存中建立索引元数据库。该进程启动后,对本地磁盘上存放缓存文件的目录结构进行扫描,检查内存中已有的缓存元数据是否正确,并更新索引元数据库。
缓存索引管理进程主要负责在索引元数据更新完成后,对元数据是否过期做出判断。
这两个进程维护的内存索引元数据库,为工作进程对缓存数据的快速查询提供了便利。
进程交互
Nginx服务器在使用Master-Worker模型时,会涉及主进程与工作进程(Master-Worker)之间的交互和工作进程(Worker-Worker)之间的交互。这两类交互都依赖于管道(channel)机制,交互的准备工作都是在工作进程生成时完成的。
Master-Worker交互
工作进程是由主进程生成的(使用了fork函数,具体的源码实现我们在后边的相关章节中完整解析)。Nginx服务器启动以后,主进程根据配置文件决定生成的工作进程的数量,然后建立一张全局的工作进程表用于存放当前未退出的所有工作进程。
在主进程生成工作进程后,将新生成的工作进程加入到工作进程表中,并建立一个单向管道并将其传递给该工作进程。该管道与普通的管道不同,它是由主进程指向工作进程的单向管道,包含了主进程向工作进程发出的指令、工作进程ID、工作进程在工作进程表中的索引和必要的文件描述符等信息。
主进程与外界通过信号机制进行通信,当接收到需要处理的信号时,它通过管道向相关的工作进程发送正确的指令。每个工作进程都有能力捕获管道中可读事件,当管道中有可读事件时,工作进程从管道读取并解析指令,然后采取相应的措施。这样就完成了Master-Worker的交互。
Worker-Worker交互
Worker-Worker交互在实现原理上和Master-Worker交互基本是一样的。只要工作进程之间能够得到彼此的信息,建立管道,即可通信。由于工作进程之间是相互隔离的,因此一个进程要想知道另一个进程的信息,只能通过主进程来设置了。
为了达到工作进程之间交互的目的,主进程在生成工作进程后,在工作进程表中进行遍历,将该新进程的ID以及针对该进程建立的管道句柄传递给工作进程表中的其他进程,为工作进程之间的交互做准备。每个工作进程捕获管道中可读事件,根据指令采取响应的措施。
当工作进程W1需要向W2发送指令时,首先在主进程给它的其他工作进程信息中找到W2的进程ID,然后将正确的指令写入指向W2的通道。工作进程W2捕获到管道中的事件后,解析指令并采取相应措施。这样就完成了Worker-Worker交互。
Run Loops事件处理循环模型
Run Loops,指的是进程内部用来不停地调配工作,对事件进行循环处理的一种模型。它属于进程或者线程的基础架构部分。该模型对事件的处理不是自动的,需要在设计代码过程中,在适当的时候启动Run-Loop机制对输入的事件作出响应。
该模型是一个集合,集合中的每一个元素称为一个Run-Loop。每个Run-Loop可运行在不同的模式下,其中可以包含它所监听的输入事件源、定时器以及在事件发生时需要通知的Run-Loop监听器(Run-Loop Observers)。为了监听特定的事件,可以在Run Loops中添加相应的Run-Loop监听器。当被监听的事件发生时,Run-Loop会产生一个消息,被Run-Loop监听器捕获,从而执行预定的动作。
Nginx服务器在工作进程中实现了Run-Loop事件处理循环模型的使用,用来处理客户端发来的请求事件。该部分的实现可以说是Nginx服务器程序实现中最为复杂的部分,包含了对输入事件繁杂的响应和处理过程,并且这些处理过程都是基于异步任务处理的。
通过学习Nginx服务器的整体架构,我们对Nginx服务器各个模块的作用和联系有了比较清晰的认识。可以看到,Nginx服务器提供了异步的、非阻塞的Web服务,系统中的模块各司其职,彼此之间通常使用网络、管道和信号等机制进行通信,从而保持了松耦合的关系。工作进程中事件处理机制的使用,在很大程度上降低了在网络负载繁重的情况下Nginx服务器对内存、磁盘的压力,同时保证了对客户端请求的及时响应。
这里需要提及的一点是,当磁盘没有足够的性能处理大量IO调用时,工作进程仍然可能因为磁盘读写调用而阻塞,进而导致客户端请求超时失败等问题。目前可以通过多种方法来降低对磁盘IO的调用,比如引入异步输入/输出(Asynchronous Input/Output,AIO)机制等,但这些处理办法没有从根本上解决问题。为了尽量避免产生这种问题,大家在实际部署Nginx服务器时,应当对其运行环境有一个基本的了解,针对不同的网络负载环境选择相匹配的硬件环境,并对Nginx服务器进行合理的配置。