CloseableHttpClient填坑經(jīng)歷 資源回收 最大連接數(shù)
因?yàn)轫?xiàng)目中的一些原因管宵,部分網(wǎng)絡(luò)請(qǐng)求無(wú)法使用封裝好的RestTemplate,只好使用CloseableHttpClient壹瘟,這才有了以下的填坑經(jīng)歷钾唬,如果可以司顿,這些知識(shí)點(diǎn)學(xué)習(xí)就好馅扣,項(xiàng)目中還是使用封裝好的組件比較妥當(dāng)。
問(wèn)題場(chǎng)景:
在使用CloseableHttpClient的請(qǐng)求中娘侍,都是一樣的請(qǐng)求咖刃,只是會(huì)分發(fā)到不同的服務(wù)器,在這些服務(wù)器中憾筏,唯獨(dú)有一臺(tái)服務(wù)器在一些操作之后嚎杨,到該服務(wù)器的連接都失敗嗎,報(bào)504 Gateway Timeout氧腰;
經(jīng)過(guò)排查枫浙,排除了服務(wù)器的原因;
后續(xù)測(cè)試發(fā)現(xiàn)古拴,是若到某個(gè)服務(wù)器的某些請(qǐng)求因?yàn)槟承┰蚴纱魏舐嶂悖罄m(xù)到該服務(wù)器的請(qǐng)求都將失敗,而到該服務(wù)器的其他請(qǐng)求依舊正常黄痪;
問(wèn)題原因
具體分析過(guò)程不再贅述紧帕,直接上結(jié)果:
在CloseableHttpClient中,有最大連接數(shù)的限制桅打,默認(rèn)值為:
1是嗜、 單個(gè)Host域名,最大連接數(shù)為2挺尾;
2鹅搪、 整個(gè)客戶端內(nèi),總共最大連接數(shù)為20遭铺;
上面第一點(diǎn)是最騷的丽柿,竟然還會(huì)限制單個(gè)域名下的請(qǐng)求數(shù)恢准,一下就和問(wèn)題場(chǎng)景對(duì)上了;
問(wèn)題修復(fù)
修復(fù)一:改連接數(shù)限制
經(jīng)過(guò)分析甫题,很明顯直接把默認(rèn)的請(qǐng)求數(shù)限制改大一點(diǎn)就能解決問(wèn)題:
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(XXX);//連接池最大并發(fā)連接數(shù)
cm.setDefaultMaxPerRoute(XXX);//單域名下最大連接數(shù)
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
但是這治標(biāo)不治本顷歌,不從根本找出連接被占滿的原因,再大的連接限制也終究是會(huì)被撐爆的幔睬,且看改進(jìn)。
修復(fù)二:
我的實(shí)現(xiàn)是一個(gè)請(qǐng)求函數(shù)芹扭,其內(nèi)部基本是下面這樣的
String errMsg;
HttpResponse response = closeableHttpClient.execute(myRequest);
HttpEntity resEntity = response.getEntity();
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK && resEntity != null) {
return EntityUtils.toString(resEntity, StandardCharsets.UTF_8);
} else {
LOG.debug(JSON.toJSONString(response));
errMsg = response.getStatusLine().getReasonPhrase();
}
throw new HttpException(errMsg);
很顯然麻顶,平時(shí)使用Connection的話需要手動(dòng)關(guān)閉的連接這邊都沒有出現(xiàn),那么怎么關(guān)閉呢舱卡?
在CloseableHttpClient內(nèi)部是一個(gè)連接池辅肾,我們請(qǐng)求結(jié)束后,需要盡快釋放連接轮锥,避免后面的請(qǐng)求因?yàn)闆]有連接阻塞超時(shí)矫钓,最終失敗,而釋放連接可以從三個(gè)方面入手:
- 在execute(httpRequest)中舍杜,httpRequest是可以關(guān)閉的新娜,
httpRequest.releaseConnection();
-
HttpResponse response = closeableHttpClient.execute(myRequest)
中,execute的response是繼承了Closeable的既绩,HttpResponse 改為CloseableHttpResponse概龄,并在請(qǐng)求結(jié)束后主動(dòng)關(guān)閉; -
HttpEntity resEntity = response.getEntity();
饲握,這里的resEntity內(nèi)部實(shí)際上有一個(gè)輸入流私杜,我們?cè)?code>EntityUtils.toString(resEntity, StandardCharsets.UTF_8);已經(jīng)將該流關(guān)閉了,而若請(qǐng)求失敗的話救欧,則沒有被關(guān)閉的操作衰粹,所以可以調(diào)用EntityUtils.consumeQuietly(resEntity);
主動(dòng)關(guān)閉
所以,在源頭上避免連接被耗盡笆怠,我們需要及時(shí)釋放資源铝耻,以上修改完成代碼如下:
String errMsg;
// 自動(dòng)關(guān)閉返回
try (CloseableHttpResponse response = closeableHttpClient.execute(signedRequest)){
HttpEntity resEntity = response.getEntity();
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK
&& resEntity != null) {
// toString方法內(nèi)已關(guān)閉流
return EntityUtils.toString(resEntity, StandardCharsets.UTF_8);
} else {
// 主動(dòng)關(guān)閉內(nèi)部的輸入流
EntityUtils.consumeQuietly(resEntity);
LOG.debug(JSON.toJSONString(response));
errMsg = response.getStatusLine().getReasonPhrase();
}
} catch (Exception e){
// do your business
} finally {
// 主動(dòng)釋放連接
signedRequest.releaseConnection();
}
throw new HttpException(errMsg);
總結(jié)
經(jīng)過(guò)以上第一種修改,相當(dāng)于客戶端容量增大了骑疆,能更好的容忍請(qǐng)求時(shí)間較長(zhǎng)田篇、并發(fā)數(shù)大等情況,而第二種修改則從源頭上保證了每個(gè)連接使用的高效性箍铭,避免無(wú)用連接占用資源泊柬,請(qǐng)求結(jié)束后都能夠得到有效釋放,二者結(jié)合诈火,將最大程度的提高服務(wù)器的并發(fā)數(shù)兽赁。