Httpclient核心架構設計

Http簡介

通常,我們使用IE或者safari來訪問互聯網上的內容,只需要輸入資源地址,瀏覽器便會呈現給你想要的內容。這一切的背後,都是迄今爲止在計算機領域最成功的協議–http協議。

Http協議分爲請求和響應,客戶端建立連接,接着發送請求,服務端接受並處理請求,再發送應答,再由客戶端接受並處理應答。瀏覽器是最最常見的一種客戶端,它將用戶的交互行爲作爲http請求發送,並接受服務端的應答,再將應答內容展示,一般應答都是html類型的超文本。

瀏覽器顯然不是唯一的客戶端,理論上任何遵循了http規範都可作爲客戶端。在程序裏也可以通過java api實現簡單的客戶端–使用HttpURLConnection發送http請求,並解析應答。假設應答是個html或者json,則只需要基於雙方約定的格式進行解析就能得到所需結果。

Http, tcp/ip和socket區別

Tcp/ip是傳輸層協議,而http則是建立在它之上的上層應用協議。Http聚焦在數據規範層面,tcp/ip則主要解決數據傳輸層面。如果沒有規範的應用協議,數據能從網絡裏的A節點傳到B節點,但卻無法有效識別,建立在tcp/ip上的應用協議很多,像rpc,ftp等,反過來不管應用協議有多強大最終都需要依靠傳輸層協議進行數據傳輸。 Socket則是tcp/ip的一個編程實現,在程序裏http請求(連接)最終一定需要綁定到一個具體的socket連接進行上行和下行傳輸。

整體架構

對於簡單應用,HttpURLConnection完全可以滿足。但是對於1)系統複雜度高,2)性能要求高,3)可靠性要求也高的應用,則需要一個更強大的組件。

Httpclient裏接受者稱爲爲route,併爲每個route池化若干連接。Client通過socket發送請求以及接受應答,在發送請求前和接收應答後都會經由interceptor進行鏈式處理,在httpclient裏這些interceptor被稱爲HttpProcessor,負責處理諸如設置報文頭,報文體,編碼格式等以及解析報文頭,報文體,解碼格式等http規範文本格式範疇內的事情。

HttpClient靜態結構

- HttpClient通過建造者構建出來,用戶可以通過建造者暴露出來的參數屬性方法來組織最終生成的產品屬性。HttpClients是個工廠類,用於生產HttpClient,同時也提供custom方法返回builder,由使用者組織client屬性。 - HttpClient主要由5個組件組成,分別是: 1. Closeable: 代表需要關閉的組件,client服務關閉時會回調註冊的所有Closeable組件依次關閉。用戶可以通過HttpClientBuilder#addCloseable添加自定義關閉組件。HttpClient內部利用Closeable關閉IdleConnectionEvictor以及HttpClientConnectionManager 2. IdleConnectionEvictor: 用來關閉閒置連接,它會啓動一個守護線程進行清理工作。用戶可以通過builder#evictIdleConnections開啓該組件,並通過builder#setmaxIdleTime設置最大空閒時間。 3. HttpClientConnectionManager,連接池組件,管理連接的整個生命週期。連接在連接池中創建、複用以及移除。

connection被創建出來後處於閒置狀態,由連接池管理,被lease後會校驗是否是open狀態,不是的話會進行connect,connect的過程就是將http請求(連接)綁定到socket的過程。同時連接也會因爲心跳或者過期等原因被close變成stale狀態,直至被下一次get到時或者連接滿時被清理出去。

同時連接池還能對連接進行限流–全局和單route連接數。Connection manager封裝了對連接池的具體操作,比如向連接池租用和歸還連接;還提供了基於不同schema(主要是http和https)創建不同的socket連接(ssl和plain)並且將http請求(連接)綁定到socket的能力,等等。 4. HttpRoutePlanner用來創建HttpRoute。後者代表客戶端request的對端服務器,主要包含rout的host以及proxy信息。 5. ClientExecChain代表一次完整的調用執行過程,它是一個包裝類,類似於java io類,每個包裝類完成一個特定的功能,多層嵌套完成一個系統性的功能,比如處理協議範疇的例如cookie、報文解析等,又比如處理自動重試的,等等。

連接池

  • CPool裏的連接分爲三種–available, leased和pending,分別對應空閒,佔用和堵塞三種狀態,連接池爲這三種狀態建立三個列表(List/Set)。對連接數的管理則有兩個維度,分別是全局最大數和單route最大數。全局連接和單route連接都對應三種狀態列表,CPool內部維護了route和RouteSpecificPool的映射,通過後者對單route連接進行管理,並且嚴格保證一個route只會對應一個route pool。操作(租用,釋放,阻塞或者移除等等)連接時CPool首先會依據route信息取出route pool,對其上維護的連接進行操作,之後再對CPool上的相應連接操作。RouteSpecificPool是個friend的abstract類,也就是說它是CPool隱藏起來的實現細節,對外只暴露CPool的行爲甚至用戶都可以不理會CPool只關心connection mananger。
  • 連接池對外透出的是PoolEntryFuture,後者的get方法能夠獲取一個閒置連接,或者進入堵塞等待。
  • 連接池的連接連同route信息一起被包含在PoolEntry裏返回給消費者,除此之外,PoolEntry還包含了連接的失效時間等等,超過失效時間會在下一次被get到時close。
  • CPool還有流控功能,get請求在沒有空閒連接但連接數沒達到閾值時通過連接池創建連接並池化放入available或者leased。leased連接數達到閾值時對請求進行堵塞–PoolEntryFuture#await,並且將PoolEntryFuture放入pending。其他請求釋放連接時會喚醒堵塞請求,被喚醒的請求獲取到連接後會被從pending列表中移除。 超過任何一個最大數閾值後CPool首先都會進行收縮,超過單route最大數,則收縮單route連接,超過全局最大數,則收縮全局連接。收縮的過程只會關閉空閒連接,直至連接數等於閾值-1。

執行鏈

  • MainClientExec是真正執行客戶端請求的,它位於包裝類的最裏層,它通過連接管理器向CPool requestConnection,綁定http請求到socket,通過request executor發送請求,並且還能基於keep-alive策略處理連接的複用等等。
  • ProtocolExec通過一系列的HttpProcessor處理鏈對Http消息按格式編碼以及解碼。每一個processor處理一個範疇的事情,比如處理header,content以及cookie等等。
  • RetryExec,對特定的io異常進行重連,保證可用性。特定是指除了一下四中情況的io異常以外:
InterruptedIOException
UnknownHostException
ConnectException
SSLException
  • RedirectExec,處理301,302,303和307的情況,即move和redirect。
  • ServiceUnavailableRetryExec, 返回碼爲503時進行重試。
  • BackoffStrategyExec對出現連接或者響應超時異常的route進行降級,縮小該route上連接數,能使得服務質量更好的route能得到更多的連接。降級的速度可以通過因子設置,默認是每次降級減少一半的連接數,即降級因子是0.5。

最後注意一點,以上的這些exec只有MainClientExec和ProtocolExec是默認開啓的,其他的都需要通過HttpClientBuilder設置參數開啓,具體可以參考文檔或者源碼。

調優方向

瞭解了架構原理後,就可以着手在3個方向進行調優: 1. 連接數,通過設立全局最大連接數和單route連接數,增加吞吐能力。用戶可通過HttpClientBuilder#maxConnTotal和#maxConnPerRoute分別設置。 2. 獲取連接的超時時間,調小超時時間能夠有效提高響應速度並且降低積壓請求量,但相應的也會增加請求失敗的機率。用戶可以通過RequestConfig的connectionRequestTimeout進行設置。 3. 建立連接和route響應的超時時間,調小能夠有效的降低bad request對連接的佔用,留給質量更好的請求,有效提高系統提高吞吐能力及響應速度。否則有可能在峯值期被慢請求佔滿連接池,導致系統癱瘓。兩者分別可通過RequestConfig#connectionTimeout和socketTimeout進行設置。 4. 開啓BackoffStrategyExec,對狀況差的route進行降級處理,將連接讓給其他route

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章