4 Nginx 服务器架构初探

felix.shao2025-02-18

4 Nginx 服务器架构初探

TIP

 本小节,主要学习以下主要内容:

  • 模块化结构的相关知识;
  • Nginx 如何处理 Web 请求;
  • Nginx 的事件驱动模型;
  • Nginx 设计架构的概览。

模块化结构

 模块化结构理论略。

Nginx 模块化结构

 习惯上将 Nginx 涉及到的模块分为核心模块、标准 HTTP 模块、可选 HTTP 模块、邮件服务模块以及第三方模块等五大类。
 核心模块和标准 HTTP 模块在 Nginx 快速编译后就包含在 Nginx 中。在 Linux 系统中,将工作目录定位到 /nginx/objs 目录,objs 目录中包含了这些内容:

[root@VM-0-17-centos objs]# clear
[root@VM-0-17-centos objs]# pwd
/usr/local/nginx/objs
[root@VM-0-17-centos objs]# ls 
autoconf.err  Makefile  nginx  nginx.8  ngx_auto_config.h  
ngx_auto_headers.h  ngx_modules.c  ngx_modules.o  src

 在此目录中的 ngx_modules.c 文件中包含了此版本 Nginx 快速编译后包括的所有固有模块的声明。这些模块声明以 extern 关键字修饰:

[root@VM-0-17-centos objs]# cat ngx_modules.c|grep extern |cat -n
     1	extern ngx_module_t  ngx_core_module;
     2	extern ngx_module_t  ngx_errlog_module;
     3	extern ngx_module_t  ngx_conf_module;
     4	extern ngx_module_t  ngx_regex_module;
     5	extern ngx_module_t  ngx_events_module;
     6	extern ngx_module_t  ngx_event_core_module;
     7	extern ngx_module_t  ngx_epoll_module;
     8	extern ngx_module_t  ngx_http_module;
     9	extern ngx_module_t  ngx_http_core_module;
    10	extern ngx_module_t  ngx_http_log_module;

 由于使用了 extern 关键字修复,因此各模块均可以被其他模块访问。

TIP

 Nginx 中模块的命名习惯: 一般以 ngx_ 作为前缀,_module 作为后缀,中间使用一个或者多个英文单词描述模块的功能。

 所有固有模块的源码放在编译目录下的 src 目录中。在 src 目录中,我们看到一共分成 core、event、http、mail、misc 和 os 等 6 个目录。从这里看到,源码中包含了邮件服务的模块,但在快速编译时默认不将其编译到 nginx 中。

核心模块

 核心模块主要包含主体功能、响应请求事件必需的两类功能,如上"nginx 模块化结构"中 cat 打印的前 7 行模块:

  • 主体功能:进程管理、权限控制、错误日志记录、配置解析等
  • 响应请求事件必需的功能:事件驱动机制、正则表达式解析

标准 HTTP 模块

 这一模块主要对应于基本 HTTP 服务。这些模块在默认情况下会被编译到 Nginx 中的,除非在配置时添加 --without-XXX 参数声明不编译。具体常用标准 HTTP 模块列表略。

可选 HTTP 模块

 可选 HTTP 模块在目前的 Nginx 发行版本中只提供源码,但在快速编译时默认不编译。如果想使用相关模块,就必须在配置时使用 --with-XXX 参数生命。常用可选 HTTP 模块列表略。

邮件服务模块

 略,快读编译时默认并不会编译邮件服务模块。

第三方模块

 略。

Nginx 服务器的 Web 请求处理机制

三种并行处理请求工作方式

 完成并行处理请求工作有三种方式可供选择:多进程方式、多线程方式和异步方式。
 三种方式的说明,优缺点等略,可参考[Nginx高性能Web服务器详解-54页]。

TIP

 注意理解下同步(描述发送端)与阻塞(描述客户端)的区别,以及同步阻塞、同步非阻塞、异步阻塞、异步非阻塞四个概念。

Nginx 服务器如何处理请求

 Nginx 服务器的一个显著优势是能够同时处理大量并发请求。它结合多进程机制和异步机制对外提供服务。异步机制使用的是异步非阻塞方式。
 Nginx 服务器启动后,可以产生一个主进程和多个工作进程。所有工作进程都用于接受和处理客户端的请求。类似于 Apache 使用的改进的多进程机制,预先生成多个工作进程,等待处理客户端请求。

TIP

 实际上,Nginx 服务器的进程模型有两种:Single 模型和 Master-Worker 模型。Single 模型为单进程方式,性能较差,一般在实际工作中不使用。Master-Worker 模型实际上被广泛地成为 Master-Slave 模型。

 每个工作进程使用了异步非阻塞方式,可以处理多个客户端请求。
 客户端请求数量增长、网络复杂繁重时,Nginx 服务器使用多进程机制能够保证不增长对系统资源的压力;同时使用异步非阻塞方式减少了工作进程在 I/O 调用上的阻塞延迟,保证了不降低对请求的处理能力。

Nginx 服务器的事件处理机制

 Nginx 服务器的工作进程调用 IO 后,就去进行其他的工作了;当 IO 调用返回后,会通知工作进程。这里有一个问题,IO 调用是如何把自己的状态通知给工作进程的呢?
 一般解决这个问题的方案有两种。

  1. 让工作进程在进行其他工作的过程中间隔一段时间就去检查以下 IO 的运行状态给,如果完成,就去响应客户端,如果未完成,就继续正在进行的工作。
  2. IO 调用在完成后能主动通知工作进程。对于前者,虽然工作进程在 IO 调用过程中没有等待,但不断的检查仍然在时间和资源上导致了不小的开销,这是最理想的解决方案。

 具体来说,select/poll/epoll/kqueue 等着用的系统调用就是用来支持第二种解决方案的。这些系统调用,也常被称为事件驱动模型,它们提供了一种机制,让进程可以同时处理多个并发请求,不用关心 IO 调用的具体状态。IO 调用完全由事件驱动模型来管理,事件准备好之后就通知工作进程事件已经就绪。

Nginx 服务器的事件驱动模型

 事件驱动模型是 Nginx 服务器保障完整功能和具有良好性能的重要机制之一。

事件驱动模型概述

 事件驱动模型一般是由事件收集器、事件发送器和事件处理器三部分基本单元组成。

  • 事件收集器:专门负责收集所有的事件。
  • 事件发送器:复杂将收集器收集到的事件分发到目标对象中。
  • 事件处理器:负责具体事件的响应工作,它往往要到实现阶段才能完全确定。

Nginx 的事件驱动模型

 Nginx 服务器响应和处理 Web 请求的过程,就是基于事件驱动模型的,它也包含事件收集器、事件发送器和事件处理器等三部分单元。它的事件收集器和事件发送器的实现没有太大的特点,我们重点了解一下它的事件处理器。
 通常,我们在编写服务器处理模型的程序时,基于事件驱动模型,“目标对象”中的“事件处理器”可以由以下几种实现办法:

  • “事件发送器”每传递过来一个请求,“目标对象”就创建一个新的进程,调用“事件处理器”来处理该请求。
  • “事件发送器”每传递过来一个请求,“目标对象”就创建一个新的线程,调用“事件处理器”来处理该请求。
  • “事件发送器”每传递过来一个请求,“目标对象”就将其放入一个待处理事件的列表,使用非阻塞 I/O 方式调用“事件处理器”来处理该请求。

 上面的三种处理方式,各有特点,第一种方式,由于创建新的进程的开销比较大,会导致服务器性能比较差,但其实现相对来说比较简单;第二种方式,由于要涉及到线程的同步,故可能会面临死锁、同步等一系列问题,编码比较复杂;第三种方式,在编写程序代码时,逻辑比前面两种都复杂。大多数网络服务器采用了第三种方式,逐渐形成了所谓的“事件驱动处理库”。
 事件驱动处理库又被称为多路 IO 复用方法。最常见的包括以下三种:select 模型、poll 模型和 epoll 模型。nginx 服务器还支持 stsig 模型、kqueue 模型、dev/poll 模型和 eventport 模型等。通过 Nginx 配置可以使得 Nginx 服务器支持这几种事件驱动处理模型。

select 库

 select 库,是各个版本的 Linux 和 Windows 平台都支持的基本事件驱动模型库,并且在接口的定义上也基本相同,知识部分参数的含义略有差异。
 使用 select 库的方法和 select 轮询等具体流程略。
 Nginx 服务器在比那一过程中如果没有为其指定其他高性能事件驱动模型库,它将自动编译该库。我们可以使用 --with-select_module 和 --without-select-module 两个参数强制 Nginx 是否编译该库。

poll 库

 poll 库,作为 Linux 平台上的基本事件驱动模型,是在 Linux2.1.23 中引入的,Windows 平台不支持 poll 库。
 poll 于 select 的基本供桌方式是相同的,都是先创建一个关注事件的描述符集合,再去等待这些事件发送,然后再轮询描述符集合,检查有没有事件发送,如果由,就进行处理。
 poll 库与 select 库的主要区别在于,select 库需要为读事件、写事件和异常事件分别创建一个描述符集合,因此在最后轮询的时候,需要分别轮询这三个集合。而 poll 库只需要创建一个集合,在每个描述符对应的结构上分别设置读事件、写事件或者异常处理事件,最后轮询的时候,可以同时检查这三种事件是否发生。可以说,poll 库是 select 库的优化实现。

epoll 库

 epoll 库是 Nginx 服务器支持的高性能事件驱动库之一,它是公认的非常优秀的事件驱动模型,和 poll 库及 select 库由很大的不同,epoll 术语 poll 库的一个变种,是在 Linux2.5.44 中引入的,在 Linux2.6 及以上的版本都可以使用它。poll 库和 select 库在实际工作中,最大的区别在于效率。
 在前面的介绍我们知道,他们的处理方式都是创建一个待处理事件列表,然后把这个列表发给内核,返回的时候,再去轮询检查这个列表,以判断事件是否发生。这样在描述符比较多的应用中,效率就显得比较低下了。一种比较好的做法是,把描述符列表的管理交由内核负责,一旦由某种事件发生,内核把事件的描述符列表通知给进程,这样就避免了轮询整个描述符列表。epoll 库就是这样一种模型。
 首先,epoll 库通过相关调用通知内核创建一个有N个描述符的事件列表;然后,给这些描述符设置所关注的事件,并把它添加到内核的事件列表中去,在具体的编码过程中也可以通过相关调用对事件列表中的描述符进行修改和删除。
 完成设置之后,epoll 库就开始等待内核通知事件发生了。某一事件发生后,内核将发生事件的描述符上报给 epoll 库。得到事件列表的 epoll 库,就可以进行事件处理了。
 epoll 库在 Linux 平台上是高效的。它支持一个进程打开大数目的事件描述符,上限是系统可以打开文件的最大数目;同时,epoll 库的 IO 效率不随描述符数目增加而线性下降,因为它只会对内核上报的“活跃”的描述符进行操作。

rtsig 模型

 rtsig 是 Real-Time Signal 的缩写,是实时信号的意思。严格意义上说,stsig 模型并不是常用的事件驱动模型,但 Nginx 服务器提供了使用实时信号对事件进行响应的支持,官方文档中将 rtsig 模型与其他事件驱动模型并列。

其他事件驱动模型

 除了以上四种主要的事件驱动模型,Nginx 服务器针对特定的 Linux 平台提供了响应的事件驱动模型支持。目前实现的主要有 kqueue 模型、/dev/poll 模型、eventport 模型等。

  • kqueue 模型,是用于支持 BSD 系列平台的高效事件驱动模型。该模型也是 poll 模型的一个变种,其和 epoll 库的处理方式没有本质上的区别,都是通过避免轮询操作提供效率。
  • /dev/poll 模型,是用于支持 Unix 衍生平台的高效事件驱动模型。该模型是 Sun 公司在开发 Solaris 系列平台时提出的用于完成事件驱动机制的方案,它使用了虚拟的 /dev/poll 设备,开发人员可以将要监视的文件描述符加入这个设备,然后通过 ioctl() 调用来获取事件通知。
  • eventport 模型,是用于支持 Solaris10 及以上版本平台的高效事件驱动模型。该模型也是 Sun 公司在开发 Solaris 系列平台时提出的用于完成事件驱动机制的方案,它可以有效防止内核崩溃等情况的发生,Nginx 服务器为此提供了支持。

 以上就是 Nginx 服务器支持的事件驱动库。可以看到,Nginx 服务器针对不同的 Linux 或 Unix 衍生平台提供了多种事件驱动模型的处理,尽量发挥系统平台本身的优势,最大程度地提高处理客户端请求事件的能力。在实际工作中,我们需要根据具体情况和应用情景选择使用不同 ing 的事件驱动模型,以保证 Nginx 服务器的高效运行。

设计架构概览

TIP

 探讨 Nginx 的“架构之美”

Nginx 服务器架构

 Nginx 服务器启动后,产生一个主进程,主进程执行一系列工作后产生一个或者多个工作进程。主进程主要进行 Nginx 配置文件解析、数据结构初始化、模块配置和注册、信号处理、网络监听生成、工作进程生成和管理等工作;工作进程主要进行进程初始化、模块调用和请求处理等工作,是 Nginx 服务器提供服务的主体。
 在客户端请求动态站点的过程中,nginx 服务器还涉及和后端服务器的通信。nginx 服务器将接受到的 Web 请求通过代理转发到后端服务器,由后端服务器进行数据处理和页面组织,然后将结果返回。
 另外,Nginx 服务器为了提高对请求的响应效率,进一步降低网络压力,采用了缓存机制,将历史应答数据缓存到本地。在每次 Nginx 服务器启动后的一段时间内,会启动专门的进程对本地缓存的内容重建索引,保证对缓存文件的快速访问。
 根据上面的分析,我们可以将 Nginx 服务器的结构大致分为主进程、工作进程、后端服务器和缓存等部分。架构示意图如下open in new window
nginx_server_architecture.png

Nginx 服务器的进程

 Nginx 服务器的三大类进程:一类是主进程,另一类是由主进程生成的工作进程,还有用于为缓存文件建立索引的进程

主进程(Master Process)

 Nginx 服务器启动时运行的主要进程。它的主要功能是与外界通信和对内部其他进程进行管理,具体来说有以下几点:

  • 读取 Nginx 配置文件并验证其有效性和正确性
  • 建立、绑定和关闭 Socket
  • 按照配置生成、管理和结束工作进程
  • 接收外界指令,比如重启、升级、退出服务器等指令
  • 不中断服务,实现平滑重启,应用新配置
  • 不中断服务,实现平滑升级,升级失败进行回滚处理
  • 开启日志文件,获取文件描述符
  • 编译和处理 Perl 脚本

工作进程(Worker Process)

 由主进程生成,生成数量可以通过 Nginx 配置文件指定,正常情况下生存于主进程的整个生命周期。该进程的主要工作有以下几项:

  • 接收客户端请求;
  • 将请求依次送入各个功能模块进行过滤处理;
  • IO 调用,获取响应数据;
  • 与后端服务器通信,接收后端服务器处理结果;
  • 数据缓存,访问缓存索引、查询和调用缓存数据;
  • 发送请求结果,响应客户端请求;
  • 接收主程序指令,比如重启、升级和退出等指令。 ...

缓存索引重建及管理进程(Cache Loader & Cache Manager)

 Cache 模块,主要由缓存索引重建(Cache Loader)和缓存索引管理(Cache Manager)两类进程完成工作。缓存索引重建进程实在 Nginx 服务启动一段事件之后(默认是 1 分组)由主进程生成,在缓存元数据重建完成后就自动退出;缓存索引管理进程一般存在于主进程的整个生命周期,负责对缓存索引进行管理。
 缓存索引重建进程完成的主要工作是,根据本地磁盘上的缓存文件在内存中建立索引元数据库。该进程启动后,对本地磁盘上存放缓存文件的目录结构进行扫描,检查内存中已有的缓存元数据是否正确,并更新索引源数据库。
 缓存索引管理进程主要负责在索引元数据更新完成后,对元数据是否过期做出判断。
 这两个进程维护的内存索引源数据库,为工作进程对缓存数据的快速查询提供了便利。

TIP

 问题: 缓存索引重建进程是否只是在启动时运行一次,还是定时运行,另外文件更新后,是否过期是怎么触发的,延时多久触发。

进程交互

 Nginx 服务器在使用 Master-Worker 模型时,会涉及主进程与工作进程(Master-Worker)之间的交互和工作进程(Worker-Worker)之间的交互。这两类交互都依赖于管道(channel)机制,交互的准备工作都是在工作进程生成时完成的。

Master-Worker 交互

 工作进程是由主进程生成的。Nginx 服务器启动以后,主进程根据配置文件决定生成的工作进程的数量,然后建立一张全局的工作进程表用于存放当前未退出的所有工作进程。
 在主进程生成工作进程后,将新生成的工作进程加入到工作进程表中,并建立一个单向管道并将其传递给该工作进程。该管道与普通的管道不同,它是由主进程指向工作进程的单向管道,包含了主进程向工作进程发出的指令、工作进程 ID、工作进程在工作进程表中的索引和必要的文件描述符等信息。
 主进程与外界通过信号机制进行通信,当接收到需要处理的信号时,它通过管道向相关的工作进程发送正确的指令。每个工作进程都有能力捕获管道中可读事件,当管道中由可读事件时,工作进程从管道读取并解析指令,然后采取相应的措施。这样就完成了 Master-Worker 的交互。

TIP

 问题:主进程的管道指令,会被多个进程同时处理吗?

Worker-Worker 交互

 Worker-Worker 交互在实现原理上和 Master-Worker 交互是一样的。只要工作进程之间能够得到彼此的信息,建立管道,即可通信。由于工作进程之间是相互隔离的,因此一个进程要想知道另外一个进程的信息,只能通过主进程来设置了。
 为了达到工作进程之间交互的目的,主进程在生成工作进程后,在工作进程表中进行遍历,将该新进程的ID以及针对该进程建立的管道句柄传递给工作进程表中的其他进程,为工作进程之间的交互做准备。每个工作进程捕获管道中可读事件,根据指令采取相应的措施。
 当工作进程 W1 需要向 W2 发送指令时,首先在主进程给它的其他工作进程信息中找到 W2 的进程 ID,然后将正确的指令写入指向 W2 的通道。工作进程 W2 捕获到管道中的事件后,解析指令并采取相应措施。就有就完成了 Worker-Worker 交互

Run Loops 事件处理循环模型

 Run Loops,指的是进程内部用来不停的调配工作,对事件进行循环处理的一种模型。它属于进程或者线程的基础架构部分。该模型对事件的处理不是自动的,需要在涉及代码过程中,在适当的时候启动 Run-Loop 机制对输入的事件做出响应。
 这里只是大概了解下,具体还需在源码小节中展开探讨。

参考文献

  • [Nginx 高性能 Web 服务器详解]
Last Updated 2/18/2025, 5:05:12 PM