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

debug 过程
初步判断——day1
- 怀疑客户端的 HTTPS 连接出现异常后直接退出,没有关闭连接导致。本地修改代码进行模拟测试,没有这个问题
- 用 TeamViewer 远程连接客户那边的客户端和服务器,复现问题
- 本地设计器远程连接服务器,没有复现问题
- 把客户那边的客户端工程拷到本地,再次测试,没有复现问题
- 现在的现象是,内网有问题,外网正常。网络环境的问题
驳回:客户那边网络排查的结果出来了,说是外网用的是七层转发,那些无意义的连接是被负载均衡的网络设备承担了,所以没到 Tomcat 那里,现在把外网也改成四层转发后,外网的设计器也会导致服务器宕机了。
继续跟进 day2
- 客户那边配好了网络环境,远程连过去,复现问题
- 目前外网暂时改为四层转发,用本地的设计器连客户的服务器,也能复现问题
- 在 Tomcat 的 server.xml 中配置 keepAliveTimeout 为 20s,测试,没有任何效果。看来跟这个配置无关
- 在 HttpClient 的 connect 和 release 中加 log,发现 connect 和 release 的次数是相等的。说明客户端不存在连接未释放的问题
- 继续查找相关资料,怀疑跟 HttpClient 用法有关。搜“HttpClient CLOSE_WAIT”,出来大量相关内容。但说的都是 Apache 包里的 HttpClient。
- 尝试先调用 client.release 后调用 in.close/out.close,没有发现变化。
- 注释所有的 client.release 和 in.close/out.close,发现服务器有少量 CLOSE_WAIT,大量 TIME_WAIT,一段时间后,全部变为 CLOSE_WAIT
- 尝试修改 header,设置 Connection: close(conn.setRequestProperty("Connection", "close");)。测试无效。
- 当服务器出现大量 CLOSE_WAIT 时,查看客户端的网络状态,有大量的 FIN_WAIT_2。跟从网上查到的原理图一致
day3
- 没有进展,继续查阅 CLOSE_WAIT 相关资料,加深理解
- HttpURLConnection 的错误用法、Tomcat 的 bug,都可能引起这个问题。排查 HttpURLConnection 用法,没发现可疑情况。客户那边是 Tomcat 9(网上的 bug 是 Tomcat 8 的 http://www.cnblogs.com/saaav/p/6258831.html),应该不会有问题
- 可以尝试修改服务器的 KeepAliveTime 配置,http://blog.csdn.net/chenjianqi0502/article/details/78490295。这样会影响全局,尽量不要用吧,而且远程调试起来麻烦,先考虑其他方案
- 再次回顾 CLOSE_WAIT 原理。肯定是服务端没有关闭 socket。而我们的服务端代码里,并没有关闭 socket 的接口,socket 是由 Tomcat 容器处理的。怀疑 Tomcat,下面开始验证:
- 把客户的 Tomcat 拷贝到本地(项目工程超过25G,没有拷贝),搭建本地环境,运行不起来。1、相关环境变量有问题;2、启动过程中有其他报错,导致无法启动(在客户的服务器上,也有这个报错,但是可以启动)
- 直接在服务器上验证。下载新的 Tomcat 9,把原来 Tomcat 中的 conf/ 和 WebReport/ 拷贝到新的 Tomcat 中。启动 Tomcat,测试,CLOSE_WAIT 消失
结论:Tomcat 的问题,更新之后就好了。
知识点记录
调试技巧——打印堆栈
不方便断点调试的时候,很有用:new Exception().printStackTrace()
查看网络状态
netstat -an | grep '443'
CLOSE_WAIT 连接太多的危害
由于端口数量限制,同一时间只有有限数量的 socket 连接可以建立。如果太多的 socket 处于 CLOSE_WAIT 状态,一直占用大量端口,将很难再建立新连接。服务器宕机。
CLOSE_WAIT 原理 / TCP “四次挥手”

TCP 连接是全双工,我关了你的连接,并不等于你关了我的连接。
长连接与短连接
短连接:建立连接——数据传输——关闭连接...建立连接——数据传输——关闭连接
长连接:建立连接——数据传输...(保持连接)...数据传输——关闭连接
从 HTTP/1.1 起,默认使用长连接,如果要用短连接,需要在请求头部显示设置 Connection: close。
