HttpClient使用连接池

「这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战

1. http请求存在的问题

Http1.0连接是无状态的,短链接,每次请求时建立连接、请求结束后断开连接。也可以在请求头中设置Connection:keep-alive来实现Http的长连接。

Http连接的建立和关闭本质上就是TCP连接的建立和关闭,在建立和关闭时会有三次握手和四次挥手的过程,占用资源多、开销大。

为了降低频繁建立和断开Http连接对资源的消耗,就需要使用Http连接池来管理Http的连接,并保证一定数量的长连接,且系统能拥有更高的并发性能。

2. 创建http连接池

HttpClient中创建http连接池的方式是使用定义的PoolingHttpClientConnectionManager类,该类实现了HttpClientConnectionManager接口和ConnPoolControl接口。

  • 初始化连接池对象时,可以在构造函数中传入时间值参数,指定连接存活时间
  • 使用对象的setMaxTotal()方法来设置连接池的最大连接数
  • 使用对象的setDefaultMaxPerRoute()方法设置路由最大连接数
/** http连接池对象 */
private static PoolingHttpClientConnectionManager cm;
/**
 * 初始化连接池
 */
public static void init(){
    //创建http连接池,可以同时指定连接超时时间
    cm = new PoolingHttpClientConnectionManager(60000, TimeUnit.MILLISECONDS);
    //最多同时连接20个请求
    cm.setMaxTotal(20);
    //每个路由最大连接数,路由指IP+PORT或者域名
    cm.setDefaultMaxPerRoute(50);
}
复制代码

3. 通过连接池获取httpClient

HttpClient中http请求的连接由HttpClient对象发起,加入连接池后就可以从连接池中获取HttpClient对象信息。

/**
 * 从连接池中获取httpClient连接
 */
public static CloseableHttpClient getHttpClient(PoolingHttpClientConnectionManager cm){
    //设置请求参数配置,创建连接时间、从连接池获取连接时间、数据传输时间、是否测试连接可用、构建配置对象
    RequestConfig requestConfig = RequestConfig.custom()
        .setConnectTimeout(1000)
        .setConnectionRequestTimeout(3000)
        .setSocketTimeout(10 * 1000)
        .setStaleConnectionCheckEnabled(true)
        .build();
    
    //创建httpClient时从连接池中获取,并设置连接失败时自动重试(也可以自定义重试策略:setRetryHandler())
    CloseableHttpClient httpClient = HttpClients.custom()
        .setDefaultRequestConfig(requestConfig)
        .setConnectionManager(cm)
        .disableAutomaticRetries()
        .build();
    return httpClient;
}
复制代码

在使用http连接池获取httpClient连接时,需要配置相关参数信息:

  • 定义RequestConfig对象来设置请求时的时间参数,如连接创建时间、从连接池中获取连接时间、数据传输请求时间、是否请求前测试可用性等
    • RequestConfig对象可以为整个httpClient设置,也可以针对GET请求和POST请求分别设置
  • 接收一个CloseableHttpClient对象作为httpClient,使用建造者模式获取时需要设置连接对象的配置信息、失败重连信息以及来源的连接池信息。

4. 设定请求类型并执行请求

创建httpClient连接后,之后的请求执行流程和单独创建httpClient对象的请求流程基本一致。

需要注意的就是在使用连接池获取的httpClient请求完成后,如果希望将连接释放到连接池中,就不能使用close()方法关闭,而是调用EntityUtils.consume()方法。

/**
 * 执行请求
 */
public static void doGetRequest(CloseableHttpClient httpClient,String url){
    //创建http请求类型
    HttpGet httpGet = new HttpGet(url);
​
    CloseableHttpResponse httpResponse = null;
    try {
        httpResponse = httpClient.execute(httpGet);
        if(200 == httpResponse.getStatusLine().getStatusCode()){
            System.out.println("请求返回数据内容:" + EntityUtils.toString(httpResponse.getEntity()));
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(httpResponse != null){
            //执行httpResponse.close关闭对象会关闭连接池,
            //如果需要将连接释放到连接池,可以使用EntityUtils.consume()方法
            try {
                EntityUtils.consume(httpResponse.getEntity());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
复制代码

5. 连接池状态观察

使用连接池获取httpClient连接发起请求时,可以通过连接池中连接的存活等信息来观察使用情况。

当使用单线程执行多次http请求时,连接池中始终保持一个连接,因为单线程总是执行完上一个才会开始下一个请求的执行。

String url = "http://www.baidu.com";
for(int i = 0; i < 5; i++){
    //连接池中获取httpClient
    CloseableHttpClient httpClient = getHttpClient(cm);
    //单线程执行请求
    doGetRequest(httpClient, url);
    System.out.println(cm.getTotalStats());
}
复制代码
  • 因为是单线程,线程池的状态会打印5次,每次结果都是//[leased: 0; pending: 0; available: 1; max: 20]

而使用多线程执行http请求时,每个请求开启一个线程后,最终执行完成并将连接释放至连接池后,连接池中存活了请求中并存的最大连接数。

String url = "http://www.baidu.com";
for(int i = 0; i < 5; i++){
    //获取httpClient
    CloseableHttpClient httpClient = getHttpClient(cm);
    //每个请求开启一个单独的线程
    doGetRequestMulitThread(httpClient,url);
    System.out.println(cm.getTotalStats());
}
复制代码
  • 最终只会打印一次线程池状态,结果为[leased: 0; pending: 0; available: 5; max: 20]

猜你喜欢

转载自juejin.im/post/7032673516151537694