TIME_WAIT 状态详解

一、问题描述

(一)现象描述

服务器有两个现象,第一是 TCP 连接数不多,不超过 10 个,但是 TIME_WAIT 状态的连接却有 2000。第二是按照以往的性质,在很少用户访问的情况下,服务器的资源几乎没有使用,比如 CPU 不超过 5%。而现在没有什么用户的情况下,CPU 损耗持续在 40% 左右,夜间也不停歇。里面运行着好几个 web 项目,都用 docker 启动的容器分开。

(二)相关知识

TCP 连接有 3 次握手,断开有 4 次挥手(整个流程的图解可以看《三次握手与四次挥手》)。

三次握手中第一次,是主动端发出 SYN 信号给正在 listen 的被动端,然后自己变成了 SYN-SENT 状态;第二次是被动端发送 ACK 确认收到信号和 SYN 信号;第三次是主动端发出 ACK 信号确认已经收到了被动端的 SYN。然后双双进入了 ESTABLISHED 状态,便是已经连接成功。

四次挥手中的第一次就是主动端断开,发送 FIN 信号,变成 FIN-WAIT-1 状态;第二次是被动方收到 FIN 信号,就变成 CLOSE_WAIT 状态,然后赶紧发送 ACK 信号给主动方确认,这时候主动方变为 FIN-WAIT-2 状态;第三次还是被动方等自己的应用断开连接的时候,发送 FIN 信号给主动方,被动方的状态变成 LAST-ACK;第四次是主动方收到被动方的 FIN 信号,然后发送的 ACK 信号,瞬间自己变成 TIME_WAIT 状态,然后等待回收。

就是说,谁有 TIME_WAIT,谁就是主动方。这点可以排除用户频繁关闭网页的可能。意思就是说这都是服务器主动请求断开连接的,而 TIME_WAIT 状态的连接也没有回收。

二、问题推测

(一)网络

网络上面的就是网络不好,或者被攻击。

(二)应用

中间件的参数不对,导致有中间件断开的连接,或者应用程序错误造成的主动断开连接。或者也是应用方面导致消耗资源太多。

三、排查

这个服务器有三个项目,每个项目的架构都是 lanmp(Linux + Apache + Nginx + MySQL + PHP)。问题复杂在于服务器里面好几个项目,每个项目都用了一个反向代理。好的一点是后端是 docker 容器,分开的。

(一)TCP 连接上的 IP

1.先看各容器的 IP。

2.再看连接中本地的 IP。排名第一的是我们本地 IP,6601 是 API 项目的监听端口,从这里可以看出在所有的 TIME_WAIT 状态的 TCP 里面,API 项目的后端是被请求最多的那个,估计反向代理服务器也被请求了很多。

3.接着看连接本地 API 项目的对端 IP。可以看出,请求连接 API 后端的全部都是 nginx 的 IP,这也很容易理解,nginx 反向代理是入口嘛。下面就看看到底是谁对 nginx 发出请求。

4.然后是连接中外地的 IP。对 API 的请求是 600,对 nginx 的请求是 300,说明所有的 TIME_WAIT,一部分是请求 nginx 的,一部分是 nginx 请求 API 的。

5.最后看看到底是谁在请求 web 前端 nginx。原来是 192.168.42.1 这个 IP 的请求。其实 192.168.42.1 这个 IP 是 docker 的虚拟网卡的 IP,作为全部容器的网关,也就是说反正这就是这些容器发出的请求,但是不能确定是哪一个。

综上所述,可以排除网络问题;中间件 apache 的参数没有改,但是对 web 前端 nginx 的请求那么多,可以说明问题不是出在 apache 的请求上面。那就往代码错误方面考虑。

(二)宿主机上的容器

1.应用和网络的关系

可能 TIME_WAIT 的问题就是后端程序乱发请求,apache 是主项目的后端容器,apache-api 就是 api 的后端程序。webserver 占用的 CPU 上升,刚好就说明容器使用的系统资源就是由这种请求引起的。下面用 tail 看看 API 的 access 日志。

实时监测,发现 API 一秒钟被请求 12 次左右,根据业务性质和 docker 的状态显示,可以断定是主项目的循环请求造成的系统资源内耗。而每次请求 API 项目就返回了 access_token,API 返回数据之后就发出断开信号,逻辑和现象很符合,也可以断定 TIME_WAIT 的状态也是这请求引起。而 TIME_WAIT 不是不回收,而是回收了,但不断地生成(协议为什么要这么设计,可以看《为什么需要 TIME_WAIT》)。

四、结论

最终的结论和推测对得上:主项目在循环请求 API 项目,API 每次返回 access_token 之后就主动断开连接,服务端这边于是不断地生成 TIME_WAIT——不是不回收,而是生成速度一直压着回收速度,顺带把 CPU 也耗在了这些内部请求上。

回头看这条排查路径:先通过 TIME_WAIT 连接两端的 IP 弄清楚谁在连谁,再用 access 日志确认请求的频率和来源,最后回到业务代码里找原因,问题就能落到底。


   转载规则


《TIME_WAIT 状态详解》 由 吴杭沉 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
TCP 为什么需要 TIME_WAIT TCP 为什么需要 TIME_WAIT
问题背景文首的状态机图展示了通信双方建立 TCP 连接之后的状态转换过程(图片来源:tcpipguide.com)。 通信双方中,主动发起关闭连接的一端,存在 TIME_WAIT 状态;被动接受关闭连接的一端,会进入 CLOSE_WAIT
2021-10-12
下一篇 
服务端 sleep、客户端持续发数据会发生什么? 服务端 sleep、客户端持续发数据会发生什么?
如果一个服务端和一个客户端建立 TCP 连接后,服务端一直 sleep,然后客户端一直发送数据,会是什么现象? 要回答这个问题,首先我们需要知道 socket 缓冲区和 TCP 发送数据流程。 每个 socket 在创建成功后,内核都会自
2021-09-28
  目录