大量 CLOSE_WAIT 状态的 TCP 连接,引起服务器宕机问题

问题描述

FineReport设计器切换工作目录到远程服务器。服务器出现大量 CLOSE_WAIT 状态的 TCP 连接(多的有 40 个)。如果多开几个客户端设计器,服务器的 tcp 连接数迅速上升到几百个,严重消耗系统资源,引起宕机。本地不能复现,只能在客户的服务器上复现。而且以前 http 方式是正常的,启用 https 后,才出现这个问题。

关键词:远程设计、CLOSE_WAIT、特定服务器、https

debug 过程

初步判断——day1

  1. 怀疑客户端的 https 连接出现异常后直接退出,没有关闭连接导致。本地修改代码进行模拟测试,没有这个问题
  2. 用 TeamViewer 远程连接客户那边的客户端和服务器,复现问题
  3. 本地设计器远程连接服务器,没有复现问题
  4. 把客户那边的客户端工程拷到本地,再次测试,没有复现问题
  5. 现在的现象是,内网有问题,外网正常。网络环境的问题

驳回

客户那边网络排查的结果出来了,说是外网用的是七层转发,那些无意义的连接是被负载均衡的网络设备承担了,所以没到tomcat那里,现在把外网也改成四层转发后,外网的设计器也会导致服务器宕机了

继续跟进

day2

  1. 客户那边配好了网络环境,远程连过去,复现问题
  2. 目前外网暂时改为4层转发,用本地的设计器连客户的服务器,也能复现问题
  3. 在 tomcat 的 server.xml 中配置 keepAliveTimeout 为 20s,测试,没有任何效果。看来跟这个配置无关
  4. 在 HttpClient 的 connect 和 release 中加 log,发现 connect 和 release 的次数是相等的。说明客户端不存在连接未释放的问题
  5. 继续查找相关资料,怀疑跟 HttpClient 用法有关。搜“HttpClient CLOSE_WAIT”,出来大量相关内容。但说的都是 apache 包里的 HttpClient。
  6. 尝试先调用 client.release 后调用 in.close/out.close,没有发现变化。
  7. 注释所有的 client.release 和 in.close/out.close,发现服务器有少量 CLOSE_WAIT,大量 TIME_WAIT,一段时间后,全部变为 CLOSE_WAIT
  8. 尝试修改 header,设置 Connection : close(conn.setRequestProperty("Connection", "close");)。测试无效。
  9. 当服务器出现大量 CLOSE_WAIT 时,查看客户端的网络状态,有大量的 FIN_WAIT_2。跟从网上查到的原理图一致

day3

  1. 没有进展,继续查阅 CLOSE_WAIT 相关资料,加深理解
  2. HttpURLConnection 的错误用法、tomcat 的 bug,都可能引起这个问题。排查 HttpURLConnection 用法,没发现可疑情况。客户那边是 tomcat9(网上的bug是tomcat8的http://www.cnblogs.com/saaav/p/6258831.html),应该不会有问题
  3. 可以尝试修改服务器的 KeepAliveTime 配置,http://blog.csdn.net/chenjianqi0502/article/details/78490295。这样会影响全局,尽量不要用吧,而且远程调试起来麻烦,先考虑其他方案
  4. 再次回顾 CLOSE_WAIT 原理。肯定是服务端没有关闭 socket。而我们的服务端代码里,并没有关闭 socket 的接口,socket 是由 tomcat 容器处理的。怀疑 tomcat,下面开始验证:
  5. 把客户的 tomcat 拷贝到本地(项目工程超过25G,没有拷贝),搭建本地环境,运行不起来。1、相关环境变量有问题;2、启动过程中有其他报错,导致无法启动(在客户的服务器上,也有这个报错,但是可以启动)
  6. 直接在服务器上验证。下载新的 tomcat9,把原来 tomcat 中的 conf/ 和 WebReport/ 拷贝到新的 tomcat 中。启动 tomcat,测试,CLOSE_WAIT 消失

结论:tomcat 的问题,更新之后就好了。

 

知识点记录

调试技巧——打印堆栈

不方便断点调试的时候,很有用

new Exception().printStackTrace()

查看网络状态

netstat -an | grep '443'

参考:Linux netstat命令

CLOSE_WAIT 连接太多的危害

由于端口数量限制,同一时间只有有限数量的 socket 连接可以建立。如果太多的 socket 处于 CLOSE_WAIT 状态,一直占用大量端口,将很难再建立新连接。服务器宕机。

CLOSE_WAIT 原理 / TCP “四次挥手”

TCP 连接是全双工,我关了你的连接,并不等于你关了我的连接。

图解TCP协议中的三次握手和四次挥手
懵逼的HTTP、Socket与TCP

长连接与短连接

短连接:

  • 建立连接——数据传输——关闭连接...建立连接——数据传输——关闭连接

长连接:

  • 建立连接——数据传输...(保持连接)...数据传输——关闭连接

从 HTTP/1.1 起,默认使用长连接,如果要用短连接,需要在请求头部显示设置 Connection: close。

TCP长连接和短连接