HttpClient 异常解决

HttpClient PoolingHttpClientConnectionManager

Posted by gomyck on November 18, 2020

遇见个 httpclient 异常, 查看源码, 记录下解决过程

Exception

org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool

Cause By

看异常信息, 是等待连接池的连接超时了, 那么进一步分析, 应该是连接池连接数不够

Resolve It!

查看 HttpClients (工厂类), 找到 build 方法, 查看源代码, 找有关连接池的相关代码, 发现代码片段如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
HttpClientConnectionManager connManagerCopy = this.connManager;
if (connManagerCopy == null) {
    LayeredConnectionSocketFactory sslSocketFactoryCopy = this.sslSocketFactory;
    if (sslSocketFactoryCopy == null) {
        final String[] supportedProtocols = systemProperties ? split(
                System.getProperty("https.protocols")) : null;
        final String[] supportedCipherSuites = systemProperties ? split(
                System.getProperty("https.cipherSuites")) : null;
        HostnameVerifier hostnameVerifierCopy = this.hostnameVerifier;
        if (hostnameVerifierCopy == null) {
            hostnameVerifierCopy = new DefaultHostnameVerifier(publicSuffixMatcherCopy);
        }
        if (sslContext != null) {
            sslSocketFactoryCopy = new SSLConnectionSocketFactory(
                    sslContext, supportedProtocols, supportedCipherSuites, hostnameVerifierCopy);
        } else {
            if (systemProperties) {
                sslSocketFactoryCopy = new SSLConnectionSocketFactory(
                        (SSLSocketFactory) SSLSocketFactory.getDefault(),
                        supportedProtocols, supportedCipherSuites, hostnameVerifierCopy);
            } else {
                sslSocketFactoryCopy = new SSLConnectionSocketFactory(
                        SSLContexts.createDefault(),
                        hostnameVerifierCopy);
            }
        }
    }
    @SuppressWarnings("resource")
    final PoolingHttpClientConnectionManager poolingmgr = new PoolingHttpClientConnectionManager(
            RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", sslSocketFactoryCopy)
                .build(),
            null,
            null,
            dnsResolver,
            connTimeToLive,
            connTimeToLiveTimeUnit != null ? connTimeToLiveTimeUnit : TimeUnit.MILLISECONDS);
    if (defaultSocketConfig != null) {
        poolingmgr.setDefaultSocketConfig(defaultSocketConfig);
    }
    if (defaultConnectionConfig != null) {
        poolingmgr.setDefaultConnectionConfig(defaultConnectionConfig);
    }
    if (systemProperties) {
        String s = System.getProperty("http.keepAlive", "true");
        if ("true".equalsIgnoreCase(s)) {
            s = System.getProperty("http.maxConnections", "5");
            final int max = Integer.parseInt(s);
            poolingmgr.setDefaultMaxPerRoute(max);
            poolingmgr.setMaxTotal(2 * max);
        }
    }
    if (maxConnTotal > 0) {
        poolingmgr.setMaxTotal(maxConnTotal);
    }
    if (maxConnPerRoute > 0) {
        poolingmgr.setDefaultMaxPerRoute(maxConnPerRoute);
    }
    connManagerCopy = poolingmgr;
}

默认的连接池只有 5 个!!!!

解决方式分为多种, 一种是设置系统属性, 但是前提是得开启系统属性支持(systemProperties = true)

另外一种是在 build 前, 设置 HttpClientConnectionManager

Code Opt

在我的工具类中, 加入了以下代码:

1
2
3
4
5
6
7
8
9
10
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();

{
    cm.setMaxTotal(300);
    cm.setDefaultMaxPerRoute(75);
    HttpHost localhost = new HttpHost("locahost", 80);
    cm.setMaxPerRoute(new HttpRoute(localhost), 50);
}

public CloseableHttpClient httpclient = HttpClients.custom().setDefaultRequestConfig(defaultRequestConfig).setConnectionManager(cm).useSystemProperties().build();

属性说明:

cm.setMaxTotal(300); // 设置最大连接数, 也就是最大并发连接数 cm.setDefaultMaxPerRoute(75); // 设置单个路由的最大连接数 比如要请求两个 host: a.com b.com 那么同时高并发访问, a 和 b 每个 host 最大可发起 75 个连接(不是 300) cm.setMaxPerRoute(new HttpRoute(localhost), 50); // 指定路由最大连接数