美文网首页
Http连接池之:Timeout waiting for con

Http连接池之:Timeout waiting for con

作者: CaptainXero | 来源:发表于2019-12-02 19:54 被阅读0次

背景

系统 A 调用系统 B 的接口,最近发现当 B 停机维护时,系统 A 在出发到调用系统 B 的接口的功能时会发生阻塞。查看日志,发现异常如下:

org.apache.http.HttpException: Timeout waiting for connection from pool


图1-异常日志

排查

定位到如下代码:

    private String executeHttpPost(Request request) throws HttpException {
         
          
        String responseString = null;
        HttpPost httpPost = null;
        try {
            String countryCode = request.getCustomer().getCountryCode();
            String postData = XMLUtil.object2XML(request);
            
            StringBuffer cacheServiceURL = new StringBuffer();
            cacheServiceURL.append(serviceURL)
                           .append("?country=")
                           .append(countryCode);
            
            System.err.println(cacheServiceURL);
            
            httpPost = new HttpPost(cacheServiceURL.toString());
            
            httpPost.setHeader(HttpHeaders.AUTHORIZATION, "Basic " + userToken);
            httpPost.setHeader(HttpHeaders.CONTENT_TYPE, contentType);
            HttpEntity requestEntity = new StringEntity(postData, "UTF-8");
            httpPost.setEntity(requestEntity);

            HttpResponse httpResponse = httpClient.execute(httpPost,localContext);
            CacheResponseStatus responseStatus = (CacheResponseStatus) localContext.getAttribute(
                    CachingHttpClient.CACHE_RESPONSE_STATUS);
            System.err.println(responseStatus);
            
            LogContextFactory.singleton().getLogContext().error(this, "The service (" + serviceURL + ") return cache response status " + responseStatus);

            int returnHttpStatus = httpResponse.getStatusLine().getStatusCode();
            if (HttpStatus.SC_OK == returnHttpStatus) {
                if(httpResponse.getEntity() != null)
                    responseString = EntityUtils.toString(httpResponse.getEntity());
            } else {
                LogContextFactory.singleton().getLogContext().error(this, "The service (" + serviceURL + ") return status " + returnHttpStatus);
                throw new HttpException("The service (" + serviceURL + ") return status " + returnHttpStatus);
            }
        } catch (Exception e) {
            LogContextFactory.singleton().getLogContext().error(this, "When execute executeHttpPost method,a fatal issue has occured.");
            LogContextFactory.singleton().getLogContext().error(this, e);
            throw new HttpException(e.getMessage());
        } finally {
            if (httpPost != null) {
                httpPost.releaseConnection();
            }
        }
        return responseString;
    }

很明显,这段代码负责执行HTTP的POST方法,看起来在finally中也做了connection的关闭。那么分析日志得出结果如下:

  1. 连接池大小为20
  2. 在 B 系统维护时,前20次调用都可以立即返回 404
  3. 后续的调用抛出Timeout waiting for connection from pool 异常
  4. 在服务器上使用netstat查看TCP状况,发现这些链接出于 ESTABLISHED 状态

通过日志,分析可能是连接池泄露了,于是重新检查这段方法。发现这段代码居然是在try作用域内初始化的,在finally中已经跳出作用域,十分之可疑。

HttpResponse httpResponse = httpClient.execute(httpPost,localContext);

解决

查了一些博客和文档,都说这个 httpResponse 也需要在finally中释放,于是改进后的代码如下:

    private String executeHttpPost(Request request) throws HttpException {    
        String responseString = null;
        HttpPost httpPost = null;
        HttpResponse httpResponse = null;
        try {
            //Omit some code
            httpResponse = httpClient.execute(httpPost,localContext);
            //Omit some code
            if (HttpStatus.SC_OK == returnHttpStatus) {
                if(httpResponse.getEntity() != null)
                    responseString = EntityUtils.toString(httpResponse.getEntity());
            } else {    
               //Omit some code 
            }
        } catch (Exception e) { 
          //Omit some code
        } finally {
            if (httpResponse != null) {
                try {
                    EntityUtils.consume(httpPost.getEntity());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (httpPost != null) {
                httpPost.releaseConnection();
            }
        }
        return responseString;
    }

运行结果:


图2-正常返回

当调用的服务不可用时,确实立刻反回了404,不会再阻塞业务。

Solution分析

疑惑

  1. 为什么 B 服务正常返回的时候不会出现连接池泄露?
  2. finally中调用的 httpPost.releaseConnection() 是干嘛的?

首先看代码的这部分代码:


图3-判断逻辑

这部分代码并没有处理返回状态不是 200 的情况,所以直接看 finally 块:

            if (httpPost != null) {
                httpPost.releaseConnection();
            }

这段代码看起来是在释放什么东西,点进源码发现其实就是对连接进行重置而已:


图4-releaseConnection
图5-reset方法

原来这个releaseConnection方法仅仅是对连接的状态进行标记,使其可以标记为被复用的连接。那么为什么返回状态码是 200 的正常情况不出现连接池泄露呢?返回图3,当返回状态码为200时,只有这一行代码:

responseString = EntityUtils.toString(httpResponse.getEntity());

查看发现 EntityUtils.toString() 方法调用了关闭流的方法!


图6-EntityUtils.toString()
图7-EntityUtils.toString()

再来看后来在finally块中添加上的这段代码:

 EntityUtils.consume(httpPost.getEntity()); 
image.png

一目了然,这才是真正关闭了流。

总结

但凡遇到什么“池”,都要再三小心,尤其在异常情况下释放资源这块。

相关文章

网友评论

      本文标题:Http连接池之:Timeout waiting for con

      本文链接:https://www.haomeiwen.com/subject/ubifgctx.html