1. 程式人生 > >超高效能管線式HTTP請求

超高效能管線式HTTP請求

這裡的高效能指的就是網絡卡有多快請求傳送就能有多快,基本上一般的伺服器在一臺客戶端的壓力下就會出現明顯延時。

該篇實際是介紹pipe管線的原理,下面主要通過其高效能的測試實踐,解析背後資料流量及原理。最後附帶一個簡單的實現

 

實踐

先直接看對比測試方法

測試內容單一客戶的使用盡可能快的方式向伺服器傳送一定量(10000條)請求,並接收返回資料

對於單一客戶端對伺服器進行http請求,一般我們的方式

1:單程序或執行緒輪詢請求(這個效能自然很低,原因會講到,也不用測試)

2:多條執行緒提前準備資料等待訊號(對客戶端效能要求較高)

3:提前準備一組執行緒同時輪詢操作

4:使用系統/平臺自帶非同步傳送機制(實際就是平臺執行緒池的方式,傳送與接收使用從執行緒池中的不同執行緒)

 

對於測試方案1,及方案2測試中效能較低沒有可比性,後面測試不會展示其結果

以下展示後面2種測試方法及當前要說的管線式的方式

  • 先講管線式(pipe)測試方案(原理在後面會講到),測試中使用100條管線(管道),實際上更少甚至一條管線也是能達到近似的效能,不過多數伺服器nginx限制一條管可以持續傳送request的數量(大部分是100也有部分會是200或是更高),每條管線傳送100個請求。
  • 然後是執行緒組的方式準備100條執行緒(100條執行緒並不是很多不會對系統本身有明顯影響),每條執行緒輪詢傳送100個request。
  • 非同步方式的方式,10000全部提交發送執行緒,由執行緒池控制接收。

 

測試環境:普通家用PC,i5 4核,12G ,100Mb電信頻寬

 

測試資料:

GET http://www.baidu.com HTTP/1.1

Content-Type: application/x-www-form-urlencoded

Host: www.baidu.com

Connection: Keep-Alive

 

這裡就是測試最常用的baidu,如果測試介面效能不佳,大部分請求會在應用伺服器排隊,難以直觀提現pipe的優勢(其實就是還沒有用到pipe的能力,伺服器就先阻塞了) 

 

下文中所有關於pipe的測試都是使用PipeHttpRuner  (http://www.cnblogs.com/lulianqi/p/8167843.html 為該測試工具的下載地址,使用方法及介紹)

 

先直接看管道式的表現:(截圖全部為windows自帶工作管理員及資源管理器)

 

 

先解釋下截圖含義,後面的截圖也都是同樣的含義

 

第一副為工作管理員的截圖實線為接收資料,虛線為傳送資料,取樣0.5s,每一個正方形的刻度為1.5s(因為工作管理員繪圖策略速率上升太快過高的沒有辦法顯示,不過還是可以看到時間線)

第二副為資源管理器,添加了3個取樣器,紅色為CPU佔用率,藍色為網路接收速率,綠色為網路傳送速率。

 

測試中 一次原始請求大概130位元組,加上tcp,ip包頭,10000條大概也只有1.5Mb(包頭不會太多因為管道式請求裡會有多個請求放到一個包裡的情況,不過大部分伺服器無法有這麼快的響應速度會有大量重傳的情況,實際上傳流量可能遠大於理論值)

一次的回包大概在60Mb左右(因為會有部分連線中途中斷所以不一定每次測試都會有10000個完整回覆)

 

可以看到使用pipe形式效能表現非常突出,總體完成測試僅僅使用了5s左右

傳送本身壓力比較小,可以看到0.5秒即到達峰值,其實這個時候基本10000條request已經發送出去了,後面的流量主要來自於伺服器端快取等待(TCP window Full)來不及處理而照成是重傳,後面會講到。

再來看看response的接收,基本上也僅僅使用了0.5s即達到了接收峰值,使用大概5s 即完成了全部接收,因為測試中cpu佔用上升並不明顯,而對於response的接收基本上是從tcp快取區讀出後直接就存在了內容裡,也沒有涉及磁碟操作(所以基本上可以說對於pipe這個測試並沒有發揮出其全部效能,瓶頸主要在網路頻寬上)。

 

 

再來看下執行緒組的方式(100條執行緒每條100次)

 

 

 

 

下面是非同步接收的方式

 

 

 

很明顯的差距,對於執行緒組的形式大概使用了25秒,而非同步接收使用了超過1分鐘的時間(非同步接收的模式是平臺推薦的傳送模式,正常應用情況下效能是十分優越的,而對於過高的壓力不如自定義的執行緒組,主要還是因為其使用了預設的執行緒池,而預設執行緒池不可能在短時間開100條執行緒出來用來接收資料,所以大量的回覆對執行緒池裡的執行緒就會有大量的切換,通過設定預設執行緒池數量可以提高測試中的效能)。更為重要的是這2者中的無論哪一種方式在測試中,cpu的佔用都幾乎是滿的(即是說為了完成測試計算機已經滿負荷工作了,很難再有提高)

 

後面其實還針對jd,toabao,youku,包括公司自己的伺服器進行過測試,測試結果都是類似的,只要伺服器不出問題基本上都有超過10倍的差距(如果客戶端頻寬足夠這個差距會更大)。

 

下面我們再對介面形式的HTTP進行簡單一次測試

這裡選用網易電商的介面(電商的介面一般可承受的壓力比較大,這裡前面已經確認測試不會對其正常使用造成實質的影響)

http://you.163.com/xhr/globalinfo/queryTop.json?__timestamp=1514784144074 (這裡是一個獲取商品列表的介面)

 

測試資料設定如下

 

 

 

 

 

 

 

請求量還是10000條接收的response資料大概有326Mb 30s之內完成。基本上是網路的極限,此時cpu也基本無然後壓力(100條管線,每條100個請求)

這裡其實請求是帶時間戳的,因為測試時使用的是同一個時間戳,所以實際對應用伺服器的影響不大,真實測試時可以為每條請求設定不同時間戳(這裡是因為要演示使用了線上公開服務,測試時請使用測試服務)

 

注意,這裡的測試如果選擇了效能較低的測試物件,大部分流量會在伺服器端排隊等候,導致吞吐量不大,這實際是伺服器端處理過慢,與客戶端關係不大。

一般情況下一臺普通的pc在使用pipe進行測試時就可以讓伺服器出現明顯延時

 

 

原理

正常的http一般實現都是連線完成後(tcp握手)發生request流向伺服器,然後及進入等待,收到response後才算結束(如下圖)

 

當然http1.1 即支援keep alive,完成一次收發後完全可以不關閉連線使用同一個連結發生下一個請求(如下圖)

 

 

這種方式對效能的提升還是比較明顯的,特別早些年伺服器效能有限,網路資源匱乏,RTT大(網路時延大)。不過對如今的情況,其實這些都已經不是最主要的問題了

可以明顯看到上面的模式,是一定要等到response到達後,客戶端才能發起下一個request的,如果應用伺服器需要時間處理,所有後面的請求都需要等待,即使不需要任何處理直接回復給客戶端,請求,回覆在網路上的時間也是必須完整的等下去,而且由於tcp傳輸本身的特性,速率是逐步上升的,這樣斷斷續續的傳送接收十分影響tcp迅速達到線路效能最大值。

 

pipe (管線式)正是迴避了上面的問題,他不需要等回覆達到即可直接傳送(事實上http1.1協議也從來沒有講過必須要等response到達後客戶端才能傳送下一個請求,只是為了方便應用層業務實現,一般的http庫都是這樣實現的,而現在看到的絕大多少http伺服器都是預設支援pipe的),這樣傳送與接收即可以分離開來(如下圖)

 

在事實情況下,發生可能會比這個圖表現的更快,請求1,2,3,4很可能被放到一個tcp包裡被一次性全部發出去(這種模式也給部分應用帶來了麻煩,後面會講到)

 

對於pipe相對真實的情況如上圖,多個請求會被打包在一起被髮送,甚至有時是所有request傳送完成後,伺服器才開始回覆第一個response。

 

 

而普通的keepalive的模式如上圖,一條線代表一個請求,不僅一次只能傳送一個,而且必須等待回覆後才能發下一個。

 

 

下面看下實際測試中pipe的模式具體是什麼模樣的

 

 

可以看到握手完成後(實際上握手時間也不長只用了4ms),隨後即直接開始了request的傳送,可以看到後面的一個tcp包裡直接包含了完整的12個請求。在沒有收到任何一個回覆的情況下,就可以把所有要傳送的請求提前全部發出(伺服器已經關閉了Nagle演算法)。

 

 

 

 

由於傳送速度過快直到發出一大半近70個request的時候第一個tcp確認包序號為353的包(只是確認包不是response)才發出(327的ack),而且伺服器很快就發現下一個包出問題了並引發了TCP DUP ACK (https://ask.wireshark.org/questions/29216/why-are-duplicate-tcp-acks-being-seen-in-wireshark-capture 產生原因可以參考這裡)

【TCP DUP ACK 出現在接收方發現數據包缺口時(資料包失序),這種情況就會發送重複的ACK,這不僅用於快重傳,會觸發比快重傳更快的恢復機制(Fast Retransmission)如果發現重複的ACK,但是報文中未發現缺口,這表示你捕獲的是資料來源(而不是接收方),這是十分正常的如果資料在發往接收方的時候發生了丟失。你應該會看到一個重傳包】

其實就是說伺服器沒有發現下一個包後面又發了3次(一共4次·)TCP DUP ACK 都是針對353的,所以後面客戶端很快就重傳了TCP DUP ACK 所指定的丟失的包(即下面看到的362)

後面還可以看到由於過快的速度,還造成了部分的失序列(out of order)。不過需要說明的是,這些錯誤在tcp的傳輸中是很常見的,tcp有自己的一套高效的機制對這些錯誤進行恢復,即便有這些錯誤的存在也不會對pipe的實際效能造成影響。

 

如果伺服器異常誤包不能馬上被恢復可能會造成指數退避的情況如下圖

 

 

 

高速收發帶來的問題,不僅有丟包,失序,重傳,無論是客戶端還是伺服器都會有接收視窗耗盡的情況,如果接收端視窗耗盡會出現TCP ZeroWIndow / Window full。 所以無論是客戶端還是伺服器都需要快速讀取tcp緩衝區資料

 

 

 

 

 

通過對TCP流的檢查可以確定在本次測試中的部分管道的100條request是全部發出後,response才逐步被伺服器發出

 

現在看一下response的回覆情況

 

 

因為response本身很大,而客戶端的MSS只有1460 (上面看到的1506不是超過了MSS的意思,實際該資料包只有1424,加上48個位元組的TCP包頭,20位元組的ip包頭,14位元組的乙太網包頭一共是1506,正常tcp包頭為20位元組因為這個tcp包被拆包了,所以包頭裡多了28個位元組的options)所以一個response被拆成了多個包。

通過報文不難看出這個response在網路中傳輸大概花了1ms不到的時間(大概730微秒),因為看到是過濾掉過埠(指定管道)的流量,實際上在這不到1ms的時間裡另外的管道也是可能同時在接收資料的。

 

pipe之所以能比常規請求方式效能高出這麼多,主要有以下幾點

1:管線式傳送,每條request不要等response回覆即可直接傳送下一個(重點不在於使用的是同一條線路,而且不約等待回覆)

2:多條請求打包傳送,在網路條件合適的情況下一個包可以包含多條request

3:只要伺服器允許只需要創建極少tcp連結 (因為非區域網的TCP線路一般都遵循慢啟動,網路正常情況下需要一定時間後效率才能達到最高)

 

現在我們可以來說下pipe弊端

實際pipe早就被http1.1所支援,並且大部分nginx伺服器也支援並開啟了這一功能。

相比普通的http keepalive傳輸 pipe http 解決了HOL blocking (Head-of-Line Blocking),而正是不再遵循一發一收的模式,使得應用層不能直接將每個請求與回覆一一對應起來,對部分需要提交併區分返回結果的POST一類的請求,這種方式顯的不是很友好。

解決方法其實也很簡單,在應用服務上為request於response加上唯一標籤即可以區分,或者直接使用HTTP2.0(https://tools.ietf.org/pdf/rfc7540.pdf)(這也是2.0的一個重要改進,http2.0也是通過類似的方式為其每個幀新增標識當前stream的id來實現區分的)

 

 下面是pipe與常規http的簡單對比

pipe 管線式HTTP

普通HTTP 1.1

使用同一條tcp線路

使用不同連結(支援keepalive 可以保持連結)

不用等待回覆即可以直接傳送下一個請求

同一個連結必須收到回覆後才能發起下一個請求

一次/一包可以同時傳送多個請求

一次只能傳送一個請求

 

 

 

 

 

 

實現

 

如下為pipe的.NET簡單實現類庫,及應用該類庫的deom 測試工具

 

實現過程還是比較簡單的可直接參看GitHub工程,MyPipeHttpHelper為實現pipe的工具類(程式碼中有較詳細的註釋),PipeHttpRuner為使用該工具類編寫的測試工具

https://github.com/lulianqi/PipeHttp/ (工程地址)

https://github.com/lulianqi/PipeHttp/tree/master/MyPipeHttpHelper (類庫地址)

https://github.com/lulianqi/PipeHttp/tree/master/PipeHttpRuner (測試deom地址)

紅包碼

                如果能幫助您,請您掃下紅包碼(支付寶送錢),據說掃不同的紅包碼,得到大額紅包的機率大很多哦