使用PoolingHttpClientConnectionManager实现http连接池过程

 更新时间:2025年11月19日 09:06:42   作者:码上走人  
文章主要介绍了`PoolingHttpClientConnectionManager`实现HTTP连接池的原理、依赖实现、定时回收链接的方法,以及如何正确释放连接以复用

PoolingHttpClientConnectionManager实现http连接池

PoolingHttpClientConnectionManager是通过租用连接和收回链接的方式来实现的。

解决了http请求的多线程问题。

依赖

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>

实现

首先需要初始化连接池。设定链接数量等,定义清理连接池的方法,并定时调用这个方法清理无效或者长期未使用的链接。

public class HttpClientTest {
    //全局参数
    private static PoolingHttpClientConnectionManager connectionManager = null;
    //设置请求参数
    private RequestConfig config;

    private CloseableHttpClient client;

    //单例模式创建
    private void init(){
        synchronized (HttpClientTest.class) {
            if (client == null) {
                connectionManager = new PoolingHttpClientConnectionManager();
                // http请求线程池,最大连接数
                int requestMaxNum = 5000;
                ConnectionConfig connConfig = ConnectionConfig.custom().setCharset(Charset.forName("utf-8")).build();
                SocketConfig socketConfig = SocketConfig.custom().setSoTimeout(5000).build();
                connectionManager.setDefaultConnectionConfig(connConfig);
                connectionManager.setDefaultSocketConfig(socketConfig);
                // 连接池最大生成连接数
                connectionManager.setMaxTotal(requestMaxNum);
                // 默认设置route最大连接数
                connectionManager.setDefaultMaxPerRoute(requestMaxNum);
                //设置请求参数
                config = RequestConfig.custom().setConnectTimeout(5000) //连接超时时间
                        .setConnectionRequestTimeout(500) //从线程池中获取线程超时时间
                        .setSocketTimeout(5000) //设置数据超时时间
                        .build();
                // 创建builder
                HttpClientBuilder builder = HttpClients.custom();
                //管理器是共享的,它的生命周期将由调用者管理,并且不会关闭
                //否则可能出现Connection pool shut down异常
                builder.setConnectionManager(connectionManager).setConnectionManagerShared(true);
                // 长连接策略
                builder.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy());
                // 创建httpClient
                client = builder.setDefaultRequestConfig(config).setRetryHandler(new MyRetryHandle()).build();
            }
        }
    }

    /**
     * 从池子中获取连接
     * @return CloseableHttpClient
     */
    private CloseableHttpClient getClientFromHttpPool() {
        if(client == null) {
            init();
        }
        return client;
    }

}

定时回收链接

// 启动定时器,定时回收过期的连接
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
            // 关闭过期的链接
            connectionManager.closeExpiredConnections();
            // 选择关闭 空闲30秒的链接
            connectionManager.closeIdleConnections(30, TimeUnit.SECONDS);
            }
        }, 10 * 1000, 5 * 1000);

实现两个方法,执行httpqing请求

public String getTest(String url) {
        CloseableHttpClient client = getClientFromHttpPool();
        HttpGet method = new HttpGet(url);
        //设置请求头
        //method.setRequestHeader();
        long start = System.currentTimeMillis();
        BufferedReader reader = null;
        InputStream inputStream = null;
        StringBuilder result = new StringBuilder("");
        try {
            long startTime = System.currentTimeMillis();
            CloseableHttpResponse response = client.execute(method);
            if(response.getStatusLine().getStatusCode() == 200){
                HttpEntity httpEntity = response.getEntity();
                inputStream = httpEntity.getContent();
                //解析方式1
                reader = new BufferedReader(new InputStreamReader(inputStream));
                StringBuilder sb = new StringBuilder();
                String line = null;
                while ((line = reader.readLine()) != null) {
                    sb.append(line).append("\n");
                }
                String ret = sb.toString();
                System.out.println(ret);

                //解析方式二
                byte[] bytes = new byte[1024];
                int length = 0;
                while(-1 != (length = inputStream.read(bytes))) {
                    result.append(new String(bytes, 0, length));
                }
            }else {
                System.out.println("请求失败,返回" + response.getStatusLine().getStatusCode());
            }
            System.out.println("用时:" + (System.currentTimeMillis() - startTime));
            return result.toString();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(inputStream != null){
                try {
                    inputStream.close();
                }catch (Exception e){
                }
            }
            if(reader != null){
                try {
                reader.close();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
            //释放连接  
            // httpClient必须releaseConnection,但不是abort。因为releaseconnection是归还连接到到连接池,而abort是直接抛弃这个连接,而且占用连接池的数目。
            method.releaseConnection();
        }
        System.out.println("end..Duration MS:" + (System.currentTimeMillis() - start));
        return null;
    }

    public void postTest(String url, String param) {
        CloseableHttpClient client = getClientFromHttpPool();
        HttpPost method = new HttpPost(url);
        //设置请求头
        //method.setRequestHeader();
        //设置请求参数--可以用各种方式传入参数
        try {
            method.setEntity(new StringEntity(param));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        long start = System.currentTimeMillis();
        BufferedReader reader = null;
        InputStream inputStream = null;
        try {
            CloseableHttpResponse response = client.execute(method);
            if(response.getStatusLine().getStatusCode() == 200){
                HttpEntity httpEntity = response.getEntity();
                inputStream = httpEntity.getContent();
                //解析方式1
                reader = new BufferedReader(new InputStreamReader(inputStream));
                StringBuilder sb = new StringBuilder();
                String line = null;
                while ((line = reader.readLine()) != null) {
                    sb.append(line).append("\n");
                }
                String ret = sb.toString();
                System.out.println(ret);

                //解析方式二
                byte[] bytes = new byte[1024];
                int length = 0;
                StringBuilder result = new StringBuilder("");
                while(-1 != (length = inputStream.read(bytes))) {
                    result.append(new String(bytes, 0, length));
                }
                System.out.println(result);
            }else {
                System.out.println("请求失败,返回" + response.getStatusLine().getStatusCode());
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(inputStream != null){
                try {
                    inputStream.close();
                }catch (Exception e){
                }
            }
            if(reader != null){
                try {
                    reader.close();
                }catch (Exception e){
                }
            }
            //关闭池子
           /* if(client != null){
                try {
                    client.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }*/
            //释放连接
            method.releaseConnection();
        }
        System.out.println("end..Duration MS:" + (System.currentTimeMillis() - start));
    }

重试策略

加入重试策略:

// 创建httpClient
        return builder.setDefaultRequestConfig(config).setRetryHandler(new MyRetryHandle()).build();
/**
 * 请求连接池失败重试策略
 */
public class MyRetryHandle implements HttpRequestRetryHandler {
    Logger logger = LoggerFactory.getLogger(MyRetryHandle.class);
    //请求失败时,进行请求重试
    @Override
    public boolean retryRequest(IOException e, int i, HttpContext httpContext) {
        if (i > 3) {
            //重试超过3次,放弃请求
            logger.error("retry has more than 3 time, give up request");
            return false;
        }
        if (e instanceof NoHttpResponseException) {
            //服务器没有响应,可能是服务器断开了连接,应该重试
            logger.error("receive no response from server, retry");
            return true;
        }
        if (e instanceof SSLHandshakeException) {
            // SSL握手异常
            logger.error("SSL hand shake exception");
            return false;
        }
        if (e instanceof InterruptedIOException) {
            //超时
            logger.error("InterruptedIOException");
            return false;
        }
        if (e instanceof UnknownHostException) {
            // 服务器不可达
            logger.error("server host unknown");
            return false;
        }
        if (e instanceof ConnectTimeoutException) {
            // 连接超时
            logger.error("Connection Time out");
            return false;
        }
        if (e instanceof SSLException) {
            logger.error("SSLException");
            return false;
        }

        HttpClientContext context = HttpClientContext.adapt(httpContext);
        HttpRequest request = context.getRequest();
        if (!(request instanceof HttpEntityEnclosingRequest)) {
            //如果请求不是关闭连接的请求
            return true;
        }
        return false;
    }
}

原理及注意事项

连接池中连接都是在发起请求的时候建立,并且都是长连接

HaoMaiClient.java中的in.close();作用就是将用完的连接释放,下次请求可以复用,这里特别注意的是,如果不使用in.close();而仅仅使用response.close();结果就是连接会被关闭,并且不能被复用,这样就失去了采用连接池的意义。

连接池释放连接的时候,并不会直接对TCP连接的状态有任何改变,只是维护了两个Set,leased和avaliabled,leased代表被占用的连接集合,avaliabled代表可用的连接的集合,释放连接的时候仅仅是将连接从leased中remove掉了,并把连接放到avaliabled集合中

http://hc.apache.org/ 文档以及源码

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Java使用线程池实现socket编程的方法详解

    Java使用线程池实现socket编程的方法详解

    这篇文章主要为大家详细介绍了Java使用线程池实现socket编程的方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-03-03
  • Java8 Collectors.toMap的坑

    Java8 Collectors.toMap的坑

    这篇文章主要介绍了Java8 Collectors.toMap的坑,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • java中如何判断对象是否是垃圾

    java中如何判断对象是否是垃圾

    这篇文章主要介绍了java中如何判断对象是否是垃圾,Java有两种算法判断对象是否是垃圾:引用计数算法和可达性分析算法,需要的朋友可以参考下
    2023-04-04
  • Java中Getter和Setter方法及主要区别

    Java中Getter和Setter方法及主要区别

    这篇文章主要给大家介绍了关于Java中Getter和Setter方法及主要区别的相关资料,getter和setter方法是用于封装类中的私有属性的方法,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-04-04
  • Java Array与ArrayList区别详解

    Java Array与ArrayList区别详解

    这篇文章主要介绍了Java Array与ArrayList区别详解的相关资料,需要的朋友可以参考下
    2017-01-01
  • RocketMQ整合SpringBoot实现生产级二次封装

    RocketMQ整合SpringBoot实现生产级二次封装

    本文主要介绍了RocketMQ整合SpringBoot实现生产级二次封装,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06
  • Java中占位符的超全使用方法分享

    Java中占位符的超全使用方法分享

    这篇文章主要为大家详细介绍了Java中常见的一些占位符的使用方法,例如%d,%s等,文中的示例代码简洁易懂,感兴趣的小伙伴可以跟随小编一起学习学习
    2023-05-05
  • 使用Java和SNMP4J实现SNMP操作完整代码

    使用Java和SNMP4J实现SNMP操作完整代码

    这篇文章主要介绍了如何使用Java和SNMP4J库进行SNMP操作,包括初始化SNMP、创建目标、创建PDU、发送SNMP请求和处理响应等内容,通过编写SnmpUtil类,展示了完整的SNMP操作流程,需要的朋友可以参考下
    2024-12-12
  • Java解析微信获取手机号信息的示例步骤

    Java解析微信获取手机号信息的示例步骤

    在微信中,用户手机号的获取通常是通过微信小程序的getPhoneNumber接口来实现的,下面通过一个基于Java的示例,展示了如何接收并解密从微信小程序传递过来的加密手机号信息,感兴趣的朋友一起看看吧
    2024-06-06
  • Springboot事务失效的几种情况解读

    Springboot事务失效的几种情况解读

    这篇文章主要介绍了Springboot事务失效的几种情况解读,因为Spring AOP默认使用动态代理,会给被代理的类生成一个代理类,事务相关的操作都通过代理来完成,使用内部方法调用时,使用的是实例调用,没有通过代理类调用方法,因此事务不会检测到失败,需要的朋友可以参考下
    2023-10-10

最新评论