图 1:本文封面,决策树就是排查这件事的正确姿势。
凌晨告警:某台网关的连接数曲线陡增,ss 一查,几万条 TIME_WAIT。另一个团队则遇到相反剧本:连接数缓慢爬升不回落,最后耗尽文件句柄,一查是几千条 CLOSE_WAIT。两种状态长得像,病因却完全不同——一个是「节流问题」,一个是「代码缺陷」,用同一张药方治两种病,是这类故障处理最常见的错误。
先分清两种状态各自意味着什么。
TIME_WAIT 是主动关闭方的正常收尾状态:你这边先发了 FIN,走完四次挥手后要在原地驻留一段时间,确保最后一个 ACK 能重发、旧连接的残包不会混进新连接。它本身是协议设计的一部分,单个 TIME_WAIT 不是病,「量太大」才需要管。CLOSE_WAIT 则完全不同:对方已经发了 FIN、连接只剩半开,你这边该调用 close 收尾却一直没调——它是被动关闭方「忘了关 socket」的泄漏信号,任何量级都可疑,几千条 CLOSE_WAIT 几乎一定指向代码缺陷。
成因定位树:先看哪一种在涨。
排查第一条命令永远是统计,而不是猜:
# 按状态统计本机 TCP 连接数量
ss -ant | awk 'NR>1 {print $1}' | sort | uniq -c | sort -rn
TIME_WAIT 在涨,顺着「谁主动关」往下查:通常是上游调用方用了短连接——每请求一次就建连、用完即关,主动关闭方自然在自己头上堆 TIME_WAIT;也可能是下游服务响应后主动关闭,把 TIME_WAIT 转移到了你这边。CLOSE_WAIT 在涨,顺着「哪条代码路径漏了 close」往下查:常见的是异常分支——正常路径关了,超时、报错、early return 的路径没关;或者用了连接池却拿连接不还。定位手段是对照连接的对端地址与代码里的资源释放点,靠「状态统计 + 代码走读」收敛,不需要抓包就能锁定八九成。
三张对策卡,各治各的病。
对策一,连接池复用,治 TIME_WAIT 的本。短连接变长连接,建连次数降下来,TIME_WAIT 的产生速率自然降下来——这是源头治理,优先级永远最高。对策二,内核参数,治 TIME_WAIT 的标。以 tcp_tw_reuse 为例,内核文档的定义是「在协议视角安全的前提下,允许为新的外发连接复用 TIME-WAIT 套接字」——注意它只作用于主动发起连接的一侧,对服务端被动侧堆积没有意义;新版本内核的默认值是 2(仅对回环流量启用),全局开启要评估对端时钟与中间设备的兼容性,它是优化项不是救命稻草。对策三,修代码加超时兜底,治 CLOSE_WAIT 的唯一正道:补全所有路径的 close,给读写与空闲加超时,让异常连接会被强制回收而不是无限驻留;连接池要配「借出超时」与泄漏检测。没有第四种药——CLOSE_WAIT 调内核参数是无效的,因为问题根本不在内核。
图 2:定位树与三张对策卡,先分类再治理。
三个常见误区。
误区一:见 TIME_WAIT 就调内核参数。参数只能让复用更安全更快,短连接洪峰不除,驻留量就降不下来;而且 TIME_WAIT 驻留本身消耗的只是少量内核结构,真正打满的是端口与句柄上限,先确认瓶颈再动手。误区二:对 CLOSE_WAIT 也开内核参数。被动侧不 close,任何参数都救不了,这病只在代码里。误区三:把「连接数高」当结论。连接数只是症状,按状态拆开后「哪种状态、对端是谁、随时间怎么涨」才是结论;不拆状态的告警只会让值班的人每次从零开始。
给你的行动建议。
今天就在测试环境做一轮演练:用压测工具对服务打短连接流量,亲眼看着 TIME_WAIT 堆起来;再写一段故意漏 close 的客户端,看 CLOSE_WAIT 怎么只涨不跌。然后各用对策卡治理一遍,把「统计—定位—治理」三步走成肌肉记忆。顺手把这条状态统计命令塞进值班手册与告警看板,下次告警响起的三十秒内,任何人都能先回答「哪种状态在涨」。AI 应用的网关与推理服务是这套手册的高发区:上游客户端直连模型服务如果走短连接,流量一上来 TIME_WAIT 就会先在网关上堆出来;推理调用的流式连接如果异常路径不关流,CLOSE_WAIT 会慢慢吃掉句柄上限。
你在线上处理过最棘手的连接状态异常是哪一种?欢迎聊聊排查过程。