现象
生产环境和测试环境都发现有个外围应用通过搜索服务调用搜索引擎时,偶尔会出现大量的访问超时的问题,通过如下方式进行分析排查:
l 首先是拿到搜索服务的JavaCore,发现其堵在HttpClient的发送上面,被堵的连接有数百个,原因是不能够从连接池中获取到连接;
l 首先想到的就是连接池没有释放,检查代码,也确实存在着一些调用没有释放连接,特别是在异常的情况下,针对这一部分代码进行修复后,可是一段时间之后还是出现了访问超时的问题;
l 考虑到这个外围应用的访问现出问题的时候,其它的外围应用调用搜索服务是没有问题的,因此确定当前搜索服务还没有挂;
l 不同的外围应用可能调用的后台搜索引擎是不一样的,难道是该外围应用对应的搜索引擎出现了问题?不过经过对该搜索引擎进行分析,该搜索引擎本身是正常的,但是有一个奇怪的现象,在外围应用调用搜索服务发生超时时,搜索引擎本身没有接受到任何请求;
l 也就是说经搜索服务的请求都没有提交到该搜索引擎上,难道是搜索服务与该搜索引擎之间的连接有问题?通过网络排查,使用Ping和Telnet进行正向和反向确定,从搜索服务和搜索引擎之间的网络是正常的,且端口也可以正常访问;
l 再回到搜索服务所在的服务器,通过netstat一看,发现有400个CLOSE_WAIT与该搜索引擎相关的连接,这个数字恰好是应用中设置的单个Route所能够连接的最大连接数。
分析到此,问题就明朗了,HttpClient连接池的中创建连接数已经达到了最大数字,不能够创建新的连接了,已经创建的连接都是在等待关闭(CLOSE_WAIT)的状态,没有被放回到可用的连接池中,不能够用于处理新的连接请求,因而所有的请求都是堵在了从连接池中获取连接哪里。
要解决这个问题,首先需要知道CLOSE_WAIT产生的原因,才能够解决该问题,或者减少该问题的发生。
TCP连接关闭时需要四次握手才能够完成,如下图所示:
产生CLOSE_WAIT状态的一方,是属于被动关闭的一方,用简单的话对解释上图(主动关闭方为A,被动关闭方为B):
A发一条FIN(关闭)请求给B,说我要关闭了; B回应一条ACK(确认)请求给A,说我知道了,你关吧,此时B就会进行CLOSE_WAIT状态; B发送一条FIN(关闭)请给A,说我要关闭了; A收到发送一条ACK(确认)消息说,你关闭吧。
|
上面四次握手完成后,双方的连接就都关闭了,但是这里在客户端产生了CLOSE_WAIT现象,首先可以确定的是服务端主动关闭的连接,且客户端没有给服务端发送关闭的请求(第三次握手请求),就会一直处在CLOSE_WAIT的状态,可是客户端为什么不向服务端发送关闭的请求,它当时在忙什么呢,难道应用在关闭前有哪么多事情要做?还有就是为什么服务会主动关掉客户端的这么多连接?
有人说这可能是服务端在调用关闭时,而客户端正在执行RECV(数据接收),这时候有可能服务端发送的FIN包客户端接收出错,就是由TCP代回了一个ACK包,所以客户端就会处在CLOSE_WAIT的状态中,因而建议判断RECV时是否出错,如果出错就主动关闭连接,这样就可以防止没有接收到FIN包。
也有人说这是由于客户端请求服务端时,超时就有可能出现这种情况,我对这种情况做了实验,分别启动了客户端和服务端,在服务端中暴露一个超时的服务接口,在客户端中通过POST的方式调用,然后再通过第三方工具调用客户端去调用服务端的超时接口,测试分别在Linux以及Windows平台进行了测试,可是经过100万个连接超时的请求后,客户端没有出现CLOSE-WAIT的现象,只有服务端才出现了CLOSE-WAIT,并且都会正常的关闭。
我们尝试过优化Linux中TCP连接参数,减少TCP的连接时间以及增加连接的可用性,如下:
sysctl -w net.ipv4.tcp_timestamps=0 sysctl -w net.ipv4.tcp_tw_reuse=1 sysctl -w net.ipv4.tcp_tw_recycle=1 sysctl -w net.ipv4.tcp_fin_timeout=30 sysctl -w net.ipv4.tcp_keepalive_time=1800 sysctl -w net.ipv4.tcp_rmem="4096 87380 8388608" sysctl -w net.ipv4.tcp_wmem="4096 87380 8388608" sysctl -w net.ipv4.tcp_max_syn_backlog=4096 |
也优化了HttpClient的参数,可是客户端还是会出现CLOSE_WAIT的情况,且搜索引擎是使用惠普的Autonomy,闭源的不好入手优化,最后还是通过在客户端实现定时任务定期检查当前连接中状态为leased(拿走但没有返回aviable可用队列中的连接)的连接的数量,检测到该这种连接的数量超过一定数量后,就关闭该连接池,释放所有连接,然后重新初使化该连接池,就可以解决这种问题了,经过测试这种试是可行的。不过曾经考虑到这种方式比较暴力,连可用的连接都给关闭了,本想只关闭那些长久未释放的连接,不过由于连接池没有暴露操作方法,通过反射可以获取到池中的连接,不过由于关联资源较多,操作麻烦,最后没有采用这种方式。