[51CTO.com原创稿] Nginx(engine x)是一个高性能的HTTP和反向代理Web服务器,它还提供IMAP/POP3/SMTP服务。

图片来自趵突山。com
Nginx以其高性能和高可用性受到程序员的青睐。今天我们就从Nginx的整体架构入手,介绍Nginx的进程结构,进程之间的关系以及如何控制和管理进程。
今天,您将学习以下内容:
对于传统的HTTP和反向代理服务器,在处理并发请求时,会使用单进程或线程处理的模式,同时停止网络或输入/输出操作。
这种方式会消耗大量的内存和CPU资源。因为每生成一个单独的进程或线程,都需要准备一个新的运行时环境,包括分配堆和栈内存,创建新的执行上下文。
可以想象,处理多个请求时会产生相应数量的线程或进程,导致线程在不断的上下文切换中消耗大量资源。
基于以上原因,Nginx在设计之初就采用了模块化、事件驱动、异步处理和非阻塞的架构。
图1:1:Nginx的整体架构
我们来上图了解一下Nginx的概要架构,如图1所示:
①NGINX启动时,不会立即处理网络请求,负责调度工作进程。包括负载配置、启动工作程序和不间断升级。
所以Nginx启动后,在操作系统中会看到Master和Worker两种进程。图顶部的主进程负责加载分析配置文件、启动/管理工作进程以及平滑升级。
一个主进程可以管理多个工作进程,工作进程负责处理和响应用户请求,也就是图左边的HTTP/HTTPS请求。
②由于网络请求属于IO请求,为了处理高并发的Nginx,采用了kevent/epoll/ select模式的复用技术。由于这项技术,每个工作进程可以同时处理数千个网络请求。
③为了处理网络请求,Worker会包含模块,分为核心模块和功能模块。
核心模块负责维护运行循环,并在网络请求处理的不同阶段执行模块功能,如网络读写、存储读写、内容传输、出站过滤、向上游服务器发送请求等。
围绕核心模块,会有一些功能模块,用来实现具体的请求处理功能。比如有处理http请求的ht_core模块,实现负载均衡的ht_upstream模块,实现FastCGI的HT _ FastCGI模块。
这些模块将负责与后端服务器进行交互,以满足用户请求。同时,他们可以根据所需功能自由加载模块,甚至扩展第三方模块。
④Worker进程可以与本地磁盘通信,支持高级I/O、sendfile机制、AIO机制、mmap机制等。
通过上面的介绍,发现Nginx不会为每个连接生成一个进程或线程,而是通过Worker进程复用的方式处理多个请求。
在这里,我们将通过共享侦听套接字来接受新的请求,并在每个工作器中执行一个高效的运行周期,以便每个工作器可以处理数千个连接。
工作开始后,它会创建一组监听套接字,在处理HTTP请求和响应的过程中不断接受、读写套接字信息。
运行循环包括全面的内部调用,并严重依赖异步任务处理。
模块化、事件通知、回调函数和定时器支持异步操作的实现。目的是在高并发请求下实现无阻塞(尽量不阻塞竞争)。
基于上述机制,Nginx检查网络和存储的状态并初始化新的连接,将它们添加到运行周期中,并异步处理它们,直到它们完成。已处理的连接将被重新分配并从运行周期中删除。因此,Nginx可以在极端工作负载下实现低CPU利用率。
另外,因为工作会在磁盘上写,为了避免阻塞磁盘I/O上的请求,特别是当磁盘已满时。
可以设置机制和配置文件指令来减少这种磁盘I/O阻塞的发生。例如,使用发送文件和AIO组合选项来提高磁盘性能。
Nginx 进程定义从架构的介绍中,我们知道Nginx由不同的进程组成,每个进程负责处理高并发下的网络请求。接下来,让我们看看它们的定义和工作原理。
上面介绍了Nginx的总体架构,其中重点介绍了主进程和工作进程。事实上,还有另外两个过程也在架构中扮演着重要的角色。
图2:2:Nginx的四种流程
在这里,我们通过图2一起了解它们:
从上图中,你一定注意到了只有一些Worker进程,因为Nginx采用了事件驱动的模型。
为了提高处理请求的效率,每个工作进程都会找一个CPU核,提高CPU的缓存命中率,将一个工作进程与一个CPU核绑定。
需要注意的是,我们需要根据具体的应用场景来定义工作进程的数量:
如上所述,Master通过控制多个Worker进程来处理网络请求。对于Worker的独立进程,在使用资源时不需要考虑锁的问题,省去了锁带来的系统开销。同时,多流程设计使得流程之间互不影响。
当一个进程退出时,其他进程还在工作,Nginx提供的网络请求服务不会因为其中一个进程的退出而中断。一旦主进程发现一个工作进程存在,它将启动一个新的工作进程。
这里我们会发现,主进程需要与工作进程进行通信,以便对其进行控制,工作进程也需要与主进程交换信息。
Nginx 启动过程说到Master进程的重要性,我们先来看看Nginx进程的启动过程。
图3: Nginx启动主进程
如图3所示,Nginx启动时会根据配置文件进行分析和初始化,同时会从主进程中分叉出一个主进程作为自己的子进程,也就是启动主进程,然后主进程就诞生了。
Nginx的主进程在fork离开主进程后退出。然后主进程将分叉并启动工作进程,以及缓存管理器和缓存加载器进程,然后主进程将进入主循环。
需要注意的是,这里使用的fork会复制一个与当前启动进程具有相同代码段、数据段、堆和栈、fd等信息的子进程。
也就是说,我们所说的四类进程,都是从Nginx启动进程复制过来的子进程。
Master 启动过程接下来,在上述流程主流程被fork后,将执行ngx_master_process_cycle函数。
图4:主进程执行ngx_master_process_cycle函数
如图4所示,该函数主要执行以下操作:
上面的流程中提到了几个概念,这里进行解释,以便我们更好的理解主流程的执行过程。
①信号:过程中用来完成信息传递的介质。在主进程的主循环中,通过等待各种信号事件来处理不同的指令。
该信号可以传递给主进程,或者从主进程传递给其他进程。
信号分为标准信号和实时信号,标准信号范围为1-31,实时信号范围为32-64。例如,INT、QUIT和KILL是标准信号。主进程监控的信号也是标准信号。
标准信号和实时信号的区别在于,标准信号是基于比特的标记。假设在拥塞等待时,有几个相同的信号到达,当拥塞最终解除时,信号只会传输一次,因此等待期间的信号计数无法计算。
实时信号是通过队列实现的,在等待拥塞的时候会有很多相同的实时信号存储在队列中。一旦拥塞解除,队列中的所有信号都将被传递,结果是接收到许多信号。
②信号处理器:信号处理器是指当捕获到指定的信号(传递给进程)时将被调用的函数。它存在于进程中,可以随时中断进程的主程序流。
③发送信号:发送信号的操作可以使用shell命令kill来完成。例如,KILL -9 pid表示发送KILL信号。
Kill -INT pid是发送INT信号。与shell命令类似,您可以使用kill系统调用来通知进程。
④信号屏蔽:用于控制信号阻断的编码方式。每个进程都有一个信号掩码,也称为信号掩码字,它指定要屏蔽或阻止传递给进程的信号集。
对于每一个可能的信号,这个掩码中都有一个比特。对于某个信号,如果其对应的位被置位,则该信号当前被阻止。
简单地说,信号掩码是一个“位图”,其中每个位对应一个信号。如果位图中的某一位为1,则表示在当前信号集的加工程序执行过程中,相应的信号被暂时“屏蔽”或“阻断”,因此在执行过程中不会以嵌套的方式响应该信号。
说白了,就是用信号编码来屏蔽比赛信号,告诉其他发送信号的进程说“我在忙事情,你先等着,我一会处理你的信号。”
进程之间的信号发送方式有了上面的基础,我们再回头看看Nginx的过程中信息是如何交换的,主是如何通过信号与工进行交流的。
图5:5:主人和工人之间的通信
从图5可以看出,Master可以接受TERM、INT、QUIT、HUP、USR1、USR2和WINCH等信号,这些信号的含义将在后面的表格中解释。
同时,主进程还可以向工作进程发送信号,因此工作进程可以接收到以下信号:TERM、INT、QUIT、HUP、USR1、WINCH。然后工人响应客户的请求。
这里,信号的相应命令和含义被列在一个表格中,然后对它们进行解释。
从上表中可以看出,每个信号都有特定的含义。例如,退出信号表示优雅地关闭服务,并对应于退出命令。
这里的quit命令是指可以通过命令行命令主进程,从而达到发送信号的效果。当然,主控收到命令后,会转换成信号发送给相应的工作器,达到关闭服务的效果。
需要注意的是,工作者不会接受命令,但是会通过接受命令来管理所有工作者的行为。
进程协助处理网络请求了解了Master和Worker之间的通信方式之后,我们来看看它们是如何合作完成客户端请求的。
图6:客户端请求过程
如图6所示,这里描述了Master创建listen和fork离开Worker的过程,以及client请求和Worker响应请求的过程。
①从顶部开始,顺着红色箭头从上到下走。创建主进程后,套接字的IO通道将由socket方法创建。
然后,执行bind方法将其绑定到监听器listen,然后通过fork方法显示多个工作进程(绿色虚线)。
②每个工作进程中的accept方法监听套接字请求。一旦listen侦听到套接字请求,工作进程就可以通过accept接受它。
③看底部的客户端模块。当客户端通过connect方法连接Nginx时,所有使用accept方法的工作进程都会收到listen的通知,但是只有一个工作进程可以成功接受,其他进程都会失败。
这里Nginx提供了一个共享锁accept_mutex,保证在accept中同时只有一个工作进程连接,以解决死机问题。
④当Worker进程接受套接字请求时,客户端会通过send方法将请求(绿色虚线)发送给Worker。
而Worker使用recv方法接受请求,并通过send方法经过解析、处理和生成步骤将返回的响应发送给客户机,而客户机将使用recv方法接受响应。
最后,Worker调用close方法断开与客户端的连接。
总结本文从Nginx的整体架构出发,介绍了Nginx的主要组件和处理流程。然后,介绍了Nginx的四个进程,以及这些进程在Nginx启动过程中是如何产生的。
然后重点介绍了主进程启动过程中做了哪些具体的事情,尤其是主进程与Worker、缓存管理器、缓存负载之间的关系。
在进程间信号传输方式一章中,我们建立了信号、信号传输、信号处理、信号屏蔽等概念,有助于理解进程间的通信。
最后,趁热打铁,解释Nginx接受网络请求的过程以及进程如何协作处理请求。
作者:崔豪
简介:十六年开发和架构经验,曾担任惠普武汉交付中心技术专家、需求分析师、项目经理,后在创业公司担任技术/产品经理。善于学习,乐于分享。目前,他专注于技术架构和研发管理。
编辑:陶佳龙








