TCP 连接异常排障:TIME_WAIT 与半连接队列
先看清连接状态分布
任何网络排障,第一步都是看状态统计,而不是猜。
# 各状态连接数
ss -s
# 按状态分组计数
ss -ant | awk 'NR>1 {state[$1]++} END {for (s in state) print s, state[s]}' | sort -k2 -rn
常见输出解读:
| 状态 |
含义 |
是否正常 |
| ESTAB |
已建立连接 |
正常 |
| TIME_WAIT |
主动关闭方等待 2MSL |
大量出现需关注 |
| CLOSE_WAIT |
被动关闭方未调 close |
代码 bug 信号 |
| SYN_RECV |
收到 SYN,等待 ACK |
大量出现可能是 SYN Flood |
TIME_WAIT:不是 bug,是设计
TIME_WAIT 存在的两个原因:
- 确保最后一个 ACK 能到达对端(对端没收到会重发 FIN);
- 让本次连接的迟到报文在网络中消散,避免污染复用同一四元组的新连接。
持续 2MSL(Linux 默认 60 秒)。如果本机是主动关闭方且 QPS 很高,TIME_WAIT 堆积是数学必然。
判断是否真的有问题
# TIME_WAIT 数量
ss -ant state time-wait | wc -l
# 是否触及本地端口耗尽的边界
cat /proc/sys/net/ipv4/ip_local_port_range
# 32768 60999 → 约 2.8 万个可用端口
假设单机 QPS 5000 且每请求一条短连接,60 秒内 TIME_WAIT 可达 30 万 —— 早已超过端口上限。此时新连接会失败并报 Cannot assign requested address。
三层解法(按优先级)
第一层:改架构——用连接池。 长连接复用是根治手段,HTTP keep-alive、数据库连接池都属于此列。
第二层:让客户端做主动关闭方。 谁主动关闭谁承担 TIME_WAIT,把压力转移给资源更充裕的一方。
第三层:调参数。
# 允许 TIME_WAIT 状态的 socket 被新连接复用(仅客户端有效)
sysctl -w net.ipv4.tcp_tw_reuse=1
# 扩大本地端口范围
sysctl -w net.ipv4.ip_local_port_range="10000 65535"
# 缩短 FIN_WAIT2 超时(注意:不要动 tcp_fin_timeout 来"消灭" TIME_WAIT,无效)
sysctl -w net.ipv4.tcp_fin_timeout=15
# 增大 TIME_WAIT 桶上限,防止溢出后直接销毁
sysctl -w net.ipv4.tcp_max_tw_buckets=262144
注意:tcp_tw_recycle 在 Linux 4.12 后已被移除,网上大量教程仍在推荐,不要用。
CLOSE_WAIT:一定是代码问题
CLOSE_WAIT 表示对端已发 FIN,但本进程还没调用 close()。堆积说明程序忘了关闭连接——通常是异常分支没有 finally 释放,或者连接池的归还逻辑有缺陷。
# 找出 CLOSE_WAIT 属于哪个进程
ss -antp state close-wait | awk 'NR>1 {print $NF}' | sort | uniq -c | sort -rn
这个不是调内核参数能解决的,必须修代码。
SYN_RECV 与半连接队列
三次握手中,服务端收到 SYN 后连接进入 SYN_RECV,存放在半连接队列(syn queue),握手完成才移入 accept 队列(backlog)。
# 查看队列溢出统计
netstat -s | grep -i -E "listen|syn"
# 12345 times the listen queue of a socket overflowed
# 678 SYNs to LISTEN sockets dropped
两个常见根因
- backlog 太小:应用
listen(fd, backlog) 的值过小,或 somaxconn 限制了它。
- 应用 accept 太慢:进程忙于其他逻辑,来不及从队列取连接。
sysctl -w net.core.somaxconn=32768
sysctl -w net.ipv4.tcp_max_syn_backlog=8192
# 开启 syncookies 抵御 SYN Flood
sysctl -w net.ipv4.tcp_syncookies=1
排障决策树
ss -s 看状态分布。
- 大量 TIME_WAIT → 检查是否短连接 + 本机是否主动关闭方 → 上连接池。
- 大量 CLOSE_WAIT → 定位进程 → 修代码。
- 队列溢出 → 调
somaxconn 与 backlog,同时看应用 accept 是否受阻。
- 以上都正常但仍丢包 → 用
tcpdump 抓包看链路层。
小结
- TIME_WAIT 是保护机制,不要试图彻底消灭它,而是减少短连接。
- CLOSE_WAIT 只在应用层解决。
- 队列类问题同时涉及内核与应用两侧,两边都要看。