HttpComponents HttpClient连接池(9)-长连接

上一篇文章里我们介绍了 httpclient 连接池对于 SSL 的支持,这里主要介绍连接池中的长连接。

根据以前文章, 对于httpclient 连接池中的池化对象 CpoolEntry 都是可以被复用的,这样在每次申请连接的时候都会从可用连接集合 available 中获取,避免每次都重新创建连接,提高了效率。关于连接池如何决定重用连接,以及连接 keep alive 保活多久的介绍,请参考这篇文章。池化对象 CpoolEntry 虽然重用了,但是里面真正的原始 socket 是长连接么?我们从使用 httpclient 的代码分析,一般使用 httpclient 的代码如下:

/*Application code*/
try {
      CloseableHttpClient htttpClient = HttpClients.custom().build();
      HttpGet getMethod = new HttpGet("https://www.baidu.com/");
      CloseableHttpResponse response = htttpClient.execute(getMethod);
      HttpEntity entitry = response.getEntity();
      byte[] contentArray = EntityUtils.toByteArray(entitry);
 } catch (Exception e) {
        // TODO Handle Exception here
 }


/*EntityUtils*/
public static byte[] toByteArray(final HttpEntity entity) throws IOException {
    Args.notNull(entity, "Entity");
    final InputStream inStream = entity.getContent();
    if (inStream == null) {
       return null;
    }
    try {
        Args.check(entity.getContentLength() <= Integer.MAX_VALUE, "HTTP entity too large to be buffered in memory");
        int capacity = (int)entity.getContentLength();
        if (capacity < 0) {
            capacity = DEFAULT_BUFFER_SIZE;
        }
        final ByteArrayBuffer buffer = new ByteArrayBuffer(capacity);
        final byte[] tmp = new byte[DEFAULT_BUFFER_SIZE];
        int l;
        while((l = inStream.read(tmp)) != -1) {
            buffer.append(tmp, 0, l);
        }
        return buffer.toByteArray();
    } finally {
        inStream.close();
    }
}
  • 根据上述代码在调用 EntityUtils.toByteArray() 方法的时候,会最终调用 inputStream 的 close() 方法。

  • 我们 debug 调试原始代码的 inputStream 对象实例的 close() 方法。

  • 通过上述调试我们发现,最终调用了 ConnectionHolder 对象的 releaseConnection() 方法,而且是重用连接的,根据以前文章, releaseConnection() 方法在重用 connection 的情况下只是把池化对象 CpoolEntry 分别在 global 连接池和 route 的 individual 连接池的正在使用集合 leased 中移除,并加入各自的可用连接集合 available 中,并没有对原始 socket 关闭,所以是 http 长连接。

  • 另外通过上述源码分析,和 EntityUtils 对象实例的 toByteArray() 方法一样, EntityUtils 对象的 toString(), consume(), consumeQuietly() 等方法也是在 finally 块里调用了 inputStream 的 close() 方法,从而间接的归还了池化对象,同时保持原始 socket 不关闭为长连接。所以,我们在程序里一定要确保 EntityUtils 中上述方法被调用。

  • 除了调用 EntityUtils 类, httpclient 也提供了 CloseableHttpResponse 的 close() 方法,我们 debug 来看查看如下:

  • 通过 debug 我们发现 CloseableHttpResponse 的 close() 方法间接调用了 ConnectionHolder 的 releaseConnection() 方法,但是不重用连接。根据以前文章,该方法在不重用连接的情况下除了从 global 池和 individual route 池移除当前池化对象外,还关闭了原始 socket 。所以 response 的 close() 方法消除池化对象,没有长连接,这一点和 EntityUtils 中的方法有所区别。

根据以上分析,在 httpclient 中如果希望重用池化对象并且保持长连接,那么务必调用 EntityUtils 中的方法。如果不希望重用池化对象,不希望有长连接,那么请调用 CloseableHttpResponse 的 close() 方法。

对于使用长连接的情况下也有一些思考:

  • 如果池化对象归还到连接池并且没有关闭原始 socket 保持长连接。

  • 对于服务端可能主动关闭了这个连接,这个时候 httpclient 中的这个连接就为 close_wait 状态,下次申请到的时候是不可用的。

  • 这种情况,调用 http 请求就会失败,在失败的时候连接池会把这个连接关掉,然后利用以前文章里的重试机制重新尝试发送请求,从而解决这种问题。

  • 但是这样做并不优雅,当服务端把大量的连接关闭, httpclient 连接池中就有大量的 close_wait 状态的连接,属于未释放资源。一直到再次有连接申请,发送请求的时候才发现,然后再关闭连接释放资源,重新建立连接。

  • 所以这里推荐以前文章中介绍的启用连接清理方式,这样就避免了 close_wait 状态的未关闭连接。


目前先写到这里,在下一篇文章里我们总结一下 httpclient 连接池的使用建议。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值