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