核心是盯住TIME_WAIT每秒减少量而非总量,若watch命令显示数值长期横在2000+或仅个位数下降,说明回收滞后;需确认本机是否主动关闭连接、tcp_tw_reuse与tcp_timestamps是否双启用,CLOSE_WAIT堆积则表明应用层未调用close(),须修代码而非调参。

直接看 TIME_WAIT 消失速率,别数总量
看回收速度,核心不是查当前有多少 TIME_WAIT,而是盯住它“每秒减少多少”。总量高可能是流量大,但消失慢才说明回收卡顿。用 watch -n 1 'netstat -n | grep TIME_WAIT | wc -l' 看实时变化,如果数值长期横在 2000+ 不动或只掉个位数,基本就是回收滞后。
注意:netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}' 只给快照,不能反映动态;ss -tan state time-wait | wc -l 更快,但字段位置不同(状态在第1列),脚本迁移时要改 $NF 为 $1。
确认是不是本机在主动关闭连接
TIME_WAIT 必须由主动发 FIN 的一方承担,所以先搞清谁关的——服务端还是客户端。执行 netstat -n | grep TIME_WAIT | head -5,看本地端口:如果全是高位端口(比如 42987、51234),说明本机是 client,关连接正常;如果本地端口是固定服务端口(如 :80、:443),那服务端在高频短连场景下主动关连接,就容易堆积。
常见误判点:
- 用 netstat -tnp 查 PID 时,若提示 can't identify protocol 或权限不足,直接换 ss -tunp,它不依赖 /proc/net/tcp 的完整读取
- ss 输出里 state time-wait 是小写,netstat 是大写 TIME_WAIT,grep 时注意大小写
检查内核是否真启用了快速重用
tcp_tw_reuse 设成 1 不等于生效,它依赖 tcp_timestamps 开启。执行:cat /proc/sys/net/ipv4/tcp_tw_reuse → 应为 1cat /proc/sys/net/ipv4/tcp_timestamps → 也必须为 1
这两个值缺一不可。很多线上环境只开了 tw_reuse 却忘了开时间戳,结果 TIME_WAIT 还是无法复用。另外:
- tcp_tw_recycle 在 4.12+ 内核已被彻底移除,设了也无效,别再往 /etc/sysctl.conf 里写
- NAT 环境下禁用 tcp_timestamps(因会导致连接超时),此时 tw_reuse 也失效,只能靠调大端口范围或应用层长连接
CLOSE_WAIT 多说明回收根本没启动
大量 CLOSE_WAIT 比 TIME_WAIT 更危险——它意味着对端已发 FIN,本机 TCP 栈回了 ACK,但应用层代码压根没调 close()。这时候谈“回收速度”毫无意义,连接卡在半关闭状态,socket 描述符一直占着。
排查命令链:netstat -tnp | grep CLOSE_WAIT → 找出 PIDlsof -p → 确认该进程确有大量 socket 停在 CLOSE_WAITps -o pid,ppid,comm -p → 看进程是否僵死或卡在某次系统调用里
典型陷阱:
- Java 应用 catch 异常后忘了 finally 关 socket
- Go 的 http.Transport 没配 MaxIdleConnsPerHost,短连爆发后 goroutine 泄漏拖垮 close 调用
- Python 的 requests 默认 keep-alive,但手动 session.close() 漏写
真正卡住回收的,往往不是内核参数,而是应用层那一行没写的 close()。看到 CLOSE_WAIT 堆积,先别碰 sysctl,直接翻代码。TIME_WAIT 多可以调参缓解,CLOSE_WAIT 多只能修 bug。











