首页/系统/Linux 内核与性能调优速查/listen() backlog 与应用层队列
网络栈调优98 条

listen() backlog 与应用层队列

应用 listen 的 backlog 决定内核为该监听 socket 保留的处理队列长度, 最终生效值受 somaxconn 约束。

backlog 与两个队列

应用调用 listen(fd, backlog) 时,内核为监听 socket 维护半连接队列(收到 SYN、握手未完成)与接受队列(握手完成、等应用接受)。backlog 是应用给出的长度上限,内核会按 net.core.somaxconn 截断,最终生效值取两者较小者;半连接队列还受 net.ipv4.tcp_max_syn_backlog 与同步 cookie 机制影响。队列溢出时新连接会被丢弃或降级处理,客户端看到的是超时或重试。

基本信息

适用平台Linux(BSD 系参数语义相近,上限取值不同)
相关参数net.core.somaxconn、net.ipv4.tcp_max_syn_backlog、net.ipv4.tcp_abort_on_overflow
生效规则监听队列上限取应用 backlog 与 somaxconn 的较小值
查看方式监听行的发送队列列为上限,接收队列列为已建连未接受数

典型使用场景

  • 高并发短连接服务在突发流量下出现连接超时或拒绝,需要确认是队列容量不足还是应用消费太慢。
  • 内核溢出计数持续增长,需要区分是接受队列溢出还是半连接队列溢出。
  • 调整 nginx 的 backlog、Node 的 listen 第二参数、Tomcat 的 acceptCount 后,核对最终生效值。
  • 压测复现「已建连但应用报错」,判断瓶颈在握手阶段还是在应用接受环节。
  • 新服务上线的首包延迟跳变排查,确认队列长度与连接建立速率是否匹配。

读取生效值

读取生效值
ss -lnt | head -5          # 监听行:发送队列列即 backlog 上限,接收队列列为已建连未接受数
ss -lnt 'sport = :8080'    # 只看某个端口,避免被其它监听行干扰
ss -lntp | grep nginx      # 结合进程名确认是哪个服务在监听
sysctl net.core.somaxconn  # 内核允许的最大值,应用值再大也会被它截断
nstat -az TcpExtListenOverflows TcpExtListenDrops   # 观察两类溢出计数是否在增长

两类队列的区分

队列堆积特征对应计数
半连接队列握手未完成的条目堆积同步 cookie 发送数与队列溢出计数
接受队列已建连但应用未接受,接收队列列上升接受队列溢出计数
应用侧接受后处理阻塞,连接在工作线程排队只看队列看不出,需要应用内埋点
backlog 只是上限,不解决消费速度:应用接受慢或处理阻塞时,队列再大也只是把溢出时间往后推,容量与消费能力要一起看。
只把内核上限调大而应用仍用默认 backlog(或反之)不会有任何效果——内核取的是两者较小值,两侧都要改,改完用监听行的队列列复核。

易错点

  • 只改应用 backlog 或只改内核上限,以为调优生效:必须两侧同时调整,并用监听行读出真实上限。
  • 把连接超时一律归因于 backlog:队列溢出有明确计数器,先看计数再动手,否则容易白改参数。
  • 把半连接队列与接受队列混为一谈:前者对应握手阶段堆积与伪造源,后者对应应用不接受,处置方向完全相反。
  • 忽略溢出时的行为开关:置位强制丢弃开关后队列满时直接回 RST,客户端看到的是连接被重置而不是超时,现象变了但根因还在。
  • 在容器内改了内核参数却忘了命名空间:容器有自己的网络参数视图,宿主机改动不一定进入容器。

数据来源与参考

  • man 2 listen
  • man 7 tcp
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌