从TIME_WAIT爆炸到端口耗尽:Linux短连接服务排查与优化

前言

线上最"玄学"的一类故障,是服务本身没崩,但就是连不上 :请求大量超时、偶发 connection refused、网关/应用报 cannot assign requested address,重启一会儿又好。

很多时候,这不是CPU也不是内存,而是连接资源 被耗尽了------典型表现是 TIME_WAIT 爆炸本机临时端口(ephemeral ports)耗尽 、或者 conntrack 表打满

这篇文章给你一套可落地的方法:先用最少的命令把问题定性,再把根因分到"应用/系统/网络设备"三层,最后给出一组稳妥的优化组合(含风险点)。


一、先把现象说清楚:到底"耗尽"的是什么?

常见报错大致对应三类资源:

  • 端口耗尽(本机临时端口用完)

    • 客户端侧常见:bind: cannot assign requested address
    • 直觉:你以为是"连不上对方",实际上是"我自己没端口了"
  • 连接/文件描述符耗尽(FD不够)

    • 常见:too many open files
    • 直觉:进程能跑,但新连接建不起来
  • 连接跟踪(conntrack)耗尽(常见于NAT/防火墙)

    • 常见:转发/网关侧丢包、连接建立慢、随机超时
    • 直觉:业务机器看起来正常,但"过网关就坏"

这三类问题会互相叠加,所以第一步必须定性:现在卡在谁身上。


二、三条命令快速定性(5分钟内出结论)

2.1 先看连接状态分布

bash 复制代码
ss -s

重点看:

  • TCP: 总量
  • estab(ESTABLISHED)是否异常高
  • timewait 是否异常高
  • orphaned / tw(不同系统显示略有差异)

再细一点看:

bash 复制代码
ss -tan state time-wait | wc -l
ss -tan state established | wc -l

如果 TIME-WAIT 远大于 ESTABLISHED,通常是短连接过多(或连接复用失败)引起。

2.2 确认是否是"端口耗尽"

先看本机临时端口范围:

bash 复制代码
sysctl net.ipv4.ip_local_port_range

输出类似:

ini 复制代码
net.ipv4.ip_local_port_range = 32768 60999

这意味着可用临时端口大约 28k 个。然后看 TIME_WAIT 数量是不是接近这个数量级(甚至更高)。

快速估算:如果你有 30k 左右 TIME_WAIT,同时应用还在猛烈发起新连接,端口耗尽就很容易出现。

进一步确认:抓一个典型报错进程,看它是否频繁对外建立连接:

bash 复制代码
pid=<你的进程PID>
lsof -p "$pid" -nP | grep TCP | head

如果大量是 -> 目标IP:目标端口 的 outbound 连接,而且端口持续变化,就是典型的"客户端短连接风暴"。

2.3 排除/确认 conntrack 打满(NAT/网关特别关键)

bash 复制代码
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max

如果 count 接近 max,基本可以判定:conntrack 在掉链子。这类问题常见在:

  • 出口 NAT 网关
  • K8s 节点(iptables 模式 + 大量出向连接)
  • 防火墙设备(Linux 做网关)

三、把根因分层:应用层 / 系统层 / 网络层

3.1 应用层:连接没有复用(最常见)

现象:TIME_WAIT 多、端口消耗快,但服务CPU/内存并不高。

根因通常是:

  • HTTP 客户端没启用 keep-alive(或频繁创建 client)
  • 连接池太小,池满后退化成短连接
  • 请求超时设置不当导致连接被动关闭/频繁重建

3.1.1 Go:最容易踩的几个点

Go 的 http.Client可复用对象 。常见错误是每次请求都 &http.Client{}http.NewRequest 后不正确处理 body。

关键要点:

  • 复用同一个 http.Client
  • 读完并关闭 resp.Body(否则连接无法复用)
  • 设置合理的连接池参数(Transport)

示例(可作为生产默认模板):

go 复制代码
tr := &http.Transport{
    Proxy: http.ProxyFromEnvironment,
    MaxIdleConns:        1000,
    MaxIdleConnsPerHost: 200,
    IdleConnTimeout:     90 * time.Second,
    TLSHandshakeTimeout: 10 * time.Second,
    ExpectContinueTimeout: 1 * time.Second,
}

client := &http.Client{
    Transport: tr,
    Timeout:   5 * time.Second,
}

resp, err := client.Get(url)
if err != nil { /* ... */ }
defer resp.Body.Close()
io.Copy(io.Discard, resp.Body) // 读完,保证连接可复用

注意:如果你只 Close() 不读完 body,很多情况下连接还是会被丢弃,复用效果很差。

3.1.2 Java:连接池与复用要"显式正确"

以 Apache HttpClient / OkHttp 为例,核心是:

  • 连接池要单例
  • 超时设置要合理(connect/read/write)
  • 线程数/并发量要匹配池大小

如果你用的是"每次 new 一个客户端",TIME_WAIT 爆炸基本是必然的。

3.2 系统层:端口范围太小 / TIME_WAIT 回收策略不当

3.2.1 扩大临时端口范围

默认范围在很多高并发出向场景不够用。可以扩大到:

bash 复制代码
sysctl -w net.ipv4.ip_local_port_range="10000 65535"

持久化写入 /etc/sysctl.conf/etc/sysctl.d/*.conf

3.2.2 TIME_WAIT 的回收与复用(谨慎)

你经常会看到建议开:

bash 复制代码
sysctl -w net.ipv4.tcp_tw_reuse=1

它的含义是:在一定条件下允许复用处于 TIME_WAIT 的连接(更准确说复用本地端口)。对"客户端大量出向短连接"有帮助。

注意事项:

  • tcp_tw_recycle 已经被移除(不要再找它,也不要再抄过时文章)
  • tcp_tw_reuse 主要影响"主动发起连接的一端",对纯服务端监听端口帮助有限
  • 如果你的流量经过 NAT、或对端对时间戳敏感,极端情况下可能引发连接异常(虽不常见,但要灰度验证)

3.2.3 FIN/TIME_WAIT 相关参数(别乱调)

有人喜欢调:

bash 复制代码
sysctl -w net.ipv4.tcp_fin_timeout=15

这对某些场景能减少资源占用,但不是万能药,并可能影响长连接的关闭行为。建议先把"连接复用"做对,再谨慎调系统参数。

3.3 网络层:NAT/负载均衡导致端口与跟踪表压力

3.3.1 典型场景:出口 NAT 端口耗尽

当很多内网机器通过一个出口IP访问公网时,出口设备需要为每条连接分配 SNAT 端口。SNAT 端口同样是有限的。

表现:

  • 内网服务访问公网不稳定、随机超时
  • 出口设备 nf_conntrack 接近上限

解决思路:

  • 增加出口IP(SNAT 多IP)分摊端口
  • 增大 conntrack 表,并优化超时
  • 降低短连接:让应用尽量长连接/连接池复用

四、从"症状 → 根因"的排查路径(建议照抄)

4.1 先确认是不是应用没复用连接

bash 复制代码
# 看某个目的端口的连接状态分布(以 443 为例)
ss -tan '( dport = :443 )' | awk 'NR>1{print $1}' | sort | uniq -c | sort -rn

# 看某个进程的对外连接数量
pid=<PID>
lsof -p "$pid" -nP | grep TCP | wc -l

如果连接建得非常快、TIME_WAIT 多、而且目的地集中在少数几个服务,优先改应用。

4.2 如果是网关/NAT,先看 conntrack

bash 复制代码
watch -n 1 'echo -n "count="; cat /proc/sys/net/netfilter/nf_conntrack_count; echo -n " max="; cat /proc/sys/net/netfilter/nf_conntrack_max'

如果 count 持续顶着 max 跑,先别在业务机上"乱调 sysctl",根因可能在网关。

4.3 排除 FD 限制

bash 复制代码
pid=<PID>
cat /proc/"$pid"/limits | grep -i 'open files'
lsof -p "$pid" | wc -l

如果 lsof 数接近上限,先提 FD,再看连接复用。


五、落地优化方案(按优先级)

5.1 优先级A:应用侧连接复用(收益最大、风险最小)

  • HTTP 客户端复用 + 连接池
  • 确保响应体正确关闭/读完
  • 合理的超时与重试策略(避免"超时→重连风暴")

5.2 优先级B:扩大端口池(中等收益、低风险)

bash 复制代码
sysctl -w net.ipv4.ip_local_port_range="10000 65535"

5.3 优先级C:适度开启端口复用(需要灰度)

bash 复制代码
sysctl -w net.ipv4.tcp_tw_reuse=1

只建议在"客户端出向短连接 + 已确认复用仍不足"的场景使用,并灰度验证。

5.4 优先级D:网关/防火墙/节点 conntrack 调优(看场景)

增大表:

bash 复制代码
sysctl -w net.netfilter.nf_conntrack_max=1048576

同时要评估内存占用与超时策略(不同发行版默认值差异很大),别"只加 max 不管回收"。


六、一份速查清单(排查时直接对照)

6.1 现象 → 可能原因

现象 常见原因
cannot assign requested address 本机临时端口耗尽 / TIME_WAIT 太多
too many open files FD 上限不够 / 连接泄漏
随机超时、跨网段更明显 网关 conntrack 打满 / NAT 端口耗尽
TIME_WAIT 高但服务端也有问题 客户端复用失败 + 服务端短连接

6.2 必跑命令(建议收藏)

bash 复制代码
ss -s
ss -tan state time-wait | wc -l
sysctl net.ipv4.ip_local_port_range
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max

总结

连接问题排查的核心不是"调内核参数",而是:

  1. 先定性:端口耗尽 / FD 耗尽 / conntrack 耗尽
  2. 先治本:应用侧连接复用与连接池
  3. 再兜底:扩大端口范围、谨慎启用 tw_reuse
  4. 别忽略网络设备:NAT/网关的 conntrack 与 SNAT 端口同样会成为瓶颈

只要按这个顺序做,TIME_WAIT 爆炸和端口耗尽基本都能稳住,而且不会引入隐性风险。


更多运维与网络实战文章,欢迎关注公众号:北平的秋葵

相关推荐
Victor3565 小时前
MongoDB(2)MongoDB与传统关系型数据库的主要区别是什么?
后端
JaguarJack5 小时前
PHP 应用遭遇 DDoS 攻击时会发生什么 从入门到进阶的防护指南
后端·php·服务端
BingoGo5 小时前
PHP 应用遭遇 DDoS 攻击时会发生什么 从入门到进阶的防护指南
后端
Victor3565 小时前
MongoDB(3)什么是文档(Document)?
后端
牛奔7 小时前
Go 如何避免频繁抢占?
开发语言·后端·golang
想用offer打牌12 小时前
MCP (Model Context Protocol) 技术理解 - 第二篇
后端·aigc·mcp
KYGALYX13 小时前
服务异步通信
开发语言·后端·微服务·ruby
掘了13 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
爬山算法14 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
Moment14 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端