超高性能管線式HTTP請(qǐng)求(實(shí)踐·原理·實(shí)現(xiàn))
這里的高性能指的就是網(wǎng)卡有多快請(qǐng)求發(fā)送就能有多快,基本上一般的服務(wù)器在一臺(tái)客戶端的壓力下就會(huì)出現(xiàn)明顯延時(shí)。
該篇實(shí)際是介紹pipe管線的原理,下面主要通過(guò)其高性能的測(cè)試實(shí)踐,解析背后數(shù)據(jù)流量及原理。***附帶一個(gè)簡(jiǎn)單的實(shí)現(xiàn)。
實(shí)踐
先直接看對(duì)比測(cè)試方法:
測(cè)試內(nèi)容單一客戶的使用盡可能快的方式向服務(wù)器發(fā)送一定量(10000條)請(qǐng)求,并接收返回?cái)?shù)據(jù)。
對(duì)于單一客戶端對(duì)服務(wù)器進(jìn)行http請(qǐng)求,一般我們的方式:
1:?jiǎn)芜M(jìn)程或線程輪詢請(qǐng)求(這個(gè)效能自然很低,原因會(huì)講到,也不用測(cè)試)
2:多條線程提前準(zhǔn)備數(shù)據(jù)等待信號(hào)(對(duì)客戶端性能要求較高)
3:提前準(zhǔn)備一組線程同時(shí)輪詢操作
4:使用系統(tǒng)/平臺(tái)自帶異步發(fā)送機(jī)制(實(shí)際就是平臺(tái)線程池的方式,發(fā)送與接收使用從線程池中的不同線程)
對(duì)于測(cè)試方案1,及方案2測(cè)試中性能較低沒(méi)有可比性,后面測(cè)試不會(huì)展示其結(jié)果。
以下展示后面2種測(cè)試方法及當(dāng)前要說(shuō)的管線式的方式:
- 先講管線式(pipe)測(cè)試方案(原理在后面會(huì)講到),測(cè)試中使用100條管線(管道),實(shí)際上更少甚至一條管線也是能達(dá)到近似的性能,不過(guò)多數(shù)服務(wù)器nginx限制一條管可以持續(xù)發(fā)送request的數(shù)量(大部分是100也有部分會(huì)是200或是更高),每條管線發(fā)送100個(gè)請(qǐng)求。
- 然后是線程組的方式準(zhǔn)備100條線程(100條線程并不是很多不會(huì)對(duì)系統(tǒng)本身有明顯影響),每條線程輪詢發(fā)送100個(gè)request。
- 異步方式的方式,10000全部提交發(fā)送線程,由線程池控制接收。
測(cè)試環(huán)境:普通家用PC,i5 4核,12G ,100Mb電信帶寬。
測(cè)試數(shù)據(jù):
GET http://www.baidu.com HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: www.baidu.com
Connection: Keep-Alive
這里就是測(cè)試最常用的baidu,如果測(cè)試接口性能不佳,大部分請(qǐng)求會(huì)在應(yīng)用服務(wù)器排隊(duì),難以直觀提現(xiàn)pipe的優(yōu)勢(shì)(其實(shí)就是還沒(méi)有用到pipe的能力,服務(wù)器就先阻塞了)。
下文中所有關(guān)于pipe的測(cè)試都是使用PipeHttpRuner (http://www.cnblogs.com/lulianqi/p/8167843.html 為該測(cè)試工具的下載地址,使用方法及介紹)。
先直接看管道式的表現(xiàn):(截圖全部為windows自帶任務(wù)管理器及資源管理器)
先解釋下截圖含義,后面的截圖也都是同樣的含義:
***副為任務(wù)管理器的截圖實(shí)線為接收數(shù)據(jù),虛線為發(fā)送數(shù)據(jù),取樣0.5s,每一個(gè)正方形的刻度為1.5s(因?yàn)槿蝿?wù)管理器繪圖策略速率上升太快過(guò)高的沒(méi)有辦法顯示,不過(guò)還是可以看到時(shí)間線)。
第二副為資源管理器,添加了3個(gè)采樣器,紅色為CPU占用率,藍(lán)色為網(wǎng)絡(luò)接收速率,綠色為網(wǎng)絡(luò)發(fā)送速率。
測(cè)試中 一次原始請(qǐng)求大概130字節(jié),加上tcp,ip包頭,10000條大概也只有1.5Mb(包頭不會(huì)太多因?yàn)楣艿朗秸?qǐng)求里會(huì)有多個(gè)請(qǐng)求放到一個(gè)包里的情況,不過(guò)大部分服務(wù)器無(wú)法有這么快的響應(yīng)速度會(huì)有大量重傳的情況,實(shí)際上傳流量可能遠(yuǎn)大于理論值)。
一次的回包大概在60Mb左右(因?yàn)闀?huì)有部分連接中途中斷所以不一定每次測(cè)試都會(huì)有10000個(gè)完整回復(fù))。
可以看到使用pipe形式性能表現(xiàn)非常突出,總體完成測(cè)試僅僅使用了5s左右。
發(fā)送本身壓力比較小,可以看到0.5秒即到達(dá)峰值,其實(shí)這個(gè)時(shí)候基本10000條request已經(jīng)發(fā)送出去了,后面的流量主要來(lái)自于服務(wù)器端緩存等待(TCP window Full)來(lái)不及處理而照成是重傳,后面會(huì)講到。
再來(lái)看看response的接收,基本上也僅僅使用了0.5s即達(dá)到了接收峰值,使用大概5s 即完成了全部接收,因?yàn)闇y(cè)試中cpu占用上升并不明顯,而對(duì)于response的接收基本上是從tcp緩存區(qū)讀出后直接就存在了內(nèi)容里,也沒(méi)有涉及磁盤操作(所以基本上可以說(shuō)對(duì)于pipe這個(gè)測(cè)試并沒(méi)有發(fā)揮出其全部性能,瓶頸主要在網(wǎng)絡(luò)帶寬上)。
再來(lái)看下線程組的方式(100條線程每條100次):
下面是異步接收的方式:
很明顯的差距,對(duì)于線程組的形式大概使用了25秒,而異步接收使用了超過(guò)1分鐘的時(shí)間(異步接收的模式是平臺(tái)推薦的發(fā)送模式,正常應(yīng)用情況下性能是十分優(yōu)越的,而對(duì)于過(guò)高的壓力不如自定義的線程組,主要還是因?yàn)槠涫褂昧四J(rèn)的線程池,而默認(rèn)線程池不可能在短時(shí)間開(kāi)100條線程出來(lái)用來(lái)接收數(shù)據(jù),所以大量的回復(fù)對(duì)線程池里的線程就會(huì)有大量的切換,通過(guò)設(shè)置默認(rèn)線程池?cái)?shù)量可以提高測(cè)試中的性能)。更為重要的是這2者中的無(wú)論哪一種方式在測(cè)試中,cpu的占用都幾乎是滿的(即是說(shuō)為了完成測(cè)試計(jì)算機(jī)已經(jīng)滿負(fù)荷工作了,很難再有提高)。
后面其實(shí)還針對(duì)jd,toabao,youku,包括公司自己的服務(wù)器進(jìn)行過(guò)測(cè)試,測(cè)試結(jié)果都是類似的,只要服務(wù)器不出問(wèn)題基本上都有超過(guò)10倍的差距(如果客戶端帶寬足夠這個(gè)差距會(huì)更大)。
下面我們?cè)賹?duì)接口形式的HTTP進(jìn)行簡(jiǎn)單一次測(cè)試:
這里選用網(wǎng)易電商的接口(電商的接口一般可承受的壓力比較大,這里前面已經(jīng)確認(rèn)測(cè)試不會(huì)對(duì)其正常使用造成實(shí)質(zhì)的影響)。
http://you.163.com/xhr/globalinfo/queryTop.json?__timestamp=1514784144074 (這里是一個(gè)獲取商品列表的接口)。
測(cè)試數(shù)據(jù)設(shè)置如下:
請(qǐng)求量還是10000條接收的response數(shù)據(jù)大概有326Mb 30s之內(nèi)完成?;旧鲜蔷W(wǎng)絡(luò)的極限,此時(shí)cpu也基本無(wú)然后壓力(100條管線,每條100個(gè)請(qǐng)求)。
這里其實(shí)請(qǐng)求是帶時(shí)間戳的,因?yàn)闇y(cè)試時(shí)使用的是同一個(gè)時(shí)間戳,所以實(shí)際對(duì)應(yīng)用服務(wù)器的影響不大,真實(shí)測(cè)試時(shí)可以為每條請(qǐng)求設(shè)置不同時(shí)間戳(這里是因?yàn)橐菔臼褂昧司€上公開(kāi)服務(wù),測(cè)試時(shí)請(qǐng)使用測(cè)試服務(wù))。
注意,這里的測(cè)試如果選擇了性能較低的測(cè)試對(duì)象,大部分流量會(huì)在服務(wù)器端排隊(duì)等候,導(dǎo)致吞吐量不大,這實(shí)際是服務(wù)器端處理過(guò)慢,與客戶端關(guān)系不大。
一般情況下一臺(tái)普通的pc在使用pipe進(jìn)行測(cè)試時(shí)就可以讓服務(wù)器出現(xiàn)明顯延時(shí)。
原理
正常的http一般實(shí)現(xiàn)都是連接完成后(tcp握手)發(fā)生request流向服務(wù)器,然后及進(jìn)入等待,收到response后才算結(jié)束(如下圖):
當(dāng)然http1.1 即支持keep alive,完成一次收發(fā)后完全可以不關(guān)閉連接使用同一個(gè)鏈接發(fā)生下一個(gè)請(qǐng)求(如下圖):
這種方式對(duì)性能的提升還是比較明顯的,特別早些年服務(wù)器性能有限,網(wǎng)絡(luò)資源匱乏,RTT大(網(wǎng)絡(luò)時(shí)延大)。不過(guò)對(duì)如今的情況,其實(shí)這些都已經(jīng)不是最主要的問(wèn)題了。
可以明顯看到上面的模式,是一定要等到response到達(dá)后,客戶端才能發(fā)起下一個(gè)request的,如果應(yīng)用服務(wù)器需要時(shí)間處理,所有后面的請(qǐng)求都需要等待,即使不需要任何處理直接回復(fù)給客戶端,請(qǐng)求,回復(fù)在網(wǎng)絡(luò)上的時(shí)間也是必須完整的等下去,而且由于tcp傳輸本身的特性,速率是逐步上升的,這樣斷斷續(xù)續(xù)的發(fā)送接收十分影響tcp迅速達(dá)到線路性能***值。
pipe (管線式)正是回避了上面的問(wèn)題,他不需要等回復(fù)達(dá)到即可直接發(fā)送(事實(shí)上http1.1協(xié)議也從來(lái)沒(méi)有講過(guò)必須要等response到達(dá)后客戶端才能發(fā)送下一個(gè)請(qǐng)求,只是為了方便應(yīng)用層業(yè)務(wù)實(shí)現(xiàn),一般的http庫(kù)都是這樣實(shí)現(xiàn)的,而現(xiàn)在看到的絕大多少http服務(wù)器都是默認(rèn)支持pipe的),這樣發(fā)送與接收即可以分離開(kāi)來(lái)(如下圖):
在事實(shí)情況下,發(fā)生可能會(huì)比這個(gè)圖表現(xiàn)的更快,請(qǐng)求1,2,3,4很可能被放到一個(gè)tcp包里被一次性全部發(fā)出去(這種模式也給部分應(yīng)用帶來(lái)了麻煩,后面會(huì)講到)。
對(duì)于pipe相對(duì)真實(shí)的情況如上圖,多個(gè)請(qǐng)求會(huì)被打包在一起被發(fā)送,甚至有時(shí)是所有request發(fā)送完成后,服務(wù)器才開(kāi)始回復(fù)***個(gè)response。
而普通的keepalive的模式如上圖,一條線代表一個(gè)請(qǐng)求,不僅一次只能發(fā)送一個(gè),而且必須等待回復(fù)后才能發(fā)下一個(gè)。
下面看下實(shí)際測(cè)試中pipe的模式具體是什么模樣的。
可以看到握手完成后(實(shí)際上握手時(shí)間也不長(zhǎng)只用了4ms),隨后即直接開(kāi)始了request的發(fā)送,可以看到后面的一個(gè)tcp包里直接包含了完整的12個(gè)請(qǐng)求。在沒(méi)有收到任何一個(gè)回復(fù)的情況下,就可以把所有要發(fā)送的請(qǐng)求提前全部發(fā)出(服務(wù)器已經(jīng)關(guān)閉了Nagle算法)。
由于發(fā)送速度過(guò)快直到發(fā)出一大半近70個(gè)request的時(shí)候***個(gè)tcp確認(rèn)包序號(hào)為353的包(只是確認(rèn)包不是response)才發(fā)出(327的ack),而且服務(wù)器很快就發(fā)現(xiàn)下一個(gè)包出問(wèn)題了并引發(fā)了TCP DUP ACK (https://ask.wireshark.org/questions/29216/why-are-duplicate-tcp-acks-being-seen-in-wireshark-capture 產(chǎn)生原因可以參考這里)。
【TCP DUP ACK 出現(xiàn)在接收方發(fā)現(xiàn)數(shù)據(jù)包缺口時(shí)(數(shù)據(jù)包失序),這種情況就會(huì)發(fā)送重復(fù)的ACK,這不僅用于快重傳,會(huì)觸發(fā)比快重傳更快的恢復(fù)機(jī)制(Fast Retransmission)如果發(fā)現(xiàn)重復(fù)的ACK,但是報(bào)文中未發(fā)現(xiàn)缺口,這表示你捕獲的是數(shù)據(jù)來(lái)源(而不是接收方),這是十分正常的如果數(shù)據(jù)在發(fā)往接收方的時(shí)候發(fā)生了丟失。你應(yīng)該會(huì)看到一個(gè)重傳包】。
其實(shí)就是說(shuō)服務(wù)器沒(méi)有發(fā)現(xiàn)下一個(gè)包后面又發(fā)了3次(一共4次·)TCP DUP ACK 都是針對(duì)353的,所以后面客戶端很快就重傳了TCP DUP ACK 所指定的丟失的包(即下面看到的362)
后面還可以看到由于過(guò)快的速度,還造成了部分的失序列(out of order)。不過(guò)需要說(shuō)明的是,這些錯(cuò)誤在tcp的傳輸中是很常見(jiàn)的,tcp有自己的一套高效的機(jī)制對(duì)這些錯(cuò)誤進(jìn)行恢復(fù),即便有這些錯(cuò)誤的存在也不會(huì)對(duì)pipe的實(shí)際性能造成影響。
如果服務(wù)器異常誤包不能馬上被恢復(fù)可能會(huì)造成指數(shù)退避的情況如下圖:
高速收發(fā)帶來(lái)的問(wèn)題,不僅有丟包,失序,重傳,無(wú)論是客戶端還是服務(wù)器都會(huì)有接收窗口耗盡的情況,如果接收端窗口耗盡會(huì)出現(xiàn)TCP ZeroWIndow / Window full。 所以無(wú)論是客戶端還是服務(wù)器都需要快速讀取tcp緩沖區(qū)數(shù)據(jù)。
通過(guò)對(duì)TCP流的檢查可以確定在本次測(cè)試中的部分管道的100條request是全部發(fā)出后,response才逐步被服務(wù)器發(fā)出。
現(xiàn)在看一下response的回復(fù)情況:
因?yàn)閞esponse本身很大,而客戶端的MSS只有1460 (上面看到的1506不是超過(guò)了MSS的意思,實(shí)際該數(shù)據(jù)包只有1424,加上48個(gè)字節(jié)的TCP包頭,20字節(jié)的ip包頭,14字節(jié)的以太網(wǎng)包頭一共是1506,正常tcp包頭為20字節(jié)因?yàn)檫@個(gè)tcp包被拆包了,所以包頭里多了28個(gè)字節(jié)的options)所以一個(gè)response被拆成了多個(gè)包。
通過(guò)報(bào)文不難看出這個(gè)response在網(wǎng)絡(luò)中傳輸大概花了1ms不到的時(shí)間(大概730微秒),因?yàn)榭吹绞沁^(guò)濾掉過(guò)端口(指定管道)的流量,實(shí)際上在這不到1ms的時(shí)間里另外的管道也是可能同時(shí)在接收數(shù)據(jù)的。
pipe之所以能比常規(guī)請(qǐng)求方式性能高出這么多,主要有以下幾點(diǎn):
1:管線式發(fā)送,每條request不要等response回復(fù)即可直接發(fā)送下一個(gè)(重點(diǎn)不在于使用的是同一條線路,而且不約等待回復(fù))
2:多條請(qǐng)求打包發(fā)送,在網(wǎng)絡(luò)條件合適的情況下一個(gè)包可以包含多條request
3:只要服務(wù)器允許只需要?jiǎng)?chuàng)建極少tcp鏈接 (因?yàn)榉蔷钟蚓W(wǎng)的TCP線路一般都遵循慢啟動(dòng),網(wǎng)絡(luò)正常情況下需要一定時(shí)間后效率才能達(dá)到***)
現(xiàn)在我們可以來(lái)說(shuō)下pipe弊端:
實(shí)際pipe早就被http1.1所支持,并且大部分nginx服務(wù)器也支持并開(kāi)啟了這一功能。
相比普通的http keepalive傳輸 pipe http 解決了HOL blocking (Head-of-Line Blocking),而正是不再遵循一發(fā)一收的模式,使得應(yīng)用層不能直接將每個(gè)請(qǐng)求與回復(fù)一一對(duì)應(yīng)起來(lái),對(duì)部分需要提交并區(qū)分返回結(jié)果的POST一類的請(qǐng)求,這種方式顯的不是很友好。
解決方法其實(shí)也很簡(jiǎn)單,在應(yīng)用服務(wù)上為request于response加上唯一標(biāo)簽即可以區(qū)分,或者直接使用HTTP2.0(https://tools.ietf.org/pdf/rfc7540.pdf)(這也是2.0的一個(gè)重要改進(jìn),http2.0也是通過(guò)類似的方式為其每個(gè)幀添加標(biāo)識(shí)當(dāng)前stream的id來(lái)實(shí)現(xiàn)區(qū)分的)。
下面是pipe與常規(guī)http的簡(jiǎn)單對(duì)比:
實(shí)現(xiàn)
如下為pipe的.NET簡(jiǎn)單實(shí)現(xiàn)類庫(kù),及應(yīng)用該類庫(kù)的deom 測(cè)試工具。
實(shí)現(xiàn)過(guò)程還是比較簡(jiǎn)單的可直接參看GitHub工程,MyPipeHttpHelper為實(shí)現(xiàn)pipe的工具類(代碼中有較詳細(xì)的注釋),PipeHttpRuner為使用該工具類編寫的測(cè)試工具。