Nginx通过非阻塞I/O与epoll事件驱动重构等待逻辑,将上下文切换从“每连接一次”压缩为“每事件一次”:所有socket设为O_NONBLOCK,epoll_wait批量监听就绪状态,worker在用户态集中处理事件,避免因等待而频繁进出内核。

Non-blocking I/O 是 Nginx 降低上下文切换开销的核心机制,它不靠“减少调用次数”,而是重构了等待逻辑:把“内核替你等数据”变成“你自己轮询有没有数据”,从而把上下文切换从“每个连接都触发”压缩为“真正有事才切换”。
阻塞 I/O 怎么拖垮系统
传统 Web 服务(比如 Apache prefork)中,每个连接对应一个进程或线程。一旦调用 read() 或 accept() 而数据还没来,进程就会被内核挂起——保存寄存器、切换调度队列、唤醒其他任务,这是一次完整上下文切换;等数据就绪再唤醒,又是一次。并发 5000 连接,可能每秒触发数万次切换,CPU 大量时间花在“换人干活”而不是“干活”上。
Non-blocking + epoll 如何绕过这个问题
Nginx 不让 worker 进程等单个连接,而是用 epoll_wait() 一次性监听成千上万个 socket 的状态:
- 所有 socket 都设为
O_NONBLOCK,read()/recv()立即返回,不会卡住事件循环 -
epoll_wait()在内核里批量检查 fd 就绪状态,没事件时进程不睡眠,持续运行在用户态 - 只有至少一个 fd 就绪时才返回,此时 worker 在用户态集中处理这批事件(accept 新连接、recv 数据、send 响应),全程不因等待而切换
- 一次系统调用可处理几十甚至上百个就绪事件,上下文切换频次与活跃事件数挂钩,而非连接总数
光 non-blocking 不够,还得配对设计
非阻塞本身只是基础,Nginx 的实际收益来自整套协同设计:
- 每个 worker 是单线程事件循环,不创建新线程,避免线程级切换和栈内存开销
- 主进程只管管理,worker 全权处理网络 I/O,职责隔离减少干扰
- 静态文件走
sendfile(),数据在内核态直接搬运,跳过用户态拷贝,省掉两次上下文切换 - 启用
multi_accept on,让一次 epoll 返回后尽可能多地 accept 新连接,摊薄系统调用成本 - 禁用可能隐式阻塞的操作,比如
proxy_buffering off(导致上游响应慢时 worker 卡住)、aio threads(引入额外线程池)
怎么确认它真正在生效
不能只看配置写了 use epoll,得验证运行态:
-
ps -eo pid,args,psr | grep 'nginx: worker':每个 worker 的 PSR 列应长期稳定在某一个 CPU 编号上 -
vmstat 1观察cs(context switch)列:高负载下若能稳定在 5–10 万/秒以下,说明优化已起效 -
ss -s查看 ESTAB 连接数与 Nginx worker_connections 设置是否匹配,避免因 fd 不足退化到阻塞行为


















