HTTP链接池

1.为何要用Http链接池

一、下降延迟:若是不采用链接池,每次链接发起Http请求的时候都会从新创建TCP链接(经历3次握手),用完就会关闭链接(4次挥手),若是采用链接池则减小了这部分时间损耗,别小看这几回握手,本人通过测试发现,基本上3倍的时间延迟java

二、支持更大的并发:若是不采用链接池,每次链接都会打开一个端口,在大并发的状况下系统的端口资源很快就会被用完,致使没法创建新的链接安全

2.简单链接管理器

BasicHttpClientConnectionManager是个简单的链接管理器,它一次只能管理一个链接。尽管这个类是线程安全的,它在同一时间也只能被一个线程使用。BasicHttpClientConnectionManager会尽可能重用旧的链接来发送后续的请求,而且使用相同的路由。若是后续请求的路由和旧链接中的路由不匹配,BasicHttpClientConnectionManager就会关闭当前链接,使用请求中的路由从新创建链接。若是当前的链接正在被占用,会抛出java.lang.IllegalStateException异常。bash

3.链接池管理器

相对BasicHttpClientConnectionManager来讲,PoolingHttpClientConnectionManager是个更复杂的类,它管理着链接池,能够同时为不少线程提供http链接请求。Connections are pooled on a per route basis.当请求一个新的链接时,若是链接池有有可用的持久链接,链接管理器就会使用其中的一个,而不是再建立一个新的链接。服务器

PoolingHttpClientConnectionManager维护的链接数在每一个路由基础和总数上都有限制。默认,每一个路由基础上的链接不超过2个,总链接数不能超过20。在实际应用中,这个限制可能会过小了,尤为是当服务器也使用Http协议时。多线程

下面的例子演示了若是调整链接池的参数:并发

PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
    // 将最大链接数增长到200
    cm.setMaxTotal(200);
    // 将每一个路由基础的链接增长到20
    cm.setDefaultMaxPerRoute(20);
    //将目标主机的最大链接数增长到50
    HttpHost localhost = new HttpHost("www.yeetrack.com", 80);
    cm.setMaxPerRoute(new HttpRoute(localhost), 50);

    CloseableHttpClient httpClient = HttpClients.custom()
            .setConnectionManager(cm)
            .build();复制代码

4.关闭链接管理器

当一个HttpClient的实例不在使用,或者已经脱离它的做用范围,咱们须要关掉它的链接管理器,来关闭掉全部的链接,释放掉这些链接占用的系统资源。socket

CloseableHttpClient httpClient = <...>
    httpClient.close();复制代码

5. 链接回收策略

经典阻塞I/O模型的一个主要缺点就是只有当组侧I/O时,socket才能对I/O事件作出反应。当链接被管理器收回后,这个链接仍然存活,可是却没法监控socket的状态,也没法对I/O事件作出反馈。若是链接被服务器端关闭了,客户端监测不到链接的状态变化(也就没法根据链接状态的变化,关闭本地的socket)。ide

HttpClient为了缓解这一问题形成的影响,会在使用某个链接前,监测这个链接是否已通过时,若是服务器端关闭了链接,那么链接就会失效。这种过期检查并非100%有效,而且会给每一个请求增长10到30毫秒额外开销。惟一一个可行的,且does not involve a one thread per socket model for idle connections的解决办法,是创建一个监控线程,来专门回收因为长时间不活动而被断定为失效的链接。这个监控线程能够周期性的调用ClientConnectionManager类的closeExpiredConnections()方法来关闭过时的链接,回收链接池中被关闭的链接。它也能够选择性的调用ClientConnectionManager类的closeIdleConnections()方法来关闭一段时间内不活动的链接。post

public static class IdleConnectionMonitorThread extends Thread {

        private final HttpClientConnectionManager connMgr;
        private volatile boolean shutdown;

        public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr) {
            super();
            this.connMgr = connMgr;
        }

        @Override
        public void run() {
            try {
                while (!shutdown) {
                    synchronized (this) {
                        wait(5000);
                        // 关闭失效的链接
                        connMgr.closeExpiredConnections();
                        // 可选的, 关闭30秒内不活动的链接
                        connMgr.closeIdleConnections(30, TimeUnit.SECONDS);
                    }
                }
            } catch (InterruptedException ex) {
                // terminate
            }
        }

        public void shutdown() {
            shutdown = true;
            synchronized (this) {
                notifyAll();
            }
        }

    }复制代码

6. 链接存活策略

Http规范没有规定一个持久链接应该保持存活多久。有些Http服务器使用非标准的Keep-Alive头消息和客户端进行交互,服务器端会保持数秒时间内保持链接。HttpClient也会利用这个头消息。若是服务器返回的响应中没有包含Keep-Alive头消息,HttpClient会认为这个链接能够永远保持。然而,不少服务器都会在不通知客户端的状况下,关闭必定时间内不活动的链接,来节省服务器资源。在某些状况下默认的策略显得太乐观,咱们可能须要自定义链接存活策略。测试

ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() {

        public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
            // Honor 'keep-alive' header
            HeaderElementIterator it = new BasicHeaderElementIterator(
                    response.headerIterator(HTTP.CONN_KEEP_ALIVE));
            while (it.hasNext()) {
                HeaderElement he = it.nextElement();
                String param = he.getName();
                String value = he.getValue();
                if (value != null && param.equalsIgnoreCase("timeout")) {
                    try {
                        return Long.parseLong(value) * 1000;
                    } catch(NumberFormatException ignore) {
                    }
                }
            }
            HttpHost target = (HttpHost) context.getAttribute(
                    HttpClientContext.HTTP_TARGET_HOST);
            if ("www.naughty-server.com".equalsIgnoreCase(target.getHostName())) {
                // Keep alive for 5 seconds only
                return 5 * 1000;
            } else {
                // otherwise keep alive for 30 seconds
                return 30 * 1000;
            }
        }

    };
    CloseableHttpClient client = HttpClients.custom()
            .setKeepAliveStrategy(myStrategy)
            .build();复制代码


相关文章
相关标签/搜索