1. 程式人生 > >jetty http client 實現分析

jetty http client 實現分析

背景

談到http client,可能大多數想到就是apache的那個http client 或者jdk自帶的urlconnection,也許有人會考慮使用netty

無論如何,jetty的高效能實現總歸是讓人感到好奇,接下來我們一探究竟

樣例

我們結合樣例程式碼具體分析

  • 初始化
httpClient = new HttpClient();
httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
httpClient.setMaxConnectionsPerAddress(10);
httpClient.setThreadPool(new QueuedThreadPool(20)); // max 20 threads
httpClient.setTimeout(5000); // 5 seconds timeout; if no server reply, the request expire
httpClient.start();


  • 執行
               ContentExchange exchange = new ContentExchange(true) {
			@Override
			protected void onResponseComplete() throws IOException {
				if (getResponseStatus() == 200) {
					String content = getResponseContent();
					System.out.println(content);
				}
			}

			@Override
			protected void onExpire() {
				System.out.println("time out");
			}
		};
		exchange.setMethod("GET");
		exchange.setURL("http://127.0.0.1:8080/simple?id=x");
		httpClient.send(exchange);

程式碼分為兩段
  • 初始化:設定httpclient 
  • 執行:例項化ContentExchange,定義callback,本例定義了兩個常用的callback:onResponseComplete 和onExpire,更多的callbac可參考官方文件
  • APP在呼叫httpClient.send(exchange);後不會象往常一樣等待返回而是立即返回, 如果有結果或者超時會通過上面的callback通知到APP

httpclient的原理及實現

1 )httpclient的模型


  • SelectConnector: 作為一個connection管理器,封裝了selector和connection
  • HttpDestination:一個host的抽象一個HttpClient會連線到多個HttpDestination
  • HttpExchange:一次http請求的封裝,一個HttpDestination會有多個HttpExchange以及多個AsyncHttpConnection
  • AsyncHttpConnection:HttpClient對某個HttpDestination的一個網路連線,底層包含一個對應的socket, 可複用來完成多次請求, 如果空閒太久會被廢棄
  • SelectChannelEndPoint:socket的封裝,AsyncHttpConnection和SelectChannelEndPoint一一對應, 但AsyncHttpConnection承載了更多的東西
  • HttpGenerator:生成http request,在jetty server中負責生成http response
  • HttpParser: 解析http response, 在jetty server中負責解析http request
  • ThreadPool: 執行緒池,httpclient需要使用執行緒池配合完成無阻塞IO,這個會在後面的httpclient整體架構分析中詳述
  • Timeout:一個已時間排序的連結串列結構,連結串列中儲存需要過期執行的task,這個會在後面流程分析詳述

2)httpclient的整體架構


http client 分為3組執行緒配合完成

  • selector執行緒組:數目可設定,預設為1,從_change佇列中獲取socket註冊並掃描作業系統級別的網路事件, 通常是socket可讀, 可寫的資訊,一旦發現有socket可讀寫,會將相關socket任務丟入_jobs佇列供worker執行緒執行
  • worker執行緒組:數目根據併發的情況決定,從_jobs佇列獲取任務,如果任務阻塞會丟入_changes佇列非同步等待通知再幹活
  • tick執行緒:數目1個,專門用於監控超時的請求以及空閒太久的連線
  • 所有的執行緒都來自執行緒池,所以執行緒池最小為3,否則無法work


3) 典型的場景分析
     模擬一次請求

   3.1 )httpclient初始化

  • 1-2設定兩個超時連結串列,一個是超時請求連結串列,一個是超時連線連結串列
  • 3 啟動httpbuffer
  • 4 啟動執行緒池
  • 5 啟動SelectConnector,此時會啟動selector執行緒任務
  • 6 啟動tick執行緒任務

  3.2)jetty http client runtime 

  3.2.1)httpClient.send(exchange)到底幹了什麼

  • 1-2 正如樣例程式碼所示,APP設定HttpExchange,然後httpclient的send方法
  • 2.1-2.2 httpclient根據http exchange獲取對應http destination,並呼叫其send方法
  • 2.2.1 將次請求加入請求超時連結串列
  • 2.2.2 - 2.2.3 獲取空閒連線,如果沒有,則產生一個新的連線,並呼叫select進行註冊,否則直接使用該連線,並將此連線丟入     _jobs佇列讓worker執行緒完成請求
  • 此時客戶端就這樣無阻塞的完成了
3.2.2)select執行緒如何參與這個場景


  • 1-3 selector執行緒從_change佇列獲取到新的socket, 開始例項化SelectChannelEndPoint
  • 4 通知http desination連線完成,於是http detination將次連線丟入連線超時連結串列
  • 5-6 將此連線/請求丟入_jobs佇列供worker執行緒使用
  • 其實在selector執行緒內部還有一個該死的任務來處理空閒太久的socket,這個其實和tick執行緒有些重複了,我想這主要是因為jetty http client複用jetty server中select的結果

3.2.3)worker執行緒又如何參與這個場景


  • worker執行緒從佇列中獲取任務
  • 1.1 通過此連線傳送請求,請求內容http generator產生
  • 1.2 一發完請求立即通過http parser讀取響應,如果伺服器夠快,通常會讀到響應
  • 1.3 如果伺服器不能及時響應,那麼呼叫SelectChannelEndPoint的updateKey。向select更新此時感興趣讀, 並等待select非同步通知
  • 此時worker執行緒並不會阻塞等待服務返回,而是返回到執行緒池中去完成別的請求任務
3.2.4)tick執行緒又幹了什麼


  • 輪詢兩個連結串列_timeoutQ、_idleTimeoutQ,沒啥事休眠200ms
  • 請求超時連結串列_timeoutQ
      • 1 從連結串列中刪除自己
      • 2 執行連結串列取出的task,一個http exchang中匿名內部類例項
      • 2.1 執行APP 定義的callback: onExpire函式
      • 2.2 http desination專門維護一個exchange list來跟蹤進行中的請求,此時呼叫其exchangeExpired, 刪除list中該請求(可能此時list並沒有該請求)
      • 2.3 關閉連線
  • 連線超時連結串列_idleTimeoutQ
      • 1從連結串列中刪除自己
      • 2 關閉連線
      • http desination 維護了兩個list:_connections和 _idle,前者跟蹤該host的所有連線, 後者跟蹤該host的所有空閒連線,此時也會從這兩個list刪除連線

小結

從jetty http client應該能感知到一個高效能的客戶端的某種設計模式

  • worker 執行緒非同步幹活,使得app執行緒無阻塞,app執行緒通常在web 應用中也是一種服務執行緒,所以無阻塞特別重要, 想想在jetty server中使用jetty client的場景
  • select 執行緒通知網路ready事件,使得worker執行緒無阻塞,如果沒有select執行緒,worker執行緒也失去了意義, 對於app執行緒來說無非是壓力堆積到了worker執行緒這邊,worker執行緒遲早是瓶頸
  • tick執行緒,一種解決超時問題的設計

但這種模式未必適合那種效能很好且穩定的cache server,比如redis,memcache之類,如果後端處理夠快, 少量執行緒甚至單執行緒+佇列都能work,但無論如何比起常規的連線池模式強了不少