当前位置: 首页 > 工具软件 > Down > 使用案例 >

Connection pool shut down http请求异常关闭

楚畅
2023-12-01

    本人在项目运用中写了一个数据推送的组件,需要多线程频繁调用远程接口进行传输数据,远程请求通过HttpClient 使用 CloseableHttpClient 发起连接后,使用CloseableHttpResponse 接受返回结果,一开始每次请求耗时时间都比较长,因此引入了httpClient连接池。

    为什么要使用连接池:

1、降低延迟:如果不采用连接池,每次连接发起Http请求的时候都会重新建立TCP连接(经历3次握手),用完就会关闭连接(4次挥手),如果采用连接池则减少了这部分时间损耗

2、支持更大的并发:如果不采用连接池,每次连接都会打开一个端口,在大并发的情况下系统的端口资源很快就会被用完,导致无法建立新的连接

    在实际项目运行中,发现系统偶尔会出现崩溃,查看日志,发现后台每次请求都返回错误 java.lang.IllegalStateException: Connection pool shut down,寻找错误错误源头,也没发现其他的错误信息,初步判断定位到http请求的httpclient问题。

    Caused by: java.lang.IllegalStateException: Connection pool shut down
    at org.apache.http.util.Asserts.check(Asserts.java:34)
    at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.requestConnection(PoolingHttpClientConnectionManager.java:269)
    at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:176)
    at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186)
    at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)
    at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
    at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:108)
    at com.hikvision.eits.demo.xcas.http.PooledCustomHttpClient.download(PooledCustomHttpClient.java:233)

    错误1:通过问题查找网上资料,有可能使用httpClient.close()主动关闭了连接,导致下次请求的时候抛出连接池关闭异常,排查代码,发现未存在该代码, 连接池代码中,连接不需要业务管理而是交给连接池管理

    错误2:httpclient连接池单个路由最大连接数设置不合理,业务高峰期时无法及时从连接池中获取连接,导致http线程都处于BLOCKED(阻塞于锁)状态,逐渐拖垮应用服务器,当最大线程是不能超过setDefaultMaxPerRoute设置的数字,一旦超过就会死掉。这里会报错 connection pool shut down httpclient,所以每次使用完都需要CloseableHttpResponse.close()释放资源,防止资源一直被占用。

  • MaxtTotal是整个池子的大小;
  • DefaultMaxPerRoute是根据连接到的主机对MaxTotal的一个细分;

HttpClientBuilder builder = HttpClients.custom();

使用该方式定义了httpcliet,默认使用的是PoolingHttpClientConnectionManager 线程池管理器,默认每个route只允许最多2个connection,总的connection数量不超过20。

    connectionManager.setMaxTotal(80);

    connectionManager.setDefaultMaxPerRoute(80);

    一开始我在项目中设置的最大并发数为80,考虑到实际业务中会通过线程池创建大量线程,又有多处运行了单线程处理,把最大并发数改为200,后续更新后运行仍会出现连接关闭错误。

   再次查看自己创建连接池对象代码,排查过程中.setConnectionManagerShared(true) 有说添加这个设置可解决问题,于是把该配置加上,部署后果然正常稳定执行

   该配置保证后台使用一个共享连接池,供剩下打开的连接去使用

   最后完整创建对象的代码:

    private static final int DEFAULT_POOL_MAX_TOTAL = 200;
    private static final int DEFAULT_POOL_MAX_PER_ROUTE = 200;

    private static final int DEFAULT_CONNECT_TIMEOUT = 8000;
    private static final int DEFAULT_CONNECT_REQUEST_TIMEOUT = 12000;
    private static final int DEFAULT_SOCKET_TIMEOUT = 20000;

    private PoolingHttpClientConnectionManager gcm = null;

    private CloseableHttpClient httpClient = null;

    private IdleConnectionMonitorThread idleThread = null;

    // 连接池的最大连接数
    private final int maxTotal;
    // 连接池按route配置的最大连接数
    private final int maxPerRoute;

    // tcp connect的超时时间
    private final int connectTimeout;
    // 从连接池获取连接的超时时间
    private final int connectRequestTimeout;
    // tcp io的读写超时时间
    private final int socketTimeout;

    //构造方法
    public PooledCustomHttpClient() {
        this(PooledCustomHttpClient.DEFAULT_POOL_MAX_TOTAL,
                PooledCustomHttpClient.DEFAULT_POOL_MAX_PER_ROUTE,
                PooledCustomHttpClient.DEFAULT_CONNECT_TIMEOUT,
                PooledCustomHttpClient.DEFAULT_CONNECT_REQUEST_TIMEOUT,
                PooledCustomHttpClient.DEFAULT_SOCKET_TIMEOUT);
    }

    public PooledCustomHttpClient(int maxTotal, int maxPerRoute, int connectTimeout, int connectRequestTimeout, int socketTimeout) {

        this.maxTotal = maxTotal;
        this.maxPerRoute = maxPerRoute;
        this.connectTimeout = connectTimeout;
        this.connectRequestTimeout = connectRequestTimeout;
        this.socketTimeout = socketTimeout;

        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", SSLConnectionSocketFactory.getSocketFactory())
                .build();

        this.gcm = new PoolingHttpClientConnectionManager(registry);
        this.gcm.setMaxTotal(this.maxTotal);
        this.gcm.setDefaultMaxPerRoute(this.maxPerRoute);

        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(this.connectTimeout)                   // 设置连接超时
                .setSocketTimeout(this.socketTimeout)                     // 设置读取超时
                .setConnectionRequestTimeout(this.connectRequestTimeout)  // 设置从连接池获取连接实例的超时
                .build();

        HttpClientBuilder httpClientBuilder = HttpClients.custom();
        this.httpClient = httpClientBuilder
                .setConnectionManager(this.gcm)
                .setConnectionManagerShared(true)   
                .setDefaultRequestConfig(requestConfig)
                .build();

        this.idleThread = new IdleConnectionMonitorThread(this.gcm);
        this.idleThread.start();

    }

最后远程调用后释放连接资源

    public String doPostBody(String url, Map<String, String> reqHeader, String body, Map<String, String> respHeader) {
        CloseableHttpResponse response = null;
        try {
            HttpPost httpPost = new HttpPost(url);
            if (reqHeader != null && reqHeader.size() > 0) {
                for (Map.Entry<String, String> entry : reqHeader.entrySet()) {
                    httpPost.addHeader(entry.getKey(), entry.getValue());
                }
            }

            if (body != null) {
                StringEntity s = new StringEntity(body, Charset.forName("UTF-8"));
                httpPost.setEntity(s);
            }

            response = httpClient.execute(httpPost);
            if (response == null) {
                return null;
            }

            String res = null;
            HttpEntity entityRes = response.getEntity();
            if (entityRes != null) {
                res =  EntityUtils.toString(entityRes, "UTF-8");
            }
            return res;
        } catch (Throwable e) {
            throw new RuntimeException(tip, e);
        } finally {
            //关闭资源
            IOUtils.closeQuietly(response);
        }
    }

 

 类似资料: