HttpClient连接池使用不当问题分析解决

背景

最近遇到一个HttpClient问题,某个接口一直报404错误。该接口使用HttpClient调用其他服务获取数据,为了提高接口调用性能,利用httpclient池化技术来保证请求的数量,并及时关闭无效的请求,在某次版本上线后该接口出现异常。本文使用httpclient-5.2.1版本还原使用场景及分析说明。

代码实现

为了说明问题,本文将以最简洁的代码还原问题。

工具类

池化对象PoolingHttpClientConnectionManager使用单例模式。

/**
 * HttpClient工具类
 */
class HttpClientUtils {
    static PoolingHttpClientConnectionManager cm;

    static {
        cm = new PoolingHttpClientConnectionManager();
    }

    public static CloseableHttpClient getHttpClient() {
        return HttpClientBuilder.create().setConnectionManager(cm).build();
    }
}

功能实现

出现问题的代码是在某个版本的时候调用了CloseableHttpClient.close()方法释放资源。

@Slf4j
class TestTask implements Runnable {
    @Override
    public void run() {
        CloseableHttpClient client = HttpClientUtils.getHttpClient();
        HttpGet get = new HttpGet("https://www.baidu.com");

        try {
            ClassicHttpResponse execute = client.execute(get, response -> response);
        } catch (IOException e) {
            log.info("RPC error: {}", e.getMessage());
        } finally {
            try {
                client.close();
            } catch (IOException e) {
                log.info("Release error: {}", e.getMessage());
            }
        }
    }
}

模拟使用

使用多个线程模拟API接口调用

public class HttpClientTest {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(new TestTask()).start();
        }
    }
}

问题分析与定位

检查服务日志发现下图所示错误:
错误日志该错误出现的原因是调用CloseableHttpClient.close()方法释放资源的时候将单例的PoolingHttpClientConnectionManager对象关闭了,导致再次使用工具类获取client对象时因为池化对象已被关闭而报错。
通过错误信息在httpclient源码中分析出现问题的原因。

在HttpClientBuilder.build()方法中找到如下代码:
池化对象放入可关闭对象列表在InternalHttpClient.close()方法中将可关闭对象全部关闭了
CloseableHttpClient的close方法
代码找到这里,原因已经很明显了,使用HttpClient池化技术的方式有问题。

解决方案

通过HttpClientBuilder.build()中的代码,可以发现connManagerShared为true时池化对象不会被放如可关闭对象列表中。在使用HttpClientBuilder设置单例池化对象时将池化对象设置为共享状态,在调用CloseableHttpClient.close()方法释放资源时才不会将池化对象关闭导致后续连接不能通过池化对象分配连接。正确使用方式如下:

HttpClientBuilder.create().setConnectionManager(cm).setConnectionManagerShared(true).build();

总结

在使用第三方库的时候一定要注意接口说明或者查看对应的源码以避免使用不当而出现故障。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值