【.com原稿】Nginx(enginex)是一个高性能的HTTP和反向代理web服务器,同时也提供IMAP/POP3/SMTP服务。图片来自宝途网Nginx以其高性能、高可用而受到程序员的青睐。今天我们将从Nginx的整体架构入手,介绍Nginx的进程结构,进程之间的关系,以及如何控制和管理进程。今天你将学习以下内容:Nginx总体架构Nginx进程定义Nginx启动进程Master启动进程进程间信号发送方法进程辅助处理网络请求Nginx总体架构对于传统的HTTP和反向代理服务器来说,处理并发请求是很重要的使用单进程或线程模式处理,同时停止网络或I/O操作。这种方法会消耗大量内存和CPU资源。因为每产生一个单独的进程或线程,都需要准备一个新的运行环境,包括分配堆和栈内存,创建一个新的执行上下文。可以想象,在处理多个请求时会产生相应数量的线程或进程,导致线程在不断的上下文切换中消耗大量资源。基于以上原因,Nginx在设计之初就采用了模块化、事件驱动、异步处理、非阻塞的架构。图1:Nginx的整体架构让我们通过一张图来了解一下Nginx的概要架构,如图1所示:①Nginx启动时,并不立即处理网络请求,而是负责调度工作进程。包括Loadconfiguration(加载配置)、Launchworkers(启动worker进程)和Non-stopupgrade(平滑升级)。因此,Nginx启动后,会在操作系统中看到Master和Worker两种类型的进程。图中最上方的Master进程负责加载分析配置文件,启动/管理Worker进程,平滑升级。一个Master进程可以管理多个Worker进程,Worker进程负责处理和响应用户请求,即图中左侧的HTTP/HTTPS请求。②由于网络请求是IO请求,为了处理高并发,Nginx采用了kevent/epoll/select方式的复用技术。得益于这种技术,每个Worker进程可以同时处理数千个网络请求。③为了处理网络请求,Worker会包含模块,分为核心模块和功能模块。核心模块负责维护一个run-loop,执行网络请求处理不同阶段的模块功能,如网络读写、存储读写、内容传输、出口过滤、向上游服务器发送请求等。而围绕核心模块,还会有一些功能模块,实现具体的请求处理功能。比如有处理http请求的ht_core模块,负载均衡的ht_upstream模块,FastCGI的ht_fastcgi模块。这些模块将负责与后端服务器交互并完成用户请求。同时可以根据需要的功能自由加载模块,甚至可以扩展第三方模块。④Worker进程可以与本地磁盘通信,支持高级I/O(高级I/O)、sendfile机制、AIO机制、mmap等机制。通过上面的介绍,发现Nginx并不是为每个连接生成一个进程或者线程,而是通过Worker进程使用多路复用的方式来处理多个请求。这里使用一个共享的监听套接字来接受新的请求,并在每个Worker内部执行一个高效的runloop,这样每个Worker就可以处理上千个连接。Work启动后会创建一组监听socket,在HTTP请求和响应的处理过程中不断接受、读取和写入socket信息。run-loop包括全面的内部调用,严重依赖异步任务处理。支持通过模块化、事件通知、回调函数和定时器实现异步操作。它的目的是实现高并发请求下的非阻塞(尽可能不阻塞)。基于以上机制,Nginx检查网络和存储的状态并初始化新的连接,将它们加入到runloop中,异步处理直到完成。已处理的连接被重新分配并从运行循环中删除。因此Nginx可以在极端工作负载下实现更低的CPU使用率。另外,由于Work会对磁盘进行写操作,为了避免阻塞磁盘I/O请求,尤其是当磁盘已满时。可以设置机制和配置文件指令来减轻这种磁盘I/O阻塞情况。例如,使用sendfile和AIO组合选项来提高磁盘性能。Nginx进程定义从架构介绍中我们知道Nginx是由不同的进程组成的。这些进程各司其职,处理高并发下的网络请求。接下来,让我们看看它们的定义以及它们是如何工作的。上面介绍了Nginx的整体架构,其中重点介绍了Master进程和Worker进程。事实上,还有另外两个进程在架构中也扮演着重要的角色。图2:Nginx的四种进程下面我们通过图2来一起认识一下:Master进程:Nginx初始化时会作为父进程产生并启动,其他进程都是它的子进程,Master进程进程执行其他进程的创建和管理。Worker进程:是Master的子进程,负责处理网络请求。这里需要解释一下为什么Nginx采用多进程而不是多线程结构。原因是为了保证高可用,进程不像线程那样共享地址空间,也避免了当一个线程中的第三方模块出错而影响其他线程时出现的情况。CacheManager和CacheLoader进程:CacheLoader负责缓存加载,CacheManager负责缓存管理,每个请求使用的缓存由Worker决定,进程间通信通过共享内存实现。从上图中大家一定注意到了,只有多个Worker进程,因为Nginx采用的是事件驱动模型。为了提高处理请求的效率,每个Worker进程都会寻找一个CPU核,提高CPU缓存命中率,将一个Worker进程绑定到一个CPU核上。需要注意的是,我们需要根据具体的应用场景来定义worker进程的数量:CPU密集型请求,比如处理大量的TCP/IP,进行SSL或者压缩等,需要worker的数量与数量相匹配的CPU核心。对于IO密集型请求,worker数量需要是CPU核数的一到两倍。上面说到Master通过控制多个Worker进程来处理网络请求。对于独立的Worker进程,在使用资源时,无需考虑不需要加锁的问题,节省了加锁带来的系统开销。同时多进程设计,防止进程相互干扰。当一个进程退出时,其他进程仍在工作。Nginx提供的网络请求服务不会因为其中一个进程的退出而中断。一旦Master进程发现有一个Worker进程退出,就会启动一个新的Worker进程。这里我们会发现Master进程为了控制它需要和Worker通信,Worker进程也需要和Master进程交换信息。Nginx启动过程中讲到了Master进程的重要性,那么我们来看看Nginx进程的启动过程。图3:Nginx启动Master进程如图3所示,Nginx启动时会根据配置文件进行解析和初始化,同时会从主进程中fork出一个Master进程作为自己的子进程,即启动Master进程,此时Master进程诞生。forkMaster进程后,Nginx的主进程退出。然后Master进程会fork并启动Worker进程,以及CacheManager和CacheLoader进程,然后Master进程就会进入主循环。需要注意的是,这里使用的fork会复制一个与当前启动进程具有相同代码段、数据段、堆和栈、fd等信息的子进程。也就是说,我们说的这四类进程都是通过Nginx启动进程复制过来的子进程。Master启动流程遵循上述流程。Master进程fork之后,会执行ngx_master_process_cycle函数。图4:Master进程执行ngx_master_process_cycle函数如图4所示,该函数主要执行以下操作:设置进程的初始信号掩码,阻塞相关信号。Master进程派生出Worker、CacheManager和CacheLoader等子进程。进入主循环,通过sigsuspend系统调用,等待信号的到来。一旦信号到达,就会进入信号处理器ngx_signal_handler。信号处理程序执行完毕后,程序执行流程会判断各个状态位来执行不同的操作。上面的流程中提到了几个概念,这里对它们进行说明,以便我们更好的理解Master进程的执行过程。①信号:过程中用来完成信息传递的媒介。在Master进程的主循环中,通过等待各种信号事件来处理不同的指令。这个信号可以传递给Master进程,也可以从Master进程传递给其他进程。信号分为标准信号和实时信号,标准信号从1-31,实时信号从32-64。例如:INT、QUIT、KILL是标准信号。Master进程监听的信号也是标准信号。标准信号和实时信号的区别在于,标准信号是一种基于比特的标记。假设在阻塞和等待期间有多个相同的信号到达,那么信号只会在最终解阻塞时发送一次,而等待期间的信号计数是无法统计的。.实时信号是通过队列实现的。阻塞等待时,队列中会存储多个相同的实时信号。一旦解除阻塞,队列中的所有信号都会被传递,结果会收到多个信号。②信号处理器:信号处理器是指捕获到指定信号(传递给进程)时将调用的函数。它存在于进程中,可以随时打断进程的主程序流程。③发送信号:发送信号的操作可以用shell命令kill来完成。比如kill-9pid就是发送一个KILL信号。kill-INTpid就是发送INT信号。与shell命令类似,kill系统调用可用于向进程发送信号。④信号屏蔽:用于控制信号屏蔽的编码方式。每个进程都有一个信号掩码(signalmask),也称为信号掩码字,它指定了当前要屏蔽或阻止传递给进程的一组信号。对于每个可能的信号,此掩码中有一位。对于某个信号,如果其对应的位(bit)被置位,则当前被阻塞。简单地说,信号掩码是一个“位图”,其中每个位对应一个信号。如果位图中的某个位为1,则表示相应信号在当前信号集的处理程序执行期间被暂时“屏蔽”或“阻塞”,这样执行期间就不会出现对该信号的嵌套响应。说白了,就是用信号编码来阻塞信号,告诉其他发送信号的进程:“我正在忙着处理事情,你先等一下,我稍后再处理你的信号”。进程间的信号传递方式如下:上面的基础建立好之后,我们再回头看看Nginx进程中是如何进行信息交互的,Master是如何通过信号与Worker通信的。图5:Master和Worker通信从图5可以看出Master可以接受TERM、INT、QUIT、HUP、USR1、USR2、WINCH等信号。这些信号的含义将在后面的表格中解释。同时Master进程也可以向Worker进程发送信号,因此Worker进程可以接收到以下信号:TERM、INT、QUIT、HUP、USR1、WINCH。之后,Worker响应Client的请求。这里将信号对应的命令和含义列在一个表中,然后进行解释。从上表可以看出,每个信号都有特定的含义。例如QUIT信号表示优雅关闭服务,对应quit命令。这里的quit命令是指可以通过命令行向Master进程发出命令,达到发送信号的效果。当然Master收到命令后,会转换成信号发送给对应的Worker,从而达到关闭服务的效果。需要注意的是,Worker不接受命令,而是通过Worker接受命令来统一管理所有Worker的行为。该进程协助处理网络请求。了解了Master和Worker是如何通信的,我们再看看他们是如何配合完成客户端请求的。图6:客户端请求流程如图6所示,描述了Master创建Listen和forkWorker的过程,以及客户端请求和Worker响应请求的过程。①从上开始,按照红色箭头从上到下。Master进程创建完成后,会通过socket方法创建socketIO通道。然后执行bind方法将其绑定到监听器listener上,再使用fork方法fork出多个Worker进程(绿色虚线)。②每个Worker进程中的accept方法监听socket请求。一旦listen监听到socket请求,Worker进程就可以通过accept接收。③查看底部的客户端模块。当客户端通过connect方法连接到Nginx时,所有使用accept方法的Worker进程都会收到listen的通知,但是只有一个Worker进程能够成功accept,其他进程都会失败。这里Nginx提供了一个共享锁accept_mutex来保证同一时间只有一个Worker进程在接受连接,从而解决雷群问题。④当Worker进程接受到socket请求后,client会通过send方法将请求(绿色虚线)发送给Worker。Worker使用recv方法接受请求,同时通过parse(分析)、process(处理)、generate(生成响应)等步骤,将返回的response通过send方法发送给客户端,客户端将使用recv方法接受响应。最后Worker调用close方法与客户端断开连接。小结本文从Nginx的整体架构入手,介绍了Nginx的主要组件和处理流程。然后介绍Nginx的4个进程,以及这些进程在Nginx启动过程中是如何产生的。然后关注最重要的Master进程在启动过程中做了哪些具体的事情,特别是Master进程与Worker、CacheManager、CacheLoad的关系。在进程间发送信号方式一章中,我们建立了信号、发送信号、信号处理、信号掩码等概念,有助于理解进程间的通信。最后趁热打铁解释一下Nginx接受网络请求的过程,以及进程是如何协同处理请求的。作者:崔浩简介:十六年开发架构经验。曾在惠普武汉交付中心担任技术专家、需求分析师、项目经理,后在一家初创公司担任技术/产品经理。善于学习,乐于分享。目前专注于技术架构和研发管理。编辑:陶佳龙征稿:如有意向投稿或寻求报道,请联系editor@51cto.com【原创稿件请注明原作者和出处为.com,合作网站转载】
