浅谈CLOSE_WAIT
TCP 有很多连接状态,每一个都够聊十块钱儿的,比如我们以前讨论过 TIME_WAIT 和 FIN_WAIT1,最近时不时听人提起 CLOSE_WAIT,感觉有必要梳理一下。
所谓 CLOSE_WAIT,借用某位大牛的话来说应该倒过来叫做 WAIT_CLOSE,也就是说「等待关闭」,如果你还不理解其含义,可以看看 TCP 关闭连接时的图例:
不要被图中的 client 和 server 所迷惑,你只要记住:主动关闭的一方发出 FIN 包,被动关闭的一方响应 ACK 包,此时,被动关闭的一方就进入了 CLOSE_WAIT 状态。如果一切正常,稍后被动关闭的一方也会发出 FIN 包,然后迁移到 LAST_ACK 状态。
通常,CLOSE_WAIT 状态在服务器停留时间很短,如果你发现大量的 CLOSE_WAIT 状态,那么就意味着被动关闭的一方没有及时发出 FIN 包,一般有如下几种可能:
- 程序问题:代码层面遗漏或者死循环之类的,没有 close 相应的 socket 连接。
- 响应太慢:对方已经 timeout 了,本方还忙于耗时逻辑,导致 close 被延后。
- BACKLOG 太大:队列堆积严重,导致多余的请求来不及消费就被关闭了。
如果遭遇了 CLOSE_WAIT 故障,那么需要立刻通过「netstat」或者「ss」命令来判断 CLOSE_WAIT 连接是哪些进程引起的,如果是我们自己写的一些程序,比如用 HttpClient 自定义的蜘蛛,那么八九不离十是忘记了 close 相应的 socket,如果是一些使用广泛的程序,比如 Tomcat 之类的,那么不太可能是它们自身的 BUG,更可能是响应速度太慢或者 BACKLOG 设置过大导致的故障。
此外还有一点需要说明:按照前面图例所示,当被动关闭的一方处于 CLOSE_WAIT 状态时,主动关闭的一方处于 FIN_WAIT2 状态。 那么为什么我们总听说 CLOSE_WAIT 状态过多的故障,但是却相对少听说 FIN_WAIT2 状态过多的故障呢?这是因为 Linux 有一个「tcp_fin_timeout」设置,控制了 FIN_WAIT2 的最大生命周期。坏消息是 CLOSE_WAIT 没有类似的设置,如果不重启进程,那么 CLOSE_WAIT 状态很可能会永远持续下去;好消息是如果连接开启了 keepalive 机制,那么可以通过对应的设置来清理无效连接,不过 keepalive 是治标不治本的方法,还是应该对照前面的解释找到问题的症结才对。
本来想多写点的,但是着急下班回家,就写到这吧,结尾推荐两个案例:
写得都比我好,建议大家仔细阅读。