nginx参数multi_accept和accept_mutex关系

multi_accept 是 Nginx 的一个 events 模块参数,用于控制 每次收到新连接事件时,Worker 是否一次性接收所有已就绪连接

1
2
3
4
events {
worker_connections 4096;
multi_accept on;
}

工作原理

当内核通知 Nginx监听 socket 上有新的 TCP 连接。此时有两种行为。

1.multi_accept off(默认)

每次 epoll_wait() 返回后:

epoll_wait()
      │
      ▼
accept() 一个连接
      │
处理请求
      │
再次 epoll_wait()

即:一次事件,只 accept 一个连接,如果 backlog 中还有很多连接,下次 epoll 通知再继续 accept。


2.multi_accept on

每次 epoll_wait() 返回后:

epoll_wait()
      │
      ▼
accept()
accept()
accept()
accept()
...
直到 accept 返回 EAGAIN

也就是:把监听队列(backlog)里已经建立好的连接一次全部取完。

内部逻辑类似:

1
2
3
4
5
6
7
8
9
10
while (1) {
fd = accept(...);

if (fd == -1) {
if (errno == EAGAIN)
break;
}

// 创建连接对象
}

直到:

accept()

↓

EAGAIN

说明当前没有可接收连接了。

目的:

  • 建立连接延迟更低
  • 高并发突发流量时效率更高

优缺点

优点

1. 降低 epoll 唤醒次数

例如:

10000 个连接

关闭:

epoll
↓

accept1

↓

epoll

↓

accept2

可能需要很多轮。

开启:

一次 epoll
↓
accept 10000 次

减少系统调用。


2. 提高吞吐

对于:

短连接

HTTP/1.0
HTTP/1.1 keepalive 很少

或者:

L4 Proxy
TCP Proxy

效果比较明显。


3. 更快清空 backlog

backlog:

1000

如果:

multi_accept off

可能一直积压。

开启:

马上清空 backlog

可以减少连接排队时间。


缺点

最大的缺点是:一个Worker可能长时间占用 CPU 去 accept 新连接,而其他 Worker 暂时没有机会处理连接。

例如:

Worker1

accept()
accept()
accept()
accept()
...
10000 次

Worker1:

一直在 accept

Worker2:

空闲

这会导致:

  • Worker 间负载不均衡
  • 单个 Worker 更容易成为热点

与 reuseport关系

如果开启:

1
listen 80 reuseport;

每个 Worker 都有自己的监听 socket。

此时:

Worker1
Worker2
Worker3
Worker4

内核会将连接分散到各个 Worker。

即使:multi_accept on,每个 Worker只会接收属于自己监听 socket 的连接,因此负载更均衡。


与accept_mutex的关系

以前(尤其在较老版本 Linux 上):

1
2
3
events {
accept_mutex on;
}

用于避免多个 Worker 同时被唤醒(惊群)。

如果:

accept_mutex on
multi_accept on

获得锁的 Worker 会一次性接收大量连接,其他 Worker 在这一轮无法接收连接,因此可能进一步加剧负载倾斜。

现代 Linux 使用 epoll 并结合 EPOLLEXCLUSIVE(以及 reuseport)后,accept_mutex重要性已经大幅降低


是否建议开启?

场景 建议
高并发短连接(如反向代理、API 网关) 通常可开启
长连接(WebSocket、HTTP/2、大量 Keep-Alive) 收益较小,通常保持默认即可
已开启 reuseport 可以开启,负载通常较均衡
未开启 reuseport,Worker 数较多 谨慎开启,可能导致部分 Worker 接收过多连接

multi_accept on 的作用可以概括为一句话:

当监听 socket 变为可读时,一个 Worker 不只接收一个新连接,而是持续调用 accept(),直到监听队列为空(accept() 返回 EAGAIN),以更快地处理突发连接。

它的核心价值是提高突发连接场景下的接入效率,代价是可能增加 Worker 间的负载不均衡。在现代 Linux 系统中,如果同时使用 reuseport,通常更容易兼顾吞吐量和负载均衡。