ArcLibrary

TIME_WAIT 与 CLOSE_WAIT 排查

高并发服务最常见的两种异常状态 —— 一个是设计、一个是 bug。

TIME_WAITCLOSE_WAIT状态机
核心 · Key Idea

一句话TIME_WAITTCP 设计要求的等待期,主动关闭方必经;CLOSE_WAIT应用代码 bug —— 你收到对端 FIN 却没调 close()

是什么#

主动关闭方完整流程:

ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT (2*MSL ≈ 60s) → CLOSED

被动关闭方流程:

ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED
                  ↑
       应用没 close() 就卡这里不走

打个比方#

打个比方 · Analogy

TIME_WAIT = 挂电话后还把话筒贴耳朵听一会儿,确认没有迟到的回声才安心放下。 CLOSE_WAIT = 对方说挂了,你 「嗯」答应了一声却忘了真的把话筒挂上,电话一直占线。

关键概念#

MSLMax Segment Lifetime
网络上一个 segment 最多存活时间(典型 30s)。TIME_WAIT 等 2*MSL 是为了让两边的迟到包都自然消亡。
TIME_WAIT 风险源端口耗尽
高频短连接的客户端,本地端口(约 28K 个)很快被 TIME_WAIT 占满。
CLOSE_WAIT 风险fd 泄漏
应用 fd 慢慢漏,最终 'too many open files'。
tcp_tw_reuseTW 复用
Linux 选项,允许新连接复用 TIME_WAIT 占用的本地端口。
SO_LINGER 0强行 RST 关闭
跳过四次挥手直接发 RST,**慎用**:未发数据丢失。

怎么工作#

应用层 close() 决定自己是主动还是被动 —— 谁先关谁是主动方。

实操要点#

  • 统计状态

    ss -tan | awk '{print $1}' | sort | uniq -c
  • TIME_WAIT 太多(万级):

    • 客户端:net.ipv4.tcp_tw_reuse=1ip_local_port_range 调宽;
    • 服务端:通常无害(服务端被动关闭时不会进 TIME_WAIT,连接保活 / 长连接);
    • 不要tcp_tw_recycle(已被 Linux 删除)。
  • CLOSE_WAIT 持续涨

    • lsof -p <pid> | grep CLOSE_WAIT 找 fd;
    • 检查代码:异常路径忘了 defer conn.Close()
    • 反向代理后端:超时机制是否触发关闭。
  • HTTP keep-alive / 连接池:复用连接而不是每次新建是最根本的解法。

易混点#

TIME_WAIT
**协议要求**。
一段时间后自动消失。
通常调参解决。
CLOSE_WAIT
**应用 bug**。
不修永远卡着。
必须改代码。

延伸阅读#