CloseableHttpClient填坑經(jīng)歷 資源回收 最大連接數(shù)
因為項目中的一些原因,部分網(wǎng)絡(luò)請求無法使用封裝好的RestTemplate,只好使用CloseableHttpClient,這才有了以下的填坑經(jīng)歷,如果可以,這些知識點學(xué)習就好,項目中還是使用封裝好的組件比較妥當。
問題場景:
在使用CloseableHttpClient的請求中,都是一樣的請求,只是會分發(fā)到不同的服務(wù)器,在這些服務(wù)器中,唯獨有一臺服務(wù)器在一些操作之后,到該服務(wù)器的連接都失敗嗎,報504 Gateway Timeout;
經(jīng)過排查,排除了服務(wù)器的原因;
后續(xù)測試發(fā)現(xiàn),是若到某個服務(wù)器的某些請求因為某些原因失敗兩次后,后續(xù)到該服務(wù)器的請求都將失敗,而到該服務(wù)器的其他請求依舊正常;
問題原因
具體分析過程不再贅述,直接上結(jié)果:
在CloseableHttpClient中,有最大連接數(shù)的限制,默認值為:
1、 單個Host域名,最大連接數(shù)為2;
2、 整個客戶端內(nèi),總共最大連接數(shù)為20;
上面第一點是最騷的,竟然還會限制單個域名下的請求數(shù),一下就和問題場景對上了;
問題修復(fù)
修復(fù)一:改連接數(shù)限制
經(jīng)過分析,很明顯直接把默認的請求數(shù)限制改大一點就能解決問題:
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(XXX);//連接池最大并發(fā)連接數(shù)
cm.setDefaultMaxPerRoute(XXX);//單域名下最大連接數(shù)
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
但是這治標不治本,不從根本找出連接被占滿的原因,再大的連接限制也終究是會被撐爆的,且看改進。
修復(fù)二:
我的實現(xiàn)是一個請求函數(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);
很顯然,平時使用Connection的話需要手動關(guān)閉的連接這邊都沒有出現(xiàn),那么怎么關(guān)閉呢?
在CloseableHttpClient內(nèi)部是一個連接池,我們請求結(jié)束后,需要盡快釋放連接,避免后面的請求因為沒有連接阻塞超時,最終失敗,而釋放連接可以從三個方面入手:
- 在execute(httpRequest)中,httpRequest是可以關(guān)閉的,
httpRequest.releaseConnection(); -
HttpResponse response = closeableHttpClient.execute(myRequest)中,execute的response是繼承了Closeable的,HttpResponse 改為CloseableHttpResponse,并在請求結(jié)束后主動關(guān)閉; -
HttpEntity resEntity = response.getEntity();,這里的resEntity內(nèi)部實際上有一個輸入流,我們在EntityUtils.toString(resEntity, StandardCharsets.UTF_8);已經(jīng)將該流關(guān)閉了,而若請求失敗的話,則沒有被關(guān)閉的操作,所以可以調(diào)用EntityUtils.consumeQuietly(resEntity);主動關(guān)閉
所以,在源頭上避免連接被耗盡,我們需要及時釋放資源,以上修改完成代碼如下:
String errMsg;
// 自動關(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 {
// 主動關(guān)閉內(nèi)部的輸入流
EntityUtils.consumeQuietly(resEntity);
LOG.debug(JSON.toJSONString(response));
errMsg = response.getStatusLine().getReasonPhrase();
}
} catch (Exception e){
// do your business
} finally {
// 主動釋放連接
signedRequest.releaseConnection();
}
throw new HttpException(errMsg);
總結(jié)
經(jīng)過以上第一種修改,相當于客戶端容量增大了,能更好的容忍請求時間較長、并發(fā)數(shù)大等情況,而第二種修改則從源頭上保證了每個連接使用的高效性,避免無用連接占用資源,請求結(jié)束后都能夠得到有效釋放,二者結(jié)合,將最大程度的提高服務(wù)器的并發(fā)數(shù)。