Windows Server上TCP连接CLOSE_WAIT状态过多异常的排查心得

背景: Windows Server 2008 R2上运行着一个服务A,它会从某地接收数据和图片,接受到的图片通过HTTP请求的形式提交给Tomcat上的应用B,应用B负责将图片存储到HBase中。由于服务A接收到的图片数量较多,并发较高。每次接受到一张图片,就创建一个线程向应用B提交数据,提交完成后,线程结束。
表现出来的问题: 表现出来的问题当中,如下是最直接的三个问题:

  1. 在后台观察数据,可以看到有图片丢失的现象。
  2. 观察服务A的运行日志,发现日志中出现Connection Error的情况,表明是无法连接服务B。
  3. 通过netstat命令在Windows Server 2008 R2中查看,发现了大量的连接,这些连接连到了Tomcat的8080端口,但是这些连接的状态都是CLOSE_WAIT状态。
还有一些衍生出来的问题,这些都是当时发现,但是不知道怎么回事,后来才通过理论推导归纳过来的:
  1. 当出现问题的时候,我通过浏览器访问应用B的连接,浏览器也显示无法连接。但是我在其他机器上连接应用B的时候,可以正常访问。
  2. Windows Server上边安装了Nevicat,用来管理远端的一个数据库,但是Nevicat无法连接到数据库上去。测试连接时会报网络连接的错误。
  3. Windows Server上运行着一个Java应用,该应用会连接远程数据库,观察Java应用的日志,数据库连接爆出java.net.SocketException: No buffer space available (maximum connections reached ?)的错误。
排查过程: 问题出现的很诡异,图片丢失几分钟后,又不丢失图片了。当出现问题的时候,我重启服务A后,图片丢失的问题马上就解决了,但是运行一段时间之后,问题又会出现。
根据我的经验,先查看了一下远程连接8080端口的TCP连接数,可以到6万,直逼65535的极限。我感觉到是由于并发过高,端口占用过多,导致端口资源不够的问题。
针对这个假设我先在网上查了查Windows Server下端口回收的超时时间,给出的方法是修改注册表,增加KeepAliveTime等参数。还有说在Windows Server 上存在这个bug,需要安装两个补丁。我按照教程修改了注册表,安装了补丁,重启了服务器,发现效果依旧。
根据我的经验,我还检查了一下Tomcat所在的服务器上的8080端口连接情况,和服务器所有端口的使用数量。发现所有端口的使用数量和其他服务器上的使用数量相近,不存在该服务器端口占用过量的问题。观察8080端口的连接数量,也就在10~20之间,不存在由于连接过多造成Tomcat拒绝响应的问题。
在整个配查过程中,我心中一直存在一个疑问,我之前一直遇到的是TCP连接的TIME_WAIT状态,很少见到CLOSE_WAIT状态。这个疑问就让我比较关注CLOSE_WAIT这个名词。
后来我推测,可能是由于前端采集数量比较多,导致服务A的并发量比较高,我打算启用另一台Windows Server服务器来为服务A做负载均衡。
由于工程方面的原因,没有马上启用另一台服务器,我继续查找资料看能够解决这个问题。
曙光乍现: 我在查询资料的过程中,有几篇文章分别提到了TCP关闭过程的四次握手过程、Tomcat的keepalive机制,keepalive超时机制。
我在Windows Server上使用Wireshark抓包分析一段时间的网络流量。跟踪TCP流,找到服务A和应用B交互的TCP流,我发现在TCP流的末尾,没有Fin的过程。我还发现了只存在两个包的TCP流,第一个包是FIN包,第二个是ACK包。这个现象当时没有引起我的注意,在最后复盘的时候,才回忆起了这个现象,并解释了这个现象。
理论推导: 【Windows Server上TCP连接CLOSE_WAIT状态过多异常的排查心得】如下图所示,这是TCP断开连接时的标准的四次挥手的流程。
Windows Server上TCP连接CLOSE_WAIT状态过多异常的排查心得
文章图片

在我们的场景中,服务A是Client,应用B的Tomcat是Server。由于服务A应该是主动发起请求,主动关闭请求。所以按照服务A的角色,它的TCP连接不应该转入CLOSE_WAIT的状态。
然而事实确实如此,服务A端的TCP连接出现了CLOSE_WAIT状态。
那么做出一个假设,Tomcat端主动断开的连接,那么Tomcat主动断开连接的触发机会只有一个,那就是keepalive的超时机制。
后来我又进一步详细查阅了Tomcat的keepalive机制的资料,发现Tomcat默认启用了keepalive机制,并且默认存在一个超时设置。符合假设的所有前提。
情景复盘: 当服务A启动一个线程,向Tomcat上的应用B发起请求后,发送相应的数据,由于在协议沟通过程中存在keepalive的机制,所以在数据发送完毕后,该连接并没有断开。此时由于数据发送任务完成,该进程也就销毁了,但是TCP连接依然存在。当Tomcat维持了一段时间的keepalive状态后,由于没有数据交互,触发了keepalive超时机制,Tomcat主动向Windows Server上发起了Fin包,请求终止连接,Windows Server立即响应Ack包,该TCP连接进入了CLOSE_WAIT的状态,前两次挥手完成。但是由于进程已经销毁,无法进行后两次的握手,所以这些TCP连接一直处在CLOSE_WAIT状态,一直累积,占用的TCP端口,直到将系统的端口资源耗光。
Tomcat发送Fin包,并得到ACK回应后,TCP连接进入FIN-WAIT-2状态,再等待2个MSL(Maximum Segment Lifetime,即报文最大生存时间)之后,进入CLOSED状态,所以Tomcat的服务器上没有出现TCP连接累积的情况。
Windows Server上TCP连接CLOSE_WAIT状态过多异常的排查心得
文章图片

解决方案: 我推测将Tomcat的keepalive机制关闭,服务A和Tomcat在协议协商过程中不使用keepalive,服务A在数据发送结束后应该会关闭连接。
Tomcat关闭长连接过程的配置为,修改server.xml文件:

其中设置maxKeepAliveRequests=“1”,也就是禁用了KeepAlive的功能。
修改了Tomcat的配置之后,重启Tomcat,重启服务A。
隔一段时间观察一次,发现连接处在了TIME_WAIT状态,仅有1~2个处在CLOSE_TIME状态,Windows Server上的总体连接数的数量也降下来了。
问题圆满解决。
后记: 翻回头去想,CLOSE_WAIT状态过多、TCP连接端口占用过多、其他应用的TCP连接也无法使用、Wireshark抓包出现的特殊形态的包等现象都能够解释的通了。
排查的问题的过程还比较曲折,修改过注册表参数、打过系统补丁,甚至想过去负载均衡,都非正解,但是排查问题就是这样,真正的解决方法是千呼万唤始出来。

    推荐阅读