天天看點

高并發場景下的httpClient優化使用

我們有個業務,會調用其他部門提供的一個基于http的服務,日調用量在千萬級别。使用了httpclient來完成業務。之前因為qps上不去,就看了一下業務代碼,并做了一些優化,記錄在這裡。

先對比前後:優化之前,平均執行時間是250ms;優化之後,平均執行時間是80ms,降低了三分之二的消耗,容器不再動不動就報警線程耗盡了,清爽~

項目的原實作比較粗略,就是每次請求時初始化一個httpclient,生成一個httpPost對象,執行,然後從傳回結果取出entity,儲存成一個字元串,最後顯式關閉response和client。我們一點點分析和優化:

httpclient是一個線程安全的類,沒有必要由每個線程在每次使用時建立,全局保留一個即可。

tcp的三次握手與四次揮手兩大裹腳布過程,對于高頻次的請求來說,消耗實在太大。試想如果每次請求我們需要花費5ms用于協商過程,那麼對于qps為100的單系統,1秒鐘我們就要花500ms用于握手和揮手。又不是進階上司,我們程式員就不要搞這麼大做派了,改成keep alive方式以實作連接配接複用!

原本的邏輯裡,使用了如下代碼:

這裡我們相當于額外複制了一份content到一個字元串裡,而原本的httpResponse仍然保留了一份content,需要被consume掉,在高并發且content非常大的情況下,會消耗大量記憶體。并且,我們需要顯式的關閉連接配接,ugly。

按上面的分析,我們主要要做三件事:一是單例的client,二是緩存的保活連接配接,三是更好的處理傳回結果。一就不說了,來說說二。

提到連接配接緩存,很容易聯想到資料庫連接配接池。httpclient4提供了一個PoolingHttpClientConnectionManager 作為連接配接池。接下來我們通過以下步驟來優化:

關于keep-alive,本文不展開說明,隻提一點,是否使用keep-alive要根據業務情況來定,它并不是靈丹妙藥。還有一點,keep-alive和time_wait/close_wait之間也有不少故事。

在本業務場景裡,我們相當于有少數固定用戶端,長時間極高頻次的通路伺服器,啟用keep-alive非常合适

再多提一嘴,http的keep-alive 和tcp的KEEPALIVE不是一個東西。回到正文,定義一個strategy如下:

也可以針對每個路由設定并發數。

 注意:使用setStaleConnectionCheckEnabled方法來逐出已被關閉的連結不被推薦。更好的方式是手動啟用一個線程,定時運作closeExpiredConnections 和closeIdleConnections方法,如下所示。

這裡要注意的是,不要關閉connection。

一種可行的擷取内容的方式類似于,把entity裡的東西複制一份:

 但是,更推薦的方式是定義一個ResponseHandler,友善你我他,不再自己catch異常和關閉流。在此我們可以看一下相關的源碼:

 可以看到,如果我們使用resultHandler執行execute方法,會最終自動調用consume方法,而這個consume方法如下所示:

可以看到最終它關閉了輸入流。

通過以上步驟,基本就完成了一個支援高并發的httpclient的寫法,下面是一些額外的配置和提醒:

CONNECTION_TIMEOUT是連接配接逾時時間,SO_TIMEOUT是socket逾時時間,這兩者是不同的。連接配接逾時時間是發起請求前的等待時間;socket逾時時間是等待資料的逾時時間。

現在的業務裡,沒有nginx的情況反而比較稀少。nginx預設和client端打開長連接配接而和server端使用短連結。注意client端的keepalive_timeout和keepalive_requests參數,以及upstream端的keepalive參數設定,這三個參數的意義在此也不再贅述。

以上就是我的全部設定。通過這些設定,成功地将原本每次請求250ms的耗時降低到了80左右,效果顯著。

轉載:Norman Bai

♥ 作者:明志健緻遠

♠ 出處:http://www.cnblogs.com/study-everyday/

♦ 本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。

♣ 本部落格大多為學習筆記或讀書筆記,本文如對您有幫助,還請多推薦下此文,如有錯誤歡迎指正,互相學習,共同進步。

繼續閱讀