西西软件园多重安全检测下载网站、值得信赖的软件下载站!
软件
软件
文章
搜索

首页 → 业内动态 → 业内资讯 → 2万条TIME_WAIT占死端口,为什么只有1个进程在跑?

2万条TIME_WAIT占死端口,为什么只有1个进程在跑?

相关软件相关文章发表评论 来源:本站整理时间:2026/11/3 6:19:45字体大小:A-A+

tcp-zv2.6.1.72电脑版
  • 类型:网络工具大小:663KB语言:简体中文 评分:10.0
  • 标签:
立即下载

上周三晚上十点,客服微信炸了:网站打不开。SSH 上去一看,Nginx 活着,磁盘还有 40G,CPU 才 12%,内存用了不到 30%。查应用日志,只有一行红色的 bind() to 0.0.0.0:443 failed: Address already in use。可我用 ss -lntp 一看,监听 443 的就 Nginx 一个进程,占用端口的说法完全对不上。

最后查出来的原因是 TIME_WAIT 堆积。那台机器跑的是一个「短连接」为主的高并发接口服务:每个请求都新开一条 TCP 连接,用完就关。峰值 QPS 冲到 3000 的时候,内核里积了 2 万多条 TIME_WAIT,把本机端口号(ephemeral port range)全吃干了,新连接根本申请不到端口,于是 bind 失败。

如果你手上也是一台跑高并发接口的机器,正在为端口莫名不够用、连接数涨得离谱发愁,可以先看看这几家的配置和线路说明:ZJI 独立服务器(原生 IP 精品线路,为高并发业务提供裸金属基础),另一家用 数脉科技(香港多线接入,全球代理网络与低延迟回程),机器本身的线路和连接承载能力选对了,后面这类问题会少一大半。

大量TCP连接状态汇聚到单个服务端口的可视化示意图

一、TIME_WAIT 到底是什么,为什么它会「占着端口不放」

很多人一听 TIME_WAIT 就以为是「没关掉的连接」,其实方向反了。TIME_WAIT 的准确含义是:这条 TCP 连接已经彻底结束了,但它对应的那个四元组(源 IP、源端口、目的 IP、目的端口)还需要在内核里保留一小段时间(Linux 默认 60 秒),用来处理最后那个 ACK 丢失的情况如果对端的最后一个 ACK 没收到,对端还会重发 FIN,这时候内核必须能认出「这个四元组我见过」,才能回一个 ACK 而不是报错。

关键在于:TIME_WAIT 占用的是「本地端口 + 本地地址」这一侧的端口号。内核发起连接时,会从临时端口范围里随机取一个(默认 32768~60999,约 2.8 万个)。这些端口一旦被分出去,只要 TIME_WAIT 还在,内核就不会把它们分配给新的连接。所以你会看到一个很典型的现象:ss -lntp 里监听端口就那么几个,但 ss -s 里的 TIME_WAIT 计数能到几万。

更要命的是,端口耗尽之后的表现具有迷惑性。服务端 listen socket 明明还在,客户端连过来却被拒,日志里报的是「Address already in use」,很多人第一反应是「端口被别的进程占用了」,去查进程列表自然什么都查不到。我那天也是先怀疑 Nginx 抢了端口,绕了半小时弯路。

运维工程师在终端前排查服务器TCP连接状态的工作场景

二、我是怎么在一分钟内确认是 TIME_WAIT 的

三步,不需要装任何工具。

第一步,看总量:ss -s 会打印一行统计,其中 TIME-WAIT 那一列的数值如果到了几千以上,基本就有数了。我那次打出来是 21743。

第二步,看分布:ss -tan state time-wait | awk '{print $4}' | sort | uniq -c | sort -rn | head,按本地端口排序计数。如果某几个本地端口上密密麻麻全是 TIME_WAIT,说明是某个本地端口在疯狂地往外建短连接(典型就是回调、或者客户端在换源端口重试)。

第三步,看是不是还在涨:隔 10 秒跑两次 ss -s 对比。如果连接已经打满,TIME_WAIT 只会越来越多不减因为它只靠超时自然消亡,而这 60 秒里新的连接还在源源不断地进来。

顺带说一句,这三步只读系统状态、不动任何参数,属于无风险排查,可以放心在任何生产机上跑。

海量客户端连接汇聚到云端服务器的高并发概念图

三、调参:三个内核参数,分别管什么

确认是 TIME_WAIT 之后,就该动手了。Linux 上跟这个相关的就三个参数,实际用得上的主要是前两个。

net.ipv4.tcp_tw_recycle:早期用来快速回收 TIME_WAIT,但实现有 bug,对 NAT 后面的客户端(也就是公司出口、手机网络)会误丢包。新内核已经默认关闭并且标记废弃,不要再开了,很多老教程还在教人开这个,属于过时信息。

net.ipv4.tcp_tw_reuse:允许在内核认为安全的前提下,复用处于 TIME_WAIT 状态的本地端口去发起新的主动连接。这个是真正有用的,默认就是 1(较新的内核)。它只影响「主动发起连接」的一侧,也就是客户端侧和服务端回调侧。服务端的入站连接不受它影响这一点很关键,很多人是靠改这个参数"修好"了问题,其实那只是把客户端侧的瓶颈挪开了。

net.ipv4.tcp_max_tw_buckets:系统最多保留多少条 TIME_WAIT,默认大约 262144。调小它能加快回收、释放内存,但代价是内核会强制杀掉超出部分,超出上限的连接在收尾时可能被 reset,客户端会看到「连接被重置」这种莫名其妙的结果。我的做法是不动这个值,内存占用完全在可控范围。

还有一个 net.ipv4.tcp_fin_timeout,控制 FIN_WAIT_2 的超时,默认 60 秒。它跟 TIME_WAIT 不是一回事,别混用。真要调,一般 15~30 秒是常见取值。

改完记得 sysctl -p 生效,并且确认写进 /etc/sysctl.conf,否则重启就没了。这一步我用 Ansible 统一推到了所有节点上顺带说一句,十几台机器靠人肉 SSH 改参数太容易漏,后来干脆把整个 sysctl 片段交给配置管理管,每次变更都有 diff 可查。

四、真正的根治办法:别用短连接

调参数是止血,不是治本。短连接本身有个硬伤:每条连接都要三次握手 + 四次挥手,内核和网卡都要为它付成本。QPS 一高,TIME_WAIT 堆积、端口紧张、连接跟踪表(conntrack)打满这些都会一起来。

我的处理路径是这样的:

第一层,改成连接复用。同一个上游后端,用长连接而不是每次新建。HTTP/1.1 本身有 keep-alive,Nginx 配 keepalive 64、keepalive_timeout 60s,再配上 keepalive_requests 1000,复用次数会被明显拉高。这一层改完,TIME_WAIT 数量直接掉了一个数量级。

第二层,应用侧用连接池。如果后端是数据库或者 Redis 类的服务,一定要在客户端做池化,池子大小按「并发数 / 单请求耗时」去算,而不是拍脑袋给 500。我见过连接池配太大把数据库打挂的案例,这里面的权衡和我的踩坑过程在另一篇文章里详细写过。

第三层,客户端侧检查超时设置。HTTP 客户端的读超时和连接超时如果太长,请求堆积会连带把连接数放大;超时没设或者设成 0(不超时),一个卡住的后端能把连接池拖满。我那次把客户端读超时从 30 秒降到 5 秒,无效连接被快速回收,效果立竿见影。

五、什么时候不该碰 TIME_WAIT

有几个场景我建议别动:

一是机器内存很紧张的时候,TIME_WAIT 本身是纯内存开销,大量堆积确实会吃内存,这时候应该调大 tcp_max_tw_buckets 之外的手段比如直接上连接池和长连接,而不是去调小超时;二是你的业务本身就是一次性的、不会复用的短连接任务(比如离线跑批),那 TIME_WAIT 堆积是正常现象,强行复用反而可能引起数据错乱;三是已经上了 HAProxy、Nginx 做四层/七层转发的,前面已经把连接复用的活干掉了,内核层的 TIME_WAIT 本来就不多。

另外提一句监控:现在我给每台机器都加了 conntrack 使用率和 TIME_WAIT 计数的告警,阈值大概是 TIME_WAIT 超过 5000 或者本地端口使用率超过 60%。等真的爆了再查,故障恢复时间能从半小时压到三分钟。

回到那次故障,最终结论是:一条短连接跑满 2.8 万个临时端口的机器,在 QPS 3000 的业务场景下本来就不该用。我把 keep-alive 打开、连接池配好、客户端超时收紧,TIME_WAIT 从 2 万降到 2000 出头,端口再没满过。整个过程花了一天,改配置只花了十分钟,剩下的时间都在找那根被忽略的 HTTP 头。

    相关评论

    阅读本文后您有什么感想? 已有人给出评价!

    • 8 喜欢喜欢
    • 3 顶顶
    • 1 难过难过
    • 5 囧囧
    • 3 围观围观
    • 2 无聊无聊

    发表评论

    昵称:
    表情: 高兴 可 汗 我不要 害羞 好 下下下 送花 屎 亲亲