掘金 后端 ( ) • 2024-04-21 17:54

在Nginx的配置文件nginx.conf中,events代码块是一个重要的部分,用于定义与服务器事件处理模型相关的参数和指令。这些设置直接影响到Nginx如何高效地监听并响应客户端连接请求,管理并发连接,以及如何利用操作系统提供的I/O多路复用机制来处理网络事件。

一文了解ngnix基础: https://juejin.cn/post/7357698682743832615

image.png

1. 相关参数

events {
    # 事件驱动模型
    use [method];

    # 单个工作进程的最大连接数
    worker_connections [number];

    # 是否开启accept_mutex锁,控制多个工作进程间的惊群效应
    accept_mutex on|off;

    # 设置accept_mutex_delay延迟时间(仅当accept_mutex启用时有效)
    accept_mutex_delay [time];

    # 仅适用于Linux,控制epoll使用的红黑树或链表
    use epoll_rdhup;

    # 其他特定于平台或第三方模块的事件相关配置
}
  • use [method]:指定了Nginx应使用的事件处理模型。常见的方法:poll 、 select 与 epoll;
  • worker_connections [number]:定义了每个工作进程能够同时处理的最大连接数。这个值应当根据系统的资源(如内存)和预期的并发连接数量进行合理设置。
  • accept_mutex on|off:在多工作进程模式下,当一个新的客户端连接到达时,所有工作进程都会尝试接收(accept)这个连接。如果启用accept_mutex,则只有一个工作进程能够成功接收,从而避免“惊群效应”(所有进程被唤醒但只有一个能处理连接的效率问题)。默认情况下,Nginx会根据系统特性自动选择一个合适的策略;
  • accept_mutex_delay [time]:仅在accept_mutex启用时生效,设置工作进程在尝试获取锁之前等待的时间(以毫秒为单位)。适当设置该值有助于减少不必要的锁竞争,提高并发性能。

2. use-IO多路复用

这部分内容后续会专门开一个文章来讲,一般情况下,直接使用epoll就可以了:

use epoll;

3. worker_connections

ginx通常采用多工作进程模型来提高并发处理能力。worker_processes指令定义了Nginx启动的进程数。总的并发连接能力是worker_processes乘以worker_connections。例如,如果你设置了worker_processes 4和worker_connections 1024,那么理论上Nginx可以同时处理的最大连接数为4096。

worker_processes指令直接配置在nginx.conf文件中,可以直接使用auto。Nginx将自动检测服务器的CPU核心数,并根据检测结果启动相应数量的worker进程。:

worker_processes            auto;

设置worker_connections可以考虑如下几点:

  • 最大文件描述符数(ulimit -n) :每个TCP连接在系统中对应一个文件描述符。因此,worker_connections的值不能超过系统的最大文件描述符数。可以通过运行命令ulimit -n查看当前的限制,必要时可通过修改系统配置(如/etc/security/limits.conf)来增加限制。
  • 内存资源:每个连接都会占用一定的内存。虽然具体的内存消耗取决于多种因素(如HTTP头部大小、缓冲区设置等),但可以参考一个经验公式粗略估算:
内存占用 = worker_connections * (单个连接平均内存消耗)
  • 预期并发连接数:根据业务需求和历史数据,预估正常情况下及峰值时段可能达到的并发连接数。worker_connections应该至少能容纳预期的并发连接数,以避免因连接数限制导致的拒绝服务。

4. accept_mutex

通常,Nginx会根据系统特性自动选择一个合适的策略,所以我们不需要特意去进行设置。

惊群问题:当有新的客户端连接请求到达时,如果没有适当的同步机制,所有处于等待状态的worker进程都可能被内核唤醒并尝试接收(accept)这个连接。这种情况下,只有一个进程能够成功accept,其余进程会发现连接已被处理,从而返回并再次进入等待状态。这种频繁的无效唤醒和上下文切换构成了所谓的“惊群”现象,会导致不必要的CPU资源消耗和潜在的性能损失。

accept_mutex 锁是Nginx用来解决网络服务器中的“惊群”(thundering herd)问题的一种机制。其主要作用在于同步多个worker进程对新连接(accept)事件的处理,避免在高并发场景下多个进程因同时响应新连接请求而造成的资源浪费和性能下降。

accept_mutex 锁强制所有worker进程在处理新连接前必须先获取锁。这样一来,任何时候只有一个进程能持有锁并执行accept操作,其他进程则会阻塞等待锁释放。通过这种方式,Nginx确保了新连接总是由一个确定的worker进程有序地、独占式地接受,消除了多个进程争抢同一连接的可能性。

通过引入锁机制,可以显著减少由于多个进程争抢新连接而导致的上下文切换次数和系统调用开销。这有助于保持服务器的稳定性和提升整体性能,特别是在高并发场景下,避免了因无谓的竞争而导致的系统负载升高。

Nginx在使用accept_mutex的同时,还采用了事件队列(如ngx_posted_accept_events和ngx_posted_events)来进一步优化处理流程。持有锁的worker进程首先处理accept事件,将新连接的信息加入队列,然后释放锁,使得其他等待的worker进程有机会处理其他已就绪的事件(如读事件)。这样设计既避免了长时间阻塞在accept操作上,又确保了新连接的及时接纳和后续数据处理的并行化。

5. ngnix事件处理流程

Nginx事件处理流程基于高效的I/O多路复用技术,通常采用epoll(Linux系统)或kqueue(BSD系统)等高级API来实现非阻塞、事件驱动的网络通信。以下是一个详细的Nginx事件处理流程说明:

  1. 事件模块初始化
  • 在Nginx启动时,事件模块进行初始化,根据操作系统特性选择合适的I/O多路复用机制(如epoll、kqueue、poll或select)。
  • 创建主事件循环(event loop),注册事件处理器函数。
  1. 监听套接字设置
  • Nginx为每个监听端口创建一个监听套接字(socket),并将其设置为非阻塞模式。
  • 将监听套接字添加到I/O多路复用机制中,注册读事件,以便检测是否有新的连接请求到来。
  1. 主事件循环
  • Nginx进入主事件循环,核心是调用I/O多路复用API(如epoll_wait)来阻塞等待事件的发生。
    • 此过程会一直等待,直到有至少一个事件(如新连接、已连接套接字的数据可读/写、定时器超时等)发生,或者被外部信号打断。
  1. 事件处理
  • 当epoll_wait返回时,它会返回一个事件列表,包含了所有就绪的文件描述符及其对应的事件类型。
  • Nginx遍历这个事件列表,对每个事件进行处理:
    • 新连接事件
      • 如果事件是监听套接字的可读事件,表示有新的连接请求。持有accept_mutex锁的worker进程(如果启用)会调用accept系统调用接收新连接,并创建一个新的工作连接(work connection)。
      • 新的工作连接同样被设置为非阻塞,并添加到I/O多路复用机制中,注册读写事件。
      • 连接相关信息(如客户端IP、端口等)与请求上下文关联起来,准备后续的请求处理。
    • 已连接套接字事件
      • 如果事件是已连接套接字的可读/写事件,表示对应连接上有数据可读或缓冲区空间足够写入数据。
      • Nginx调用相应的读写回调函数处理这些事件:
        • 读事件:读取客户端请求数据,解析请求头和主体,填充到请求上下文中。
        • 写事件:将响应数据从输出缓冲区写回客户端,直至缓冲区清空或写操作被暂停(如遇到流控制)。
    • 定时器事件
      • 如果有定时器超时,触发相应的超时回调函数,执行清理、重试、关闭连接等操作。
  1. 定时器管理
  • Nginx维护一个红黑树(RBTree)结构的定时器队列,用于管理各类定时任务。
  • 当需要设置定时任务时,会创建一个定时器节点,计算其超时时间,并插入到定时器队列中。
  • 在每次事件循环开始时,检查当前时间与最临近超时定时器的时间差,调整epoll_wait的超时参数,以确保既能及时处理定时事件,又能避免不必要的频繁轮询。
  • 当定时器节点超时时,从队列中移除,并调用关联的回调函数处理超时任务。
  1. 信号处理
  • Nginx注册了对特定信号(如SIGINT、SIGTERM、SIGHUP等)的处理函数。
  • 当收到信号时,epoll_wait会提前返回错误。Nginx捕获到信号后,根据信号类型执行相应操作,如优雅地关闭连接、重新加载配置、平滑重启等。
  • 处理完信号后,Nginx重新进入epoll_wait等待下一轮事件。
  1. 请求生命周期管理
  • 针对每个接收到的HTTP请求,Nginx按照其配置的处理流程(如HTTP方法、URI、location匹配等)进行路由。
  • 请求经过各种阶段(如rewrite、access、content等),执行相应的模块处理逻辑,如URL重写、权限检查、内容生成等。
  • 最终,生成的响应数据通过已连接套接字的写事件发送回客户端,完成请求处理。
  1. 连接关闭与清理
  • 当请求处理完毕、出现错误、超时或者客户端主动断开连接时,Nginx会关闭相应的套接字,并从I/O多路复用机制中移除。
  • 关闭连接的同时,释放与此连接相关的资源,如内存、计数器等。
  1. 继续事件循环
  • 处理完当前事件列表后,Nginx重新进入epoll_wait,开始下一轮事件循环,持续监控并响应新的网络事件和定时器事件。

总结来说,Nginx事件处理流程的核心是利用I/O多路复用技术高效地监听和响应多个网络连接上的事件,结合定时器管理机制处理定时任务,并通过信号处理实现对服务器的控制。这一流程确保了Nginx能够在高并发环境下快速、准确地处理请求,同时保持较低的系统资源占用。