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();複製代碼


相關文章
相關標籤/搜索