TIME_WAIT 是主动关闭连接的一端(如Java客户端、Nginx代理)在发完最后一个ACK后维持的状态,持续2×MSL(Linux通常60秒),期间本地端口不可复用;高频短连接易致端口耗尽,表现为bind: cannot assign requested address。

TIME_WAIT 是谁在“占着端口不放”?
是主动关闭连接的那一端(通常是你的 Java 客户端、Nginx 代理、HTTP 调用方),不是服务端。它发完最后一个 ACK 后,必须卡在 TIME_WAIT 状态满 2×MSL(Windows 默认约 30–240 秒,Linux 通常 60 秒),期间这个本地端口就不可复用。
你每秒新建 100 个短连接,TIME_WAIT 就会堆到 6000+(按 60 秒算);如果可用临时端口只有 net.ipv4.ip_local_port_range=32768 60999(约 28k),不到 5 秒就可能触发 bind: cannot assign requested address —— 这不是连不上服务,是你自己没端口了。
怎么快速确认是不是端口耗尽?
别猜,三步定性:
-
ss -s看总连接数和time-wait占比:如果time-wait数量接近或超过net.ipv4.ip_local_port_range的差值,基本就是端口池见底 -
ss -tan state time-wait | wc -l和ss -tan state established | wc -l对比:前者远大于后者,说明连接建了就关,没复用 -
cat /proc/sys/net/netfilter/nf_conntrack_count和/proc/sys/net/netfilter/nf_conntrack_max:如果接近满,问题可能出在 NAT 层(比如 K8s Service、云 SLB),不是你本机的TIME_WAIT
内核参数调优:哪些能开,哪些必须关
关键不是“减少 TIME_WAIT”,而是让它别堵死新连接。以下配置需写入 /etc/sysctl.conf 并执行 sysctl -p:
-
net.ipv4.tcp_tw_reuse = 1:允许把TIME_WAITsocket 用于新的 outbound 连接(客户端场景最有效),但前提是tcp_timestamps = 1 -
net.ipv4.tcp_timestamps = 1:必须开启,否则tw_reuse不生效;也防止序列号绕回,现代系统默认开,但显式设上更稳 -
net.ipv4.tcp_fin_timeout = 30:把FIN_WAIT_2和TIME_WAIT的等待窗口从默认 60 秒缩到 30 秒,安全且见效快 -
net.ipv4.ip_local_port_range = 1024 65535:扩大临时端口范围(注意:某些旧应用硬编码端口上限,改前先验证兼容性) -
net.ipv4.tcp_max_tw_buckets = 200000:避免内核因桶满而静默丢弃新连接,报错见dmesg | grep "time wait bucket table overflow" -
net.ipv4.tcp_tw_recycle = 0:必须为 0!该参数在任何有 NAT 的环境(家用路由器、云 LB、K8s)都会导致随机连接失败,Linux 4.12+ 已移除,切勿启用
Java 客户端高频建连的真实解法
参数调优只是缓冲垫,根子在代码。你用 new Socket("localhost", 1972) 循环建连,等于每毫秒申请一个端口,操作系统再怎么调也扛不住。
- 优先用连接池:比如
Apache HttpClient的PoolingHttpClientConnectionManager,或 Netty 自带的连接复用机制 - 禁用
try-with-resources关闭Socket:它会强制close(),触发TIME_WAIT;改用长连接 + 心跳保活 - 若必须短连,加限流:比如用
ScheduledExecutorService控制最大并发连接数,或用RateLimiter限速(实测 Windows 下间隔 ≥15ms 才稳定) - 别信“加 delay 就行”:10ms 延时只是把问题延后,一旦流量突增或机器负载升高,照样崩
最常被忽略的一点:很多团队调完内核参数就以为搞定了,结果上线后发现 TIME_WAIT 没降多少——因为应用层还在疯狂 close(),内核再快也追不上关的速度。调参和改代码,得一起动。











