HttpClient连接池耗尽引发雪崩问题

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HttpClient连接池耗尽引发雪崩问题相关的知识,希望对你有一定的参考价值。

参考技术A 多个HTTP接口共用一个HttpClient连接池,有商品HTTP服务,库存HTTP服务还有其他的HTTP服务,由于并发流量过大,其中一个接口系统发生宕机,导致请求到该问题接口的所有HttpClient连接都访问超时,直到访问超时时,才释放HttpClient连接,导致连接池瞬间被耗尽,发生雪崩效应,影响了正常HTTP请求的调用,因为一个问题接口影响了整个系统的调用。

在获取HttpClient连接之前,维护一个依赖HTTP接口的调用开关状态,首先判断下目标HTTP接口的服务状态,如果状态可用,在获取连接执行调用,如果不可用状态,直接执行降级逻辑;
每次执行HTTP接口完成后,根据接口返回状态在同步更新依赖HTTP接口开关状态,根据阈值判断开关是否需要打开。

服务器HttpClient连接池耗尽问题排查与解决

出品|51Testing软件测试网


服务器HttpClient连接池耗尽问题排查与解决

问题情景


服务器重新部署应用后,运行一段时间后,某一微服务开始出现错误提示:

连接超时:服务忙,导致相关功能无法正常使用。


服务器HttpClient连接池耗尽问题排查与解决

问题排查



#01

日志查看


首先下载对应服务的日志查看报错内容,日志中出现了很多org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool 的异常信息。


可以看出日志中的错误定位在HttpClient连接池,连接超时可能是连接池连接耗尽,没有可用连接导致无法正常获取连接提供服务。



#02

初步猜想


HttpClient连接池中为了防止连接被长期占用会设置超时时间,如果超时时间设置的不合理,比较长,而服务中的某些连接请求时间又很长的时候,会导致连接被长期占用不被释放,最终导致HttpClient连接池耗尽。


如果初步猜想成立的话那么其他提供服务的机器应该也会出现这个问题,但是根据其他机器的日志分析来看,它们并没有出现连接池耗尽的情况,问题只出现在单个机器上。



#03

定点分析


通过日志分析出问题出现在单个机器上,我们就登录该服务器查看服务器中连接池中各个连接的状态。


可以通过 netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}' 命令对各种状态的连接分组查看,结果如下图1所示。


可以看到有很多处于CLOSE_WAIT状态的连接,数据与我们在项目中配置的连接池最大数量一致,因此可以判断是有大量未关闭的连接占满了连接池,导致连接池耗尽无法提供正常连接。

服务器HttpClient连接池耗尽问题排查与解决

图 1 连接状态分组结果


提到CLOSE_WAIT状态,我们就需要知道TCP的状态转移是如何进行的。通过下图我们可以分析得到,在被动关闭连接情况下,在已经接收到FIN,但是还没有发送自己的FIN的时刻,连接处于CLOSE_WAIT状态。


通常来讲,CLOSE_WAIT状态的持续时间应该很短,正如SYN_RCVD状态。但是在一些特殊情况下,就会出现连接长时间处于CLOSE_WAIT状态的情况。


出现大量close_wait的现象,主要原因是某种情况下对方关闭了socket链接,但是我方忙于读或者写,没有关闭连接。

服务器HttpClient连接池耗尽问题排查与解决

图2 TCP状态转移图


服务器HttpClient连接池耗尽问题排查与解决

问题定位


分析出具体的连接池耗尽原因后我们需要找到对应的代码。根据日志中出现的连接超时信息可以找到抛出该异常的错误代码位置,代码如下:

public void getResources(String url) { LogUtils.info("Get URL={}", url); CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpGet = new HttpGet(url);
try { CloseableHttpResponse response1 = httpclient.execute(httpGet); HttpEntity entity1 = response1.getEntity(); // 一些业务操作 EntityUtils.consume(entity1); } catch (Exception e) { LogUtils.error("Get error", e); }}

(左右滑动查看完整代码)


可以看到代码中对response做完操作之后并没有进行关闭,导致数据流中有一个结束符EOF一直没有被接收,从而连接一直被占用。


服务器HttpClient连接池耗尽问题排查与解决

修复问题


问题定位后,我们就要修复问题,对于返回回来的流我们获取数据做完操作之后应该及时关闭,所以按照这个思路进行了改动。

public void getResources(String url) { LogUtils.info("Get URL={}", url); CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpGet = new HttpGet(url); CloseableHttpResponse response1 = httpclient.execute(httpGet);
try { HttpEntity entity1 = response1.getEntity(); // 一些业务操作 EntityUtils.consume(entity1); } finally { response1.close(); }}

(左右滑动查看完整代码)


服务器HttpClient连接池耗尽问题排查与解决

总结


出现CLOSE_WAIT的根本原因是应用端未关闭一个已经被对端关闭的连接导致,在出现该问题时,我们可以通过netstat等命令查看tcp的对端和端口,从而定位出现问题的地方并进行修改。还有就是平时进行流处理操作时需要及时关闭流,释放资源,避免后续资源死锁。


服务器HttpClient连接池耗尽问题排查与解决

End

服务器HttpClient连接池耗尽问题排查与解决

推荐阅读

点击阅读☞

点击阅读☞

点击阅读☞

点击阅读☞

点击阅读☞

“阅读原文”一起来充电吧!

以上是关于HttpClient连接池耗尽引发雪崩问题的主要内容,如果未能解决你的问题,请参考以下文章

内部分享:一次HttpClient连接池引发的雪崩案例复盘....

糟糕!HttpClient 连接池设置引发的一次雪崩!

记一次 HttpClient 连接池参数引发的雪崩问题的定位分析解决过程!

服务器HttpClient连接池耗尽问题排查与解决

HttpClient 设置不当引发的一次雪崩

HttpClient 设置不当引发的一次雪崩