周四下午两点多,客户在群里喊:你们的服务器是不是不行了,页面打开要转十几秒,有的直接超时。

我登录上去,第一感觉是机器还活着,负载不高,CPU 和内存都正常。但页面就是慢。我 ss -s 看了一眼连接状态,瞬间明白了:

TIME-WAIT 20437
ESTAB 312

两万多个 TIME_WAIT,全是 80 端口出去连后端的。客户那套系统是个网关,前端请求进来,它要往后端转发,每转发一次建一条连接,请求一多,连接就堆积。

TIME_WAIT 这东西,教科书上说是 TCP 正常关闭后的等待状态,等 2MSL 才释放,大概 60 秒。平时没事,但架不住量大。两万多个 TIME_WAIT 占着端口和内存,新连接来了,内核得一个个去查有没有冲突,查得越多越慢,最后整个机器就跟卡死一样。

我先让业务侧把请求压一压,然后开始查代码层面有没有问题。查了一圈发现,网关程序用的是短连接——每次请求都新建连接,用完就关。这种写法在高并发下必炸,正确做法是连接池,复用连接,别每次都建。

但临时改代码来不及。我先把内核参数调了:

net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30

tcp_tw_reuse 允许新连接复用 TIME_WAIT 的端口,tcp_fin_timeout 从 60 降到 30,TIME_WAIT 释放快一倍。sysctl -p 生效后,过了十几分钟,ss -s 再看:

TIME-WAIT 4213
ESTAB 298

从两万降到四千,页面恢复流畅。

但这只是缓兵之计。内核参数调得再狠,也扛不住"每个请求都新建连接"的写法。我让开发把短连接改成连接池,改成之后,TIME_WAIT 基本稳定在几百个,tcp_tw_reuse 我都想关掉了——正常健康的系统,根本不需要靠这个续命。

后来我跟开发复盘的时候说,TIME_WAIT 多不可怕,可怕的是不知道它为什么多。你调参数之前,得先搞清楚连接是从哪来的、为什么关不掉。不然你今天调大这个,明天那个又炸。

哦对,还有个小插曲。当时我调完参数,客户那边反馈好了,但过了半小时又有点慢。我一看,TIME_WAIT 又涨回来了。后来发现是有人把新代码偷偷发上去了,还是短连接。我差点没忍住骂人。

这个行业就是这样,你永远不知道下一个坑在哪,但坑通常都埋在你以为没事的地方。