服务端出现大量 CLOSE_WAIT 的排查思路

腾讯云标志配图

问题:服务端出现大量 CLOSE_WAIT 状态,什么原因?有什么危害?应该怎么办?

CLOSE_WAIT 是什么时候出现的

回答这个问题,首先需要知道 CLOSE_WAIT 状态是什么时候出现的?还是回到经典的四次挥手过程,这个过程得不断看,直到印在脑海里(图解可以看《三次握手与四次挥手》)。

可以看到,CLOSE_WAIT 状态是在主动方(这道题里面就是客户端)发起了第一次挥手,发送 FIN 报文到被动方(这里指服务端)。在被动方收到 FIN 报文并且回复 ACK 后,被动方状态变为了 CLOSE_WAIT(这里埋个问题:是收到 FIN 之后就变为 CLOSE_WAIT,还是回复 ACK 之后才变为?从 Linux 源码看,内核在收包流程 tcp_data_queue() 里处理按序到达的 FIN 时,就会调用 tcp_fin() 把状态直接置为 TCP_CLOSE_WAIT,而回复 ACK 只是同一个收包流程里紧随其后的动作。也就是说,状态切换发生在处理 FIN 报文的那一刻,并不是等 ACK 发出去之后才切,这两件事是在同一个报文处理流程里先后完成的)。

在被动方发送自己的 FIN 后,CLOSE_WAIT 状态才变为了 LAST_ACK。顺带一提,处于 CLOSE_WAIT 的被动方并非「半死不活」:它依然能正常收报文、回 ACK,细节可以看《CLOSE_WAIT 状态还能收到报文吗》。

现在服务端出现了大量 CLOSE_WAIT 状态的 TCP 连接,那么从 TCP 层面看就是:没有发送 FIN 报文,也就是没有调用 close() 函数。

所以如果服务端出现大量的 CLOSE_WAIT 状态,问题就出在服务端,要排查服务端的代码。为什么没有调用 close() 函数,就和每个程序的业务相关了,可能导致出现这个状况的原因有:

  1. 服务端线程在某个地方阻塞了,没法调用 close()。
  2. 调用 close() 前有大量的耗时操作,导致短时间内处于 CLOSE_WAIT 状态的连接数增加。
  3. 没有正确地监听并处理连接关闭的事件,忽略了应答客户端的关闭请求。

用命令确认

在 Linux 系统中,可以使用 netstat 或 ss 命令来查询处于 CLOSE_WAIT 状态的 TCP 连接数量。

netstat -n | awk '/^tcp/ && /CLOSE_WAIT/ {print $5}' | cut -d: -f1 | sort | uniq -c

这条命令会做以下几件事:

  • 使用 netstat -n 列出所有连接,-n 表示不进行域名解析,以便快速处理。
  • awk 先筛出 tcp 开头的行,再筛选出状态为 CLOSE_WAIT 的行。
  • cut 用来从第 5 列(对端的「IP:端口」)中提取 IP 部分。
  • sort 和 uniq -c 用来计数每个 IP 有多少个 CLOSE_WAIT 状态的连接。
ss -t state close-wait | wc -l

这将会输出处于 CLOSE_WAIT 状态的连接总数。注意两个细节:ss 的状态名是 close-wait(带连字符);输出第一行是表头,所以连接总数是 wc -l 的结果减 1,也可以改用 ss -tH state close-wait | wc -l 直接去掉表头。

大量 CLOSE_WAIT 的危害

  • 资源泄漏:每个 CLOSE_WAIT 状态的连接都会占用一个文件描述符(file descriptor, fd)。系统资源有限,大量 CLOSE_WAIT 连接会迅速消耗可用的文件描述符,可能导致新的连接无法建立,出现服务拒绝的问题。
  • 性能下降:随着 CLOSE_WAIT 状态的连接积累,系统需要维护这些连接的状态,消耗内存和 CPU 资源,从而影响整体服务性能。
  • 稳定性风险:在极端情况下,资源耗尽可能导致服务崩溃或重启,影响服务的稳定性和可靠性。

应该怎么办

  1. 检查被动方的代码,找到没有调用 close() 函数的原因。
  2. 在业务层增加自己的心跳检查机制,如果在连续几个心跳收不到后,对这条连接进行 close() 处理。
  3. 记录这条连接上次收到消息的时间,如果大于一定时间没有收到下一条则进行 close() 处理。
  4. TCP 自带的 Keepalive,可以修改时间缩短检测时间(关于 Keepalive 的细节可以看《TCPKeepalive》)。

   转载规则


《服务端出现大量 CLOSE_WAIT 的排查思路》 由 吴杭沉 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
TCP 重传、滑动窗口、流量控制与拥塞控制 TCP 重传、滑动窗口、流量控制与拥塞控制
为了实现可靠性传输,需要考虑很多事情,例如数据的破坏、丢包、重复以及分片顺序混乱等问题。如不能解决这些问题,也就无从谈起可靠传输。那么,TCP 是通过序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现可靠性传输的。今天,将重点介绍
2021-11-09
下一篇 
CLOSE_WAIT 状态还能收到报文吗? CLOSE_WAIT 状态还能收到报文吗?
问题:TCP 在 CLOSE_WAIT 状态还能收到报文吗? 先说答案首先给出答案:能收到报文,而且还能正确地回复 ACK。 从四次挥手看先从四次挥手说起(不熟悉的话,可以先看《三次握手与四次挥手》),这道面试题比较简单: 我们都知道 T
2021-10-26
  目录