httpclient的連接池
為什么要使用httpclient連接池
連接池是為了復(fù)用連接而存在的,就像線程池一樣,創(chuàng)建了的線程在執(zhí)行完成任務(wù)后不銷毀,而是放入池中待命,以便執(zhí)行下次任務(wù)的時(shí)候可以直接從池中取出線程執(zhí)行,而不是先創(chuàng)建線程再執(zhí)行,省去了創(chuàng)建線程帶來(lái)的開(kāi)銷和時(shí)間。http連接也是一樣的思路,http1.1支持了keep-alive,我們?cè)賹?duì)同一個(gè)網(wǎng)址進(jìn)行請(qǐng)求的時(shí)候,就可以不用每次都建立連接;
我們都知道,http建立連接的過(guò)程是比較繁瑣的,要經(jīng)歷3次握手和4次揮手,那么省去這個(gè)建立連接的過(guò)程在高并發(fā)的時(shí)候就會(huì)比較的有必要;同時(shí),類似線程池,總有一個(gè)最大的線程數(shù),和線程失效時(shí)間,因?yàn)樵谌蝿?wù)空閑的時(shí)候,這些空閑線程占用系統(tǒng)資源,所以我們要釋放空閑時(shí)間長(zhǎng)的線程。同樣對(duì)于http連接,使用的是tcp的長(zhǎng)連接,但是長(zhǎng)連接的持有是非常耗資源的,特別是對(duì)于服務(wù)端,鏈接數(shù)是有限的,所以我們同樣需要釋放一定時(shí)間空閑的連接;
什么時(shí)候使用httpclient連接池
對(duì)自己的系統(tǒng)有正確的預(yù)估:
- 調(diào)用的請(qǐng)求是否對(duì)同一個(gè)host大量請(qǐng)求;因?yàn)橹挥袑?duì)于同一個(gè)host,長(zhǎng)連接才可能建立,如果每次請(qǐng)求一會(huì)一個(gè)http://a.com 一會(huì)一個(gè) http://b.com 這樣是起不到連接池的效果的,并且http/https也是不能共用的,因?yàn)樗麄兊亩丝诓煌?/li>
- 是否請(qǐng)求會(huì)達(dá)到一定的量級(jí);如果請(qǐng)求的數(shù)量不高,連接池體現(xiàn)不出多大的效果;如果請(qǐng)求達(dá)到一定的量級(jí),可能成為系統(tǒng)瓶頸或有較大影響的時(shí)候,可以嘗試使用;
- 對(duì)請(qǐng)求的系統(tǒng)有一定的了解;長(zhǎng)連接是雙向的,客戶端維護(hù)連接,服務(wù)端同樣需要維護(hù)連接,并且服務(wù)端服務(wù)的可能不止一個(gè)客戶端,就怕到時(shí)候這邊使用的連接池把服務(wù)端的連接占滿了,導(dǎo)致服務(wù)端無(wú)法為其他的客戶端提供服務(wù),這個(gè)鍋可能會(huì)扣在自己的頭上。
如何使用httpclient
httpclient給我們提供了PoolingHttpClientConnectionManager這個(gè)類幫助我們來(lái)管理連接(版本4.5及以上)。在我們使用httpclient的時(shí)候,如果配置了這個(gè)連接管理,那么就會(huì)通過(guò)這個(gè)來(lái)按host管理連接;
還是一樣,最簡(jiǎn)單的用法先來(lái)一個(gè)使用單例的:
public final class HttpClientUtils {
private static CloseableHttpClient client;
public static CloseableHttpClient getHttpClient() {
if(client == null) {
synchronized(HttpClientUtils.class) {
if(client == null) {
// 先設(shè)置http連接的一些配置
requestConfig = RequestConfig.custom()
// 從連接池獲取連接的超時(shí)時(shí)間
.setConnectionRequestTimeout(3000)
// 建立連接的超時(shí)時(shí)間
.setConnectTimeout(3000)
// 請(qǐng)求的超時(shí)時(shí)間
.setSocketTimeout(3000).build();
// 有的地方會(huì)配置一堆的https ssl的策略,點(diǎn)進(jìn)這個(gè)構(gòu)造函數(shù)他默認(rèn)已經(jīng)配置了,所以不用再配;
PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager();
// 配置最大的連接數(shù)
manager.setMaxTotal(300);
// 每個(gè)路由最大連接數(shù),路由是根據(jù)host來(lái)管理的,所以這里的數(shù)量不太容易把握;
manager.setDefaultMaxPerRoute(20);
client = HttpClients.custom().
setConnectionManager(manager).
setDefaultRequestConfig(requestConfig).
build();
}
}
}
return client;
}
}
這樣每次要使用http請(qǐng)求的時(shí)候,從這個(gè)工具中獲取客戶端來(lái)使用
HttpClientUtils.getHttpClient();
為什么要使用單例呢?其實(shí)直接HttpClients.custom().setDefaultRequestConfig(requestConfig).build();同樣也是使用了連接池的,可以看build中的源碼,沒(méi)有設(shè)置manager的時(shí)候默認(rèn)有一個(gè)。既然要管理連接池,那么這個(gè)管理器就只能有一個(gè),不然管理就亂了,所以我們?cè)谑褂玫臅r(shí)候,只需要要一個(gè)CloseableHttpClient,這個(gè)client配置一個(gè)連接池的管理,每次使用都去找他才能達(dá)到連接管理的目的,否則這樣寫(xiě):
public static CloseableHttpClient getHttpClient() {
return HttpClients.custom().setDefaultRequestConfig(requestConfig).build();
}
每次使用的時(shí)候都新建一個(gè)客戶端,每個(gè)客戶端是獨(dú)立的,這樣的話每次使用都完全是從頭來(lái)一次。就好像使用線程池的時(shí)候,每次都是一個(gè)新的ExecutorService。
我見(jiàn)過(guò)的連接池配置人家寫(xiě)了一堆這里就這?
的確我見(jiàn)過(guò)人家配置了一堆的東西:
- https/http協(xié)議策略,這個(gè)在
PoolingHttpClientConnectionManager的空參數(shù)構(gòu)造函數(shù)中就有;除非自己需要一些高級(jí)的自定義,否則不用重復(fù)添加; -
HttpRequestRetryHandler配置的重試策略,對(duì)于個(gè)人來(lái)說(shuō)像這樣的請(qǐng)求不太喜歡重試,失敗自己處理比較好; - 有一個(gè)定時(shí)任務(wù)的線程定時(shí)檢測(cè)超過(guò)多長(zhǎng)時(shí)間空閑的連接并銷毀;
對(duì)于第三點(diǎn),當(dāng)我看到人家自己實(shí)現(xiàn)的定時(shí)檢測(cè)任務(wù)的時(shí)候,我就在想,一個(gè)成熟的框架,人家不知至于想不到這點(diǎn),那么就去看看它到底有沒(méi)有做這件事。果然,框架的確考慮到了,但是這個(gè)默認(rèn)沒(méi)有開(kāi)啟,先看源碼:在類 HttpClientBuilder中
if (this.evictExpiredConnections || this.evictIdleConnections) {
final IdleConnectionEvictor connectionEvictor = new IdleConnectionEvictor((HttpClientConnectionManager)connManagerCopy, this.maxIdleTime > 0L ? this.maxIdleTime : 10L, this.maxIdleTimeUnit != null ? this.maxIdleTimeUnit : TimeUnit.SECONDS, this.maxIdleTime, this.maxIdleTimeUnit);
closeablesCopy.add(new Closeable() {
public void close() throws IOException {
connectionEvictor.shutdown();
try {
connectionEvictor.awaitTermination(1L, TimeUnit.SECONDS);
} catch (InterruptedException var2) {
Thread.currentThread().interrupt();
}
}
});
connectionEvictor.start();
}
可以看到,在evictExpiredConnections 或者 evictIdleConnections其中一個(gè)屬性是true的時(shí)候,就會(huì)開(kāi)啟定時(shí)檢測(cè)關(guān)閉連接的任務(wù),那么我們就可以使用這樣的方式來(lái)開(kāi)啟它:
client = HttpClients.custom().
setConnectionManager(manager).
setDefaultRequestConfig(requestConfig).
// 簡(jiǎn)單開(kāi)啟
evictExpiredConnections().
// 如果還要自定義超時(shí)時(shí)間(可以看到它默認(rèn)的是10s)
evictIdleConnections(30L, TimeUnit.SECONDS).
build();
關(guān)于這些東西的使用,官方文檔也有描述,但是描述的時(shí)候,他不會(huì)和你解釋為什么,所以有的時(shí)候,看看源碼就能理解它為什么要這樣做以及他是如何做到這些的,httpclient的配置可不止這些,如果使用的話可以先看看它有沒(méi)有幫我們實(shí)現(xiàn),沒(méi)有的話再去自己做;