httpclient使用不当产生大量CLOSE_WAIT的解决过程

案例1:

近日,我们的k8s+rancher的docker环境出现了一个很奇怪的问题,在没有进行任何操作的情况下,我们的web跑着跑着就突然挂了。导致我们自动化用例全部执行失败。于是我和罗仔开始了以下的排查过程。

首先就是在我们的web上查看日志,发现没有任何抛错。我们的自动化用例开启后,一直再向服务端发起请求,但是服务端返回给客户端的响应都是异常,说明服务端压根没有收到请求。于是我们check了Nginx的配置,没有问题,那是为什么呢,这时我们打开netstat命令

netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}' 

发现服务器有大量的CLOSE_WAIT状态的socket,这时候问题原因找到了,是因为大量的CLOSE_WAIT造成tomcat假死,以至于我们的web无法提供服务。

image.png

如下图:

四次挥手.png

根据tcp四次挥手的状态图可以看出来,这个状态是因为客户端主动发起了关闭socket连接的请求,发送了FIN报文给服务端,此时服务端处于了CLOSE_WAIT状态,但是服务器程序自己没有进一步发出ack 信号,于是导致这个资源一直被程序占着。紧接着我们查询了Nginx的日志,发现出现了大量的499状态码。查看Nginx中499的定义是 “client has closed connection”。说明客户端等的不耐烦了,主动关闭了连接。

Nginx日志.png

于是我们发现,这个时间段内,只有我们获取cookie的微服务在不断地发起http请求,于是我们停止该微服务,观察了一段时间,发现close_wait没有再增长,那么问题的源头就找到了。查看我们微服务的代码发现:

 try {
                response = HttpUtil.processJsonPost(client
                        , context
                        , gotestUrl
                        , headers,bodyData);
                String jsonBody = EntityUtils.toString(response.getEntity(), "UTF-8");
                JSONObject responseObj = JSONObject.parseObject(jsonBody);
                if (responseObj.getInteger("code")== 200){
                    logger.log(Level.INFO,"success! ");
                }else {
                    logger.log(Level.WARNING,"something going wrong! ");
                }
            }catch (Exception ex){
                logger.log(Level.SEVERE, ex.getMessage(), ex);
            }

代码中的http连接都没有关闭,所以导致出现大量的close_wait。修改代码:

            try {
                response = HttpUtil.processJsonPost(client
                        , context
                        , gotestUrl
                        , headers,bodyData);
                String jsonBody = EntityUtils.toString(response.getEntity(), "UTF-8");
                JSONObject responseObj = JSONObject.parseObject(jsonBody);
                if (responseObj.getInteger("code")== 200){
                    logger.log(Level.INFO,"success! already send cookie to gotest");
                }else {
                    logger.log(Level.WARNING,"something going wrong! cannot seccessfully send cookie to gotest");
                }
            }catch (Exception ex){
                logger.log(Level.SEVERE, ex.getMessage(), ex);
            }finally {
                if (null != client){
                    try {
                        client.close();
                    }catch (IOException e) {
                        logger.log(Level.SEVERE, e.getMessage(), e);
                    }
                }
                if (null != response) {
                    try {
                        response.close();
                    }catch (IOException e) {
                        logger.log(Level.SEVERE, e.getMessage(), e);
                    }
                }
            }

重启微服务,果然close_wait没有再增长了,问题解决。
经过这次问题的排查,也给了自己一下警示:
1.代码一定要规范,尤其是在申请资源的部分,写之前就需要注释,不要忘记释放资源;
2.排查问题的时候,需要逐步地去分析,一个一个地排除影响因子,才能更快更准确地定位问题。

案例2:

ESTABLISHED 表示正在进行网络连接的数量
TIME_WAIT 表示表示等待系统主动关闭网络连接的数量
CLOSE_WAIT 表示被动等待程序关闭的网络连接数量

上篇文章给出了解决TIME_WAIT太多的方法,本篇文章以HttpClient为例说明解决大量CLOSE_WAIT状态的方法。

HttpClient是大量使用的用于HTTP连接的包,首先需要说明的是HttpClient 3.x和4.x之间API差距很多,不过强烈建议使用4.x的版本。除此之外,4.x中每个x之间也有一些差别(比如一些弃用的类,新增加的类等),这里以4.2.3版本进行说明。

HttpClient使用的HTTP 1.1协议进行连接,相对于HTTP 1.0来说有一个持续连接的增强,为了充分利用持续连接的特性,在一次连接结束之后,即使将HttpResponse使用close方法关闭,并且将调用了HttpGet或HttpPost的releaseConnection方法,示例代码如下:

 HttpGet method = null;

 HttpResponse response = null;

 try {

     method = new HttpGet(url);

     response = client.execute(method);

 } catch(Exception e) {

    

 } finally {

     if(response != null) {

         EntityUtils.consumeQuietly(response.getEntity());

     }

     if(method != null) {

         method.releaseConnection();

     }

 }

这个时候仍然发现连接处于CLOSE_WAIT状态,这是因为HttpClient在执行close的时候,如果发现Response的Header中Connection是Keep-alive则连接不会关闭,以便下次请求相同网站的时候进行复用,这是产生CLOSE_WAIT连接的原因所在。

最简单的一种解决方法在execute方法之前增加Connection: close头信息,HTTP协议关于这个属性的定义如下:

HTTP/1.1 defines the "close" connection option for the sender to signal that the connection will be closed after completion of the response. For example:
	Connection: close 

示例代码如下:

 HttpGet method = null;

 HttpResponse response = null;

 try {

     method = new HttpGet(url);

     method.setHeader(HttpHeaders.CONNECTION, "close");

     response = client.execute(method);

 } catch(Exception e) {

    

 } finally {

     if(response != null) {

         EntityUtils.consumeQuietly(response.getEntity());

     }

     if(method != null) {

         method.releaseConnection();

     }

 }

当然,也有人建议每次请求之后关闭client,但这一点不符合HttpClient设计的原则——复用。如果每次连接完成之后就关闭连接,效率太低了。因此,需要使用PoolingClientConnectionManager,并且设置maxTotal(整个连接池里面最大连接数,默认为20)和defaultMaxPerRoute(每个主机的最大连接数,默认为2),另外client还有一个ClientPNames.CONN_MANAGER_TIMEOUT参数,用来设置当连接不够获取新连接等待的超时时间,默认和CoreConnectionPNames.CONNECTION_TIMEOUT相同。可以根据实际情况对PoolingClientConnectionManager进行设置,以达到效率最优。

还有一种情况也会造成大量CLOSE_WAIT连接,即HttpResponse的状态码不是200的时候,需要及时调用method.abort()方法对连接进行释放,详细可以参考这篇文章

参考资料:
使用httpclient必须知道的参数设置及代码写法、存在的风险
解决:HttpClient导致应用出现过多Close_Wait的问题
Using HttpClient Properly to Avoid CLOSE_WAIT TCP Connections
close_wait troubleshooting
Using HttpClient properly to avoid CLOSE_WAIT TCP connections
HttpClient连接池抛出大量ConnectionPoolTimeoutException: Timeout waiting for connection异常排查
httpclient4.2.1 连接池
爬虫简单示例,用httpClient4.2.1实现(转载)


 

发布了19 篇原创文章 · 获赞 149 · 访问量 80万+

猜你喜欢

转载自blog.csdn.net/truelove12358/article/details/103122167