背景
談到http client,可能大多數(shù)想到就是apache的那個http client 或者jdk自帶的urlconnection,也許有人會考慮使用netty
無論如何,jetty的高性能實現(xiàn)總歸是讓人感到好奇,接下來我們一探究竟
樣例
我們結(jié)合樣例代碼具體分析
- 初始化
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在調(diào)用 httpClient.send(exchange);后不會象往常一樣等待返回而是立即返回, 如果有結(jié)果或者超時會通過上面的callback通知到APP
httpclient的原理及實現(xiàn)
1 ) httpclient的模型
- SelectConnector: 作為一個connection管理器,封裝了selector和connection
-
HttpDestination:一個host的抽象一個HttpClient會連接到多個HttpDestination
- HttpExchange:一次http請求的封裝,一個HttpDestination會有多個HttpExchange以及多個AsyncHttpConnection
-
AsyncHttpConnection:HttpClient對某個HttpDestination的一個網(wǎng)絡連接,底層包含一個對應的socket, 可復用來完成多次請求, 如果空閑太久會被廢棄
-
SelectChannelEndPoint:socket的封裝,AsyncHttpConnection和SelectChannelEndPoint一一對應, 但AsyncHttpConnection承載了更多的東西
-
HttpGenerator:生成http request,在jetty server中負責生成http response
-
HttpParser: 解析http response, 在jetty server中負責解析http request
-
ThreadPool: 線程池,httpclient需要使用線程池配合完成無阻塞IO,這個會在后面的httpclient整體架構(gòu)分析中詳述
- Timeout:一個已時間排序的鏈表結(jié)構(gòu),鏈表中存儲需要過期執(zhí)行的task,這個會在后面流程分析詳述
2) httpclient的整體架構(gòu)
http client 分為3組線程配合完成
-
selector線程組:數(shù)目
可設置,默認為1,從_change隊列中獲取socket注冊并
掃描操作系統(tǒng)級別的網(wǎng)絡事件, 通常是socket
可讀, 可寫的信息,一旦發(fā)現(xiàn)有socket可讀寫,會將相關socket任務丟入_jobs隊列供worker線程執(zhí)行
-
worker線程組:數(shù)目
根據(jù)并發(fā)的情況決定,從
_jobs隊列獲取任務,如果任務阻塞會丟入_changes隊列異步等待通知再干活
- tick線程:數(shù)目1個,專門用于監(jiān)控超時的請求以及空閑太久的連接
- 所有的線程都來自線程池,所以線程池最小為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根據(jù)httpexchange獲取對應http destination,并調(diào)用其send方法
- 2.2.1 將次請求加入請求超時鏈表
- 2.2.2 - 2.2.3 獲取空閑連接,如果沒有,則產(chǎn)生一個新的連接,并調(diào)用select進行注冊,否則直接使用該連接,并將此連接丟入 _jobs隊列讓worker線程完成請求
- 此時客戶端就這樣無阻塞的完成了
- 1-3 selector線程從_change隊列獲取到新的socket, 開始實例化SelectChannelEndPoint
- 4 通知http desination連接完成,于是http detination將次連接丟入連接超時鏈表
- 5-6 將此連接/請求丟入_jobs隊列供worker線程使用
- 其實在selector線程內(nèi)部還有一個該死的任務來處理空閑太久的socket,這個其實和tick線程有些重復了,我想這主要是因為jetty http client復用jetty server中select的結(jié)果
3.2.3)worker線程又如何參與這個場景
-
worker線程從隊列中獲取任務
- 1.1 通過此連接發(fā)送請求,請求內(nèi)容http generator產(chǎn)生
- 1.2 一發(fā)完請求立即通過http parser讀取響應,如果服務器夠快,通常會讀到響應
- 1.3 如果服務器不能及時響應,那么調(diào)用SelectChannelEndPoint的updateKey。向select更新此時感興趣讀, 并等待select異步通知
- 此時worker線程并不會阻塞等待服務返回,而是返回到線程池中去完成別的請求任務
-
輪詢兩個鏈表_timeoutQ、
_idleTimeoutQ,沒啥事休眠200ms
-
請求超時鏈表_timeoutQ
- 1 從鏈表中刪除自己
- 2 執(zhí)行鏈表取出的task,一個http exchang中匿名內(nèi)部類實例
- 2.1 執(zhí)行APP 定義的callback: onExpire函數(shù)
- 2.2 http desination專門維護一個exchange list來跟蹤進行中的請求,此時調(diào)用其exchangeExpired, 刪除list中該請求(可能此時list并沒有該請求)
- 2.3 關閉連接
-
連接超時鏈表_idleTimeoutQ
- 1從鏈表中刪除自己
- 2 關閉連接
- http desination 維護了兩個list:_connections和 _idle,前者跟蹤該host的所有連接, 后者跟蹤該host的所有空閑連接,此時也會從這兩個list刪除連接
小結(jié)
從jetty http client應該能感知到一個高性能的客戶端的某種設計模式
- worker 線程異步干活,使得app線程無阻塞,app線程通常在web 應用中也是一種服務線程,所以無阻塞特別重要, 想想在jetty server中使用jetty client的場景
-
select 線程通知網(wǎng)絡ready事件,使得worker線程無阻塞,如果沒有select線程,worker線程也失去了意義, 對于app線程來說無非是壓力堆積到了worker線程這邊,worker線程遲早是瓶頸
- tick線程,一種解決超時問題的設計
但這種模式未必適合那種性能很好且穩(wěn)定的cache server,比如redis,memcache之類,如果后端處理夠快, 少量線程甚至單線程+隊列都能work,但無論如何比起常規(guī)的連接池模式強了不少
更多文章、技術交流、商務合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
