1.為什麼要用Http連線池
1、降低延遲:如果不採用連線池,每次連線發起Http請求的時候都會重新建立TCP連線(經歷3次握手),用完就會關閉連線(4次揮手),如果採用連線池則減少了這部分時間損耗,別小看這幾次握手,本人經過測試發現,基本上3倍的時間延遲
2、支援更大的併發:如果不採用連線池,每次連線都會開啟一個埠,在大併發的情況下系統的埠資源很快就會被用完,導致無法建立新的連線
2.簡單連線管理器
BasicHttpClientConnectionManager
是個簡單的連線管理器,它一次只能管理一個連線。儘管這個類是執行緒安全的,它在同一時間也只能被一個執行緒使用。BasicHttpClientConnectionManager
會盡量重用舊的連線來傳送後續的請求,並且使用相同的路由。如果後續請求的路由和舊連線中的路由不匹配,BasicHttpClientConnectionManager
就會關閉當前連線,使用請求中的路由重新建立連線。如果當前的連線正在被佔用,會丟擲java.lang.IllegalStateException
異常。
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的例項不在使用,或者已經脫離它的作用範圍,我們需要關掉它的連線管理器,來關閉掉所有的連線,釋放掉這些連線佔用的系統資源。
CloseableHttpClient httpClient = <...>
httpClient.close();複製程式碼
5. 連線回收策略
經典阻塞I/O模型的一個主要缺點就是隻有當組側I/O時,socket才能對I/O事件做出反應。當連線被管理器收回後,這個連線仍然存活,但是卻無法監控socket的狀態,也無法對I/O事件做出反饋。如果連線被伺服器端關閉了,客戶端監測不到連線的狀態變化(也就無法根據連線狀態的變化,關閉本地的socket)。
HttpClient為了緩解這一問題造成的影響,會在使用某個連線前,監測這個連線是否已經過時,如果伺服器端關閉了連線,那麼連線就會失效。這種過時檢查並不是100%有效,並且會給每個請求增加10到30毫秒額外開銷。唯一一個可行的,且does not involve a one thread per socket model for idle connections的解決辦法,是建立一個監控執行緒,來專門回收由於長時間不活動而被判定為失效的連線。這個監控執行緒可以週期性的呼叫ClientConnectionManager
類的closeExpiredConnections()
方法來關閉過期的連線,回收連線池中被關閉的連線。它也可以選擇性的呼叫ClientConnectionManager
類的closeIdleConnections()
方法來關閉一段時間內不活動的連線。
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();複製程式碼