Nginx 高并发原理:IO 模型与 backlog

Nginx 高并发原理:IO 模型与 backlog

Nginx 之所以支持高并发,核心在于它的事件驱动模型与针对性的 OS 原语使用(epoll + 边沿触发),以及合理的 backlog 处理。

1 select 模型

int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

select 把要监视的描述符分成 3 类:writefds、readfds、exceptfds。调用后 select 会阻塞,直到有描述符就绪(可读/可写/异常)或超时(立即返回可设置 timeout 为 null)。返回后通过遍历 fd_set 找到就绪的描述符。

  • 优点:跨平台性好,几乎所有平台都支持。
  • 缺点:单个进程能监视的描述符数量存在上限,Linux 下一般为 1024;即使可以通过修改宏或重编译内核提升,也会导致效率下降(每次都要对所有 fd 全量轮询)。

2 poll 模型

int poll(struct pollfd *fds, unsigned int nfds, int timeout); struct pollfd { int fd; /* file descriptor */ short events; /* requested events to watch */ short revents; /* returned events witnessed */ };

poll 用一个 pollfd 指针数组替代 select 的三个位图。pollfd 同时包含需要监视的事件和实际发生的事件,不再使用 select 的「参数-值」传递方式。poll 没有最大数量限制(但数量过大后性能同样下降)。与 select 一样,poll 返回后仍需轮询 pollfd 找出就绪的描述符。随着监视描述符数量增长,效率线性下降。

3 epoll 模型(Nginx 使用)

int epoll_create(int size); int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); typedef union epoll_data { void *ptr; int fd; __uint32_t u32; __uint64_t u64; } epoll_data_t; struct epoll_event { __uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ };

三个核心函数:

  • epoll_create:创建 epoll 文件描述符。参数 size 并非限制最大监听数,只是对内核初始分配的数据结构大小的一个建议。
  • epoll_ctl:对指定描述符 fd 执行控制,event 为关联的监听事件。op 有三种:EPOLL_CTL_ADD(添加)、EPOLL_CTL_DEL(删除)、EPOLL_CTL_MOD(修改)。
  • epoll_wait:等待 epfd 上的 IO 事件,最多返回 maxevents 个事件。

与 select/poll 的本质区别:select/poll 是进程被阻塞后,内核每次都将所有被监视的描述符扫描一遍;而 epoll 通过 epoll_ctl() 预先注册描述符,一旦某个描述符就绪,内核采用类似回调的机制迅速激活该文件描述符,进程调用 epoll_wait 时便能得到通知。

3.1 epoll 的优点

  1. 监视的描述符数量不受限制。理论上限为「可打开文件的最大数」(与内存相关,1GB 内存约 10 万左右,可用 cat /proc/sys/fs/file-max 查看)。select 的数量限制对高连接数服务器是致命的。
  2. IO 效率不随监听 fd 数量增长而线性下降——只有就绪的 fd 才会执行回调函数,select/poll 则是全量轮询。
  3. 支持两种触发模式:
    • 水平触发(LT):fd 状态变化后,若未处理会反复通知,编程简单,libevent 等大量使用;
    • 边沿触发(ET):只告知哪些 fd 刚刚变为就绪,只说一遍,若未处理不再提示。理论上性能更高,但编码复杂——Nginx 使用边沿触发。
  4. 通过 mmap 内存映射加速内核与用户空间的信息传递,避免无谓的内存拷贝。

epoll 文档:https://man.cx/epoll

4 backlog 指令含义

listen address[:port] [backlog=number];

backlog 限制了处于挂起状态的连接队列的最大长度,即:连接已完成三次握手、但尚未被 accept 处理的 socket 队列大小,默认值为 -1(使用内核默认值)。当一个连接请求到达而队列已满时,客户端会收到连接拒绝(Connection refused)。

参考:

5 生产配置建议

worker_processes auto; # 约等于 CPU 核心数 worker_rlimit_nofile 65535; # 提高 worker 可打开文件描述符上限 events { use epoll; # 使用 epoll(Linux) worker_connections 10240; # 每个 worker 的最大连接数 }

官方建议 worker_connections 不高于 worker_rlimit_nofile;总并发上限 ≈ worker_processes × worker_connections(HTTP keepalive 会被复用)。

阅读 — · 全站 —
🎸 我的歌单 0 首