TCP 连接堆积:TIME_WAIT 与 accept queue 的排查

一、现象与影响面:连接数报警不等于连接数故障

症状:高峰期接口偶发超时或 connection refused,监控上连接数曲线陡增,或者 accept queue overflow 的字样出现在 netstat 统计里。影响面从”个别请求重试后成功”到”新连接完全建立不了”不等——accept 队列溢出时,三次握手会被悄悄丢弃,客户端只觉得慢,服务器日志里却一行错误都没有,这是 TCP 连接问题最迷惑人的地方。

先立一个判断原则:连接状态统计是分诊台,不是病历本。看到 TIME_WAIT 数以万计就重启服务、清连接,等于把体温计读数当病治。反模式一:把 TIME_WAIT 堆积本身当故障处理——它绝大多数时候是高流量短连接服务的正常代谢,真正的病灶往往在别处。先取证,按下面的顺序推。

排查主线:先做全局状态统计定方向,再分别进 TIME_WAIT 线与 accept queue 线。所有命令默认在出问题的那台服务器上跑,netstat 老旧机器没有 ss 时才用 netstat。

二、第一步:状态统计,先分清四种堆积

# 服务器上;ss 比 netstat 快得多,机器上有就优先用
ss -s
1. TCP:   312 (estab 45, closed 0, orphaned 0, synrecv 0, timewait 280/0), ports 0
1.        ← 总数、estab、timewait 一目了然

1. 按状态细分,看清每种状态各有多少
ss -tan | awk 'NR>1 {print $1}' | sort | uniq -c | sort -rn
1.    280 TIME-WAIT
1.     45 ESTAB
1.     12 CLOSE-WAIT
1.      3 LISTEN

拿到分布就完成了分诊,四种典型堆积的走向完全不同:

堆积的状态 语义一句话 大概率是 下一站
TIME_WAIT 量大 主动关闭方在等协议规定的时间流逝 高 QPS 短连接,多为正常 第三节
CLOSE_WAIT 量大 对方已关,本方应用迟迟不调 close() 代码里 fd 没关,资源泄漏 第三节,但走 CLOSE_WAIT 分支
ESTAB 逼近上限 连接都是活的 流量真的涨了,或客户端没复用连接 第五节预防
SYN-RECV 量大 握手完成了一半 半连接洪水或攻击,另案处理 本文不展开

方向定了就进对应分支。TIME_WAIT 与 CLOSE_WAIT 是最容易混的一对,先用一张表把语义钉死。

三、TIME_WAIT 与 CLOSE_WAIT:一字之差,两种人生

维度 TIME_WAIT CLOSE_WAIT
谁进入这个状态 主动关闭的一方 被动关闭的一方
在等什么 等 2MSL 计时走完,确保最后的 ACK 送达、旧报文自然消亡 等本方应用调 close(),内核不能替你关
持续多久 Linux 固定约 60 秒,不可调——常被张冠李戴的 tcp_fin_timeout 管的是 FIN_WAIT_2,不是它 没有计时器,应用不 close 就永远在
数量多说明什么 刚才短连接流量大,正常代谢;占的是少量内存与端口资源 应用漏关连接,每一条都在泄漏一个文件描述符
该不该动手 一般不动;长期居高才考虑让客户端用长连接 立刻查代码:异常分支里的连接、HTTP 客户端没复用也没关、响应体没读完就丢弃

判断口诀:TIME_WAIT 多,看流量曲线对得上就对得上;CLOSE_WAIT 多,不用想,去查代码。CLOSE_WAIT 的验证也直接——挑一条挂着的连接看归属:

# 服务器上,找出 CLOSE_WAIT 挂在哪个进程上
ss -tanp state close-wait | head
1. Recv-Q Send-Q  Local Address:Port   Peer Address:Port  Process
1. 0      0       10.0.0.5:8080        10.0.0.9:52114     users:(("java",pid=1234,fd=890))
1. ← pid 拿去对进程,基本都能对到一段没处理异常关闭的调用代码

TIME_WAIT 数量确实高到影响建连(本地端口耗尽,报错里出现 Cannot assign requested address)时,正解排序是:客户端上连接池或 keep-alive(消掉大部分短连接)> 出方向连接开 tcp_tw_reuse(仅对主动发起方生效,依赖时间戳选项)> 扩源端口范围。网上的老教程常推荐 tcp_tw_recycle——这个开关已在内核 4.12 中移除,它当年在 NAT 环境下会静默丢连接,新系统里配置了也不存在,别再抄。

状态分布正常、TIME_WAIT 也在合理范围,但建连还是失败,往下查 accept queue。

四、accept queue:全连接队列溢出,最安静的故障

监听中的 socket 背后有一条队列:三次握手完成的连接先排在这里,等应用调用 accept() 取走。队列长度 = min(应用传给 listen 的 backlog,内核上限 net.core.somaxconn)。应用 accept 得不够快,或者队列上限太小,溢出后新连接直接被丢——客户端重试表现成”偶发超时”,服务器错误日志里却常常一个字都没有。

# 服务器上;-l 只看监听 socket,此时 Recv-Q/Send-Q 的含义变了:
1. Recv-Q = 当前排在 accept 队列里的连接数;Send-Q = 队列上限
ss -lnt
1. State  Recv-Q Send-Q Local Address:Port
1. LISTEN 0      128    0.0.0.0:8080        ← Send-Q 只有 128,上限偏小
1. LISTEN 197    200    0.0.0.0:9090        ← 197/200,队列正在满,抓到现行

1. 内核统计里找溢出计数
netstat -s | grep -i listen
1.     1024 times the listen queue of a socket overflowed
1.     1024 SYNs to LISTEN sockets dropped     ← 计数在涨 = 实锤溢出

两个数字对着看:ss -lnt 的 Recv-Q 长期贴着 Send-Q,加上 netstat -s 的 overflow 计数持续增长,溢出成立。然后回答”为什么满”,只有两个方向:

方向 特征 验证
应用 accept 慢:工作线程全忙、锁竞争、GC 停顿、同步阻塞在慢依赖上 CPU 或线程池先打满,队列只是结果 看进程 CPU/线程栈,压测复现时抓一次栈
队列上限太小:backlog 参数没调,或被 somaxconn 封顶 流量毛刺一来就溢出,平时 Recv-Q 很低 ss -lnt 看 Send-Q;sysctl net.core.somaxconn 看内核上限

内核上限有个值得记住的版本事实:老内核 net.core.somaxconn 默认 128,从内核 5.4 起默认提到 4096。很多老机器、老镜像里它仍是 128,应用传再大的 backlog 也会被压到 128。调大是 sysctl -w net.core.somaxconn=4096 并写进 /etc/sysctl.conf 持久化,同时把应用侧 listen 的 backlog 参数一起调(如 Nginx 的 listen 8080 backlog=4096;,数值不超过 somaxconn 才有效)。

反模式二:只调大 backlog 不查应用 accept 能力。队列从 128 加到 4096,只是把溢出点从”高峰开始后 3 秒”推迟到”高峰开始后 30 秒”,accept 慢的根子还在。队列是缓冲不是产能。另外别开 tcp_abort_on_overflow 当”解决”——它只是把静默丢弃改成发 RST,让客户端立刻报错,适合定位,不适合长期开。

五、根因清单:按出现频率排序

根因 典型症状 一行验证
客户端没复用连接,纯短连接打高流量 TIME_WAIT 数与 QPS 曲线同步起伏 ss -tan 状态分布对照流量曲线
应用漏关连接(异常分支/未读完整响应) CLOSE_WAIT 单调上涨不回落 ss -tanp state close-wait 找进程
accept 队列溢出:应用处理慢 overflow 计数增长,伴随 CPU/线程池打满 netstat -s 看 overflowed 计数
accept 队列溢出:backlog 或 somaxconn 太小 毛刺期 Recv-Q 贴满 Send-Q ss -lnt 与 sysctl net.core.somaxconn
文件描述符上限太低(ulimit) Too many open files,accept 直接失败 cat /proc/<pid>/limits 对照进程连接数
抄了 tcp_tw_recycle 的老配置 NAT 后用户批量连不上;sysctl 报不存在 sysctl net.ipv4.tcp_tw_recycle,4.12 起已移除

六、预防

措施 做什么 边界
状态曲线化 把 ss -s 的 estab/timewait/close-wait 三个数采集进监控,突变即告警 CLOSE_WAIT 的告警阈值应远低于 TIME_WAIT,几十条就该看
客户端长连接化 HTTP 客户端开 keep-alive、数据库走连接池,从源头消短连接 连接池要配上限与空闲回收,否则换成 ESTAB 堆积
队列三件套对齐 应用 backlog、somaxconn、fd 上限在部署清单里一次性对齐 队列调大只是缓冲,accept 产能以压测为准

这套走法收拢成一句:先用状态分布分诊(TIME_WAIT 正常代谢、CLOSE_WAIT 直奔代码、ESTAB 看容量),再对 accept queue 用 Recv-Q/Send-Q 加 overflow 计数实锤——重启清不掉任何一条根因,只能清掉现场。

延展阅读:ss/netstat 这些观测命令的日常用法,见《Linux 常用命令》;单机队列调到头仍扛不住时,下一步是横向分担,见《多节点集群架构规划》。

版本核实(以官方文档为准):TIME_WAIT 在 Linux 上持续约 60 秒且无直接可调参数(tcp_fin_timeout 只作用于 FIN_WAIT_2)、CLOSE_WAIT 无内核计时等待应用 close、ss 对 LISTEN socket 的 Recv-Q/Send-Q 分别表示队列当前值与上限,均为长期稳定内核/工具行为;net.core.somaxconn 默认值自内核 5.4 起由 128 提升至 4096、net.ipv4.tcp_tw_recycle 自内核 4.12 起移除,为已核实的版本事实;tcp_tw_reuse 仅对主动发起方向生效且依赖时间戳选项。具体数值语义以所用内核版本的 ip-sysctl 文档为准。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇