java httpclient 关闭_【Java系列007】HttpClient调用:你考虑过关闭连接、并发了吗?...
你好!我是miniluo,今天和你分享使用HttpClient過程中,未考慮釋放連接和并發導致的坑。
HttpClient在項目中還是比較常見的,主要都是通過GET或POST請求第三方以獲取響應結果。前段時間還了解到也有企業用它來做爬蟲。下面我們就從兩方面來一起學習HttpClient。
強占著不放
我們先來看一張因未釋放連接導致的異常圖。
我們再來看代碼,代碼很簡單,用一個static修飾HttpClient的一個對象,也是用static的方式實例化(并發下,static實例化的對象是線程不安全的)。
1@Slf4j
2public?class?MyHttpClientTest{
3????private?static?HttpClient?client;
4????static?{
5????????RequestConfig?requestConfig?=?RequestConfig.custom()
6????????????????.setConnectTimeout(5000)
7????????????????.setConnectionRequestTimeout(3000)
8????????????????.setSocketTimeout(5000).build();
9????????client?=?HttpClientBuilder.create().setDefaultRequestConfig(requestConfig)
10????????????????.build();
11????}
12
13????@Test
14????public?void?testHttpClient(){
15????????for?(int?i?=?0;?i?
16????????????String?url?=?"http://127.0.0.1:9092/learn/product/get/12";
17????????????HttpGet?httpGet?=?new?HttpGet(url);
18????????????try?{
19????????????????HttpResponse?res?=?client.execute(httpGet);
20????????????}?catch?(IOException?e)?{
21????????????????log.error("異常:?",e);
22????????????????return;
23????????????}
24????????}
25????}
26}
我們看回異常圖,圖中有2個紅框,我們先來看第一個紅框的“[total kept alive: 0; route allocated: 2 of2; total allocated: 2 of 20]”,這個DEBUG級別日志描述的什么意思呢?也就是說這個route下共有2個連接,已用2個;pool共有20個,已用2個。再來看第二個紅框“Timeout waiting for connection from pool”,從連接池獲取連接等待超時。奇怪吧,這順序執行也會出現?這其實是前兩個連接響應結果回來后,并沒有釋放連接資源,導致后面的請求等待超時。
我們看看PoolingHttpClientConnectionManager類的構造函數,其給Pool初始化時給maxConnPerRoute和maxConnTotal設置了默認值。
1????public?PoolingHttpClientConnectionManager(
2????????final?HttpClientConnectionOperator?httpClientConnectionOperator,
3????????final?HttpConnectionFactory?connFactory,
4????????final?long?timeToLive,?final?TimeUnit?tunit){
5????????super();
6????????this.configData?=?new?ConfigData();
7????????//defaultMaxPerRoute默認為2,maxTotal默認為20
8????????this.pool?=?new?CPool(new?InternalConnectionFactory(
9????????????????this.configData,?connFactory),?2,?20,?timeToLive,?tunit);
10????????this.pool.setValidateAfterInactivity(2000);
11????????this.connectionOperator?=?Args.notNull(httpClientConnectionOperator,?"HttpClientConnectionOperator");
12????????this.isShutDown?=?new?AtomicBoolean(false);
13????}
所以當請求連接被占用2個后,后面的請求并不會獲取到連接,這么說,我們需要釋放連接資源。這里我們需要介紹EntityUtils這個類,里面包含了兩個釋放連接資源的方法consumeQuietly(HttpEntity entity)和consume(HttpEntity entity),前者內部也是請求后者完成關閉InputStream。調整后的代碼如下:
1@Test
2????public?void?testHttpClient(){
3????????for?(int?i?=?0;?i?
4????????????String?url?=?"http://127.0.0.1:9092/learn/product/get/12";
5????????????HttpGet?httpGet?=?new?HttpGet(url);
6????????????HttpResponse?res?=?null;
7????????????try?{
8????????????????res?=?client.execute(httpGet);
9????????????}?catch?(IOException?e)?{
10????????????????log.error("異常:?",?e);
11????????????????return;
12????????????}?finally?{
13????????????????if?(null?!=?res)?{
14????????????????????EntityUtils.consumeQuietly(res.getEntity());
15????????????????}
16????????????}
17????????}
18????}
調整完代碼后,我們執行后發現5個請求都能正確請求并得到響應結果。或許有同學會問到如果我調整maxConnPerRoute和maxConnTotal不也行嗎?可以,但是你的連接還是沒有釋放,當超過你設置值后也會出現無法獲得連接池的問題。
被CloseableHttpClient名字所坑
最近項目中就是使用到CloseableHttpClient實現請求第三方,踩完上面的坑后,我們在項目中寫了一個工廠類內部用枚舉方式實現單例。經過并發測試后,的確沒有出現上述坑,看了CloseableHttpClient有一個execute方法,最后也有釋放資源,所以沒有在意。直到今天寫文章對應的測試案例我才發現我被這個方法欺騙了。
CloseableHttpClient的execute()重載了多個方法,而實際調用能釋放資源的execute必須是包含ResponseHandler extends T>這個屬性,否則和原有HttpClient方式是一樣的。為何,模擬高并發下并沒有發生問題呢?這是因為項目中請求響應結果后是用下面的代碼完成數據獲取的。
1?HttpResponse?response?=?client.execute(get);
2?String?res?=?EntityUtils.toString(response.getEntity());
跟進EntityUtils.toString()方法的最底層調用,會執行InputStream關閉流,和上文說的consume方法一樣。但是這里坑就坑在,如果請求第三方超時或其他異常,則直接跳過,并沒有執行上面的toString()方法,那就不會釋放連接資源。
知道坑所在,要解決也就好辦。可以有下述兩個方案:
1、外層調用增加finally釋放連接,上文所述。
2、實例化一個ResponseHandler對象,調用實現了釋放連接資源的方法。
總結
今天我們一起學習了HttpClient和CloseableHttpClient沒有正確釋放連接,以及并發受限的問題。實際項目中如果沒有考慮到這兩個情況,則會帶來生產問題,后面的請求一直無法獲取到連接資源,以及并發量起不來。很多項目并不會有這個問題是因為HttpClient和CloseableHttpClient并不是單例的,每次使用都會是新的實例。希望通過今天的學習對你日后使用HttpClient更加得心應手。由于篇幅問題,沒有把CloseableHttpClient案例放上來,有興趣的同學可以到GitHub上下載(https://github.com/littleluo/bj-share-java.git)。
思考和討論
1、案例中,我們使用了static和枚舉的方式解決單例問題,除了這兩種方法外,還有哪些方式呢?他們各自的優缺點是什么?
2、說回前面關于分布式鎖的文章中,我們用到了redis,也提到client.close()方式歸還連接,為何是歸還連接,而不是關閉連接呢?
歡迎留言與我分享和指正!也歡迎你把這篇文章分享給你的朋友或同事,一起交流。
感謝您的閱讀,我們下節再見!
掃碼關注我們,與君共進
總結
以上是生活随笔為你收集整理的java httpclient 关闭_【Java系列007】HttpClient调用:你考虑过关闭连接、并发了吗?...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 支持复工复产 阿里云618特惠开启:最高
- 下一篇: java开发cgi_編寫CGI小結(Ja