使用HTTPclient保持長連接
HTTPclient保持長連接
首先解釋一下什么是長連接
當我們向一臺服務(wù)器發(fā)起請求時,我們需要和對方建立一條通道,去傳輸數(shù)據(jù),所謂的短連接,就是說我們建立起了通道,然后在傳輸完數(shù)據(jù),就把通道摧毀,下次需要的時候再重新去建立通道。
長連接呢,就是指,我們建立了一條通道,傳遞完數(shù)據(jù)后,先不摧毀,下次如果還需要傳輸數(shù)據(jù),就復(fù)用這條通道。
因為通道的建立是需要花費時間的,所以長連接的優(yōu)勢就在于響應(yīng)速度快,但是服務(wù)器壓力大,因為同時有很多人在向服務(wù)器建立通道,即便有些通道已經(jīng)傳輸完數(shù)據(jù)了,由于長連接的原因,通道也不會被摧毀;短連接呢,則是,響應(yīng)速度慢,服務(wù)器壓力小。
由于現(xiàn)在更多的是強調(diào)用戶的體驗,所以長連接目前是最常見的。
如何在java中實現(xiàn)一個長連接呢
其實很簡單,只需要在請求的請求頭中加入特定的參數(shù) :“Connection”:"keep-alive"即可。這樣如果對方支持長連接的話,那么這個連接就會保持長連接了。
問題的關(guān)鍵就來了,在一次壓測某個https請求響應(yīng)速度的代碼中,我發(fā)現(xiàn)了,當對方響應(yīng)數(shù)據(jù)為null,也就是responseBody中帶的數(shù)據(jù)為null時,響應(yīng)速度特別快,大概在5ms左右,但是一旦對方返回了響應(yīng)數(shù)據(jù),本次響應(yīng)就可能達到了20ms。
然后請運維同事抓包,發(fā)現(xiàn)每次連接,都會耗費時間在用戶認證上,其實也就是從某個方面反應(yīng)出,每次都是新建立了一個連接。
HttpPost httpPost = new HttpPost("xxxxx"); httpPost.addHeader("Connection", "keep-alive"); CloseableHttpClient httpClient = null; for(int i =0 ;i<5000;i++){ long t1 = System.currentTimeMillis(); CloseableHttpResponse response = httpClient.execute(httpPost); long t2 = System.currentTimeMillis(); }
上了一段簡單的代碼,表示一下大概的邏輯,實際的壓測代碼還包括了線程池,連接池的完整參數(shù)等等,如果需要的可以留言或者翻看本人的其他的博客,有寫線程池和連接池。
就是這樣一段代碼,導(dǎo)致每次連接都是新建立的連接,那么原因是什么呢,我們先上代碼:
for (int i = 0; i<5000;i++){ long t1 = System.currentTimeMillis(); CloseableHttpResponse response = httpClient.execute(httpPost); if(null != response.getEntity()){ EntityUtils.consume(response.getEntity()); } long t2 = System.currentTimeMillis(); }
我們只需要簡單加上三行代碼,就可以解決這個問題了,這是為什么呢,讓我們點進去源碼看一下
這么一看, 其實這個方法也并沒有做什么,只是簡單的取到了流去關(guān)閉,為什么就保持長連接了呢。
后來仔細讀http連接的原理才得知,當一個連接建立,響應(yīng)數(shù)據(jù)時,會封裝CloseableHttpResponse這個對象里面,其中的Entity對象就是包含著響應(yīng)體的數(shù)據(jù),我們需要用流去獲取。如果你不去獲取,那么這個數(shù)據(jù)就會存在于這個對象中,連接池就會認為,這個通道里有未處理的數(shù)據(jù),然后它不會去復(fù)用這個通道,而是選擇重建一個通道。這就完美解釋了為什么壓測時,對方返回null時,響應(yīng)速度特別快,而攜帶返回數(shù)據(jù)時,響應(yīng)速度特別慢了。
那么再仔細想想,為什么我們平常不知道這個知識點,卻從來沒有報過錯呢,那是因為正常情況下,我們都是需要會對response做處理,比如String responseContent = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); 類似這種,我們點進源碼看,其實也是取到了流,并做了關(guān)閉操作。平常還是要多閱讀源碼,想想源碼。
httpclient因為保持永久長連接造成連接吊死的問題
httpclient使用了連接池,如果沒有設(shè)置keep-alive策略,PoolingHttpClientConnectionManager會默認使用永久連接。
最近在調(diào)用京東api時,發(fā)現(xiàn)一個請求開始是可以獲取到數(shù)據(jù)的,但隔了兩分鐘后再請求就會出現(xiàn)read timeout異常。
對比請求成功和請求失敗的日志后發(fā)現(xiàn),請求成功的有以下日志“Connection: keep-alive”,“Connection can be kept alive indefinitely”;但請求失敗的卻打印“Shutdown connection”,“Connection discarded”。
每次失敗后再請求都會成功。因此推測中應(yīng)該是對方服務(wù)器端禁止長連接,當連接到達一定時間會就會斷開。
后來上網(wǎng)找到keep-alive策略的代碼。
添加策略后,問題解決
ConnectionKeepAliveStrategy keepAliveStrategy = new ConnectionKeepAliveStrategy() { @Override public long getKeepAliveDuration(HttpResponse response, HttpContext context) { HeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE)); while (it.hasNext()) { HeaderElement he = it.nextElement(); String param = he.getName(); String value = he.getValue(); if (value != null && param.equalsIgnoreCase("timeout")) { try { return Long.parseLong(value) * 1000; } catch (NumberFormatException ignore) { } } } HttpHost target = (HttpHost) context.getAttribute(HttpClientContext.HTTP_TARGET_HOST); if ("bizapi.jd.com ".equalsIgnoreCase(target.getHostName())) { return 60 * 1000; } else { return 300 * 1000; } CloseableHttpClient httpClient = httpClientBuilder.setConnectionManager(pollingConnectionManager) .setKeepAliveStrategy(keepAliveStrategy).setDefaultRequestConfig(defaultRequestConfig).build();
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Jmeter后置處理器實現(xiàn)過程及方法應(yīng)用
這篇文章主要介紹了Jmeter后置處理器實現(xiàn)過程及方法應(yīng)用,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-09-09SpringBoot?整合RabbitMq?自定義消息監(jiān)聽容器來實現(xiàn)消息批量處理
Spring Boot中提供了默認的監(jiān)聽器容器,但是有時候我們需要自定義監(jiān)聽器容器,來滿足一些特殊的需求,比如批量獲取數(shù)據(jù),這篇文章主要介紹了SpringBoot?整合RabbitMq?自定義消息監(jiān)聽容器來實現(xiàn)消息批量處理,需要的朋友可以參考下2023-04-04mybatis新增save結(jié)束后自動返回主鍵id詳解
這篇文章主要介紹了mybatis新增save結(jié)束后自動返回主鍵id詳解,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12spring boot中內(nèi)嵌redis的使用方法示例
這篇文章主要給大家介紹了關(guān)于spring boot中內(nèi)嵌redis使用的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-06-06IO流概述分類字節(jié)流寫數(shù)據(jù)三種方式及問題分析
這篇文章主要為大家介紹了IO流概述分類字節(jié)流寫數(shù)據(jù)三種方式及寫數(shù)據(jù)問題分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12