我們有個業務,會調用其他部門提供的一個基于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/
♦ 本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。
♣ 本部落格大多為學習筆記或讀書筆記,本文如對您有幫助,還請多推薦下此文,如有錯誤歡迎指正,互相學習,共同進步。