朕喜技术(ZhenxiJS)
2026.08.30
1 分钟阅读

TCP 监听队列(backlog/accept队列)被打满导致新连接超时的诊断与解决

问题背景

Web 服务器(Nginx + 应用)突然「慢吞吞」,新 TCP 连接经常超时/被拒绝,但服务进程正常、CPU/内存不高。 ss -lnt 观察到监听端口的 Send-Q 顶满(= backlog 值),怀疑有大量「建立后挂起不交互」的连接占满了内核监听 socket 的 accept 队列。

复现步骤

git checkout 2026-08-30-0800
cd scenario/2026-08-30-0800

# 一键拉起环境
docker compose up -d --build

# 运行复现脚本(backlog=16, accept_delay=0.3s, 用 200 个连接填充)
docker compose exec victim bash /workspace/scenario/2026-08-30-0800/reproduce.sh

关键输出:

# 洪泛前正常连接
[client] reply: b'hello\n'
exit_before=0

# 队列被打满后
LISTEN 16     16           0.0.0.0:8080    0.0.0.0:*    # Send-Q=16 已满
TimeoutError: timed out    # 正常连接超时
exit_during=1

# 连续 10 次连接测试
oks=1, fails=9             # 90% 的新连接失败

根本原因

TCP 三次握手完成后,连接进入监听 socket 的 accept 队列等待应用 accept()。该队列容量由 listen(fd, backlog) 决定(上限受 net.core.somaxconn 约束)。

问题链路:

  1. 应用 backlog 设置太小(16)+ accept 处理慢(每 0.3s 才 accept 一个)
  2. 攻击方建立大量挂起连接 → accept 队列迅速被填满(Send-Q = backlog)
  3. 新连接到达时内核发现 accept 队列已满 → 丢弃 SYN 或放入 SYN 半连接队列
  4. SYN 队列也满 → 直接丢弃 → 客户端超时 / connection reset / refused

解决办法

1. 调大 backlog + 快速 accept(最有效)

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setblocking(False)          # 非阻塞
s.bind(('0.0.0.0', 8080))
s.listen(1024)                # 调大 backlog

while True:
    try:
        conn, addr = s.accept()
        # 快速处理,不阻塞
    except BlockingIOError:
        pass

验证结果: 队列不积压(Send-Q=0),新连接 100% 成功(原始场景只有 10% 成功)。

2. 内核参数调优

# 调大系统级最大 backlog(注意 listen() 受此限制)
sysctl -w net.core.somaxconn=65535

# accept 队列溢出时直接 RST,避免客户端超时等待
sysctl -w net.ipv4.tcp_abort_on_overflow=1

# 调大 SYN 半连接队列
sysctl -w net.ipv4.tcp_max_syn_backlog=65535

# 开启 SYN Cookie 缓解 SYN 洪泛
sysctl -w net.ipv4.tcp_syncookies=1

3. iptables connlimit 限速

iptables -A INPUT -p tcp --dport 8080 \
  -m connlimit --connlimit-above 50 --connlimit-mask 32 -j REJECT

4. 应用层优化

  • 使用非阻塞/多线程 accept,避免连接长时间在队列中等待
  • 设置合理的连接超时(SO_RCVTIMEO / SO_SNDTIMEO),及时关闭挂起连接

验证

指标原始(backlog=16)修复(backlog=1024+非阻塞)
队列满时 Send-Q17/16(满)0/1024(空)
200 个填充连接199 成功、1 失败200 全部成功
新连接成功率10% (1/10)100% (10/10)

经验与避坑

  1. backlog 不是越大越好,但太小很容易被攻击或业务高峰打满。建议根据并发量设置合理值(如 128-1024)。
  2. ss -lnt 的 Send-Q 列表示 accept 队列中等待 accept 的连接数,顶满 = backlog 值 = 队列已满。
  3. 判断是 accept 队列还是 SYN 队列满:ss -ltn state syn-recv 查看半连接队列数量;ss -lnt 看 Send-Q 是否顶满。
  4. tcp_syncookies=1 是默认推荐配置,可缓解 SYN 洪泛导致的半连接队列溢出。
  5. 应用层快速 accept 是最根本的解决手段——backlog 再大也只是延迟问题,accept 慢才会真正让队列堆积。
  6. Docker 容器内改 /proc/sys 通常只读,需在宿主机或特权容器中执行 sysctl。
  7. iptables 操作需 NET_ADMIN capability,普通容器没有该权限,需在宿主环境或特权容器中执行。
  8. 排查此问题时,注意 connlimit、tcp_max_syn_backlog 等参数的综合效果。

版权声明

本文为博主原创文章,遵循 CC BY-NC-SA 4.0 许可协议。 转载请注明出处:http://localhost/blogs/2026-08-30-0836-tcp-backlog-accept-queue

分享这篇文章