一、下降延遲:若是不採用鏈接池,每次鏈接發起Http請求的時候都會從新創建TCP鏈接(經歷3次握手),用完就會關閉鏈接(4次揮手),若是採用鏈接池則減小了這部分時間損耗,別小看這幾回握手,本人通過測試發現,基本上3倍的時間延遲java
二、支持更大的併發:若是不採用鏈接池,每次鏈接都會打開一個端口,在大併發的狀況下系統的端口資源很快就會被用完,致使沒法創建新的鏈接安全
BasicHttpClientConnectionManager
是個簡單的鏈接管理器,它一次只能管理一個鏈接。儘管這個類是線程安全的,它在同一時間也只能被一個線程使用。BasicHttpClientConnectionManager
會盡可能重用舊的鏈接來發送後續的請求,而且使用相同的路由。若是後續請求的路由和舊鏈接中的路由不匹配,BasicHttpClientConnectionManager
就會關閉當前鏈接,使用請求中的路由從新創建鏈接。若是當前的鏈接正在被佔用,會拋出java.lang.IllegalStateException
異常。bash
相對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();複製代碼
當一個HttpClient的實例不在使用,或者已經脫離它的做用範圍,咱們須要關掉它的鏈接管理器,來關閉掉全部的鏈接,釋放掉這些鏈接佔用的系統資源。socket
CloseableHttpClient httpClient = <...>
httpClient.close();複製代碼
經典阻塞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();
}
}
}複製代碼
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();複製代碼