一、前言
為何要用http連接池那?因為使用它我們可以得到以下好處:
因為使用它可以有效降低延遲和系統開銷。如果不采用連接池,每當我們發起http請求時,都需要重新發起Tcp三次握手建立鏈接,請求結束時還需要四次揮手釋放鏈接。而鏈接的建立和釋放是有時間和系統開銷的。另外每次發起請求時,需要分配一個端口號,請求完畢后在進行回收。
使用鏈接池則可以復用已經建立好的鏈接,一定程度的避免了建立和釋放鏈接的時間開銷。
二、連接池使用
public static void init() {
//1.創建連接池管理器
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(60000,//1.1
TimeUnit.MILLISECONDS);
connectionManager.setMaxTotal(1000);//1.2
connectionManager.setDefaultMaxPerRoute(50);//1.3
//2.創建httpclient對象
httpClient = HttpClients.custom()
.setConnectionManager(connectionManager)//2.1
.disableAutomaticRetries()//2.2
.build();
}
如上代碼1,我們創建了一個連接池管理器,
ClientConnectionPoolManager會維護每個路由維護和最大連接數限制。默認情況下,此實現將為每個給定路由創建不超過2個并發連接,并且總共不超過20個連接。對于許多現實應用程序,這些限制可能證明過于嚴格。但是,我們可以自由來調整連接限制。
另外構造函數中可以設置持久鏈接的存活時間TTL(timeToLive),其定義了持久連接的最大使用時間,超過其TTL值的鏈接不會再被復用。
如上代碼1.1我們設置TTL為60s(Tomcat服務器默認支持保持60s的鏈接,超過60s,會關閉客戶端的鏈接)。代碼1.2我們設置連接器最多同時支持1000個鏈接,代碼1.3設置每個路由最多支持50個鏈接。注意這里路由是指IP+PORT或者指域名。如果使用域名來訪問則每個域名有自己的鏈接池,如果使用IP+PORT訪問,則每個IP+PORT有自己的鏈接池。
如上代碼2我們基于連接池管理器創建了一個httpClient對象,下面我們就可以使用它發起http請求了。
CloseableHttpResponse response = null;
try {
//3.創建請求對象
final HttpGet httpGet = new HttpGet("http://127.0.0.1:8080/test");
RequestConfig.Builder builder = RequestConfig.custom();
builder.setSocketTimeout(3000)//3.1設置客戶端等待服務端返回數據的超時時間
.setConnectTimeout(1000)//3.2設置客戶端發起TCP連接請求的超時時間
.setConnectionRequestTimeout(3000);//3.3設置客戶端從連接池獲取鏈接的超時時間
httpGet.setConfig(builder.build());
//4.發起請求
response = httpClient.execute(httpGet);
//5.成功則解析結果
if (200 == response.getStatusLine().getStatusCode()) {
String strResult = EntityUtils.toString(response.getEntity());
System.out.println(strResult);
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//5.回收鏈接到連接池
if (null != response) {
try {
EntityUtils.consume(response.getEntity());
} catch (IOException e) {
e.printStackTrace();
}
}
}
如上代碼3創建了一個HttpGet對象,并創建RequestConfig對象來設置請求參數。
代碼3.3設置客戶端從連接池獲取鏈接的超時時間,如果在該時間內沒能從連接池獲取到連接,則拋出
ConnectionPoolTimeoutException異常。
代碼3.2設置客戶端發起TCP連接請求的超時時間,也就是創建TCP連接時候等待的時間,如果該時間內還沒完成TCP三次握手,則拋出ConnectTimeoutException異常。
代碼3.1設置客戶端等待服務端返回數據的超時時間,也就是請求發出去后,如果等待該時間服務端還沒返回結果,則拋出SocketTimeoutException異常。
代碼4則發起http請求,代碼5發現請求OK,則使用自帶工具類EntityUtils.toString解析返回值(內部讀取流結束后,會自動返還鏈接到連接池)
代碼5則當請求結束后做一個兜底鏈接歸還(如果返回狀態值不是200,則需要在這里處理鏈接歸還),注意這里不能調用response.close();因為其不是歸還鏈接到連接池,而是關閉鏈接。
三、總結
本文簡單介紹了如何使用鏈接池,使用連接池時需要注意合理設置最大鏈接數和每個路由(比如域名)對應的鏈接數,另外特別需要注意設置
setConnectionRequestTimeout參數,其決定了從連接池拿鏈接的超時時間。
另外需要注意使用鏈接池時,請求結果回來后,要記得歸還鏈接,如果鏈接得不到歸還,則首先會把連接池打滿,然后新來的請求從連接池拿不到鏈接會拋出
ConnectionPoolTimeoutException異常。
另外歸還鏈接不要調用response對象的close()方法,因為其是關閉鏈接,而不是把鏈接返回到鏈接池。需要調用EntityUtils中方法或者自己從response.getEntity().getContent()獲取流,讀取完畢(讀取完畢后會自動歸還鏈接)或者不讀取時主動調用流的close()來顯示歸還鏈接到連接池。
知識擴展:自http1.1后,http默認支持keep-alive。對于Tomcat服務器默認保持客戶端的鏈接60s,我們httpclient這邊也可以設置鏈接存活時間,最終鏈接的存活時間是取兩者中最小的。
對于過期鏈接的處理,當Tomcat主動關閉鏈接時,httpclient 4.4之前是每次在復用鏈接前進行檢查鏈接是否可用,http4.4后,是自上次使用連接以來所經過的時間超過已設置的超時時(默認超時設置為2000ms),才檢查連接。如果發現鏈接不可用,則從鏈接池剔除,在創建新的鏈接。
當客戶端設置的TTL到期時(此時Tomcat容器沒有主動關閉鏈接時),在每次發起請求時,會檢查鏈接是否已經過期,如果過期(雖然鏈接本身是可以用的),則也主動關閉鏈接,然后從鏈接池剔除,在創建新的鏈接。
另外我們可以實現自己的
ConnectionKeepAliveStrategy來給不同的域名設置不同的鏈接存活策略。