TVP思享 | 四個全新維度,極限優化HTTP效能
導語 | 當產品的使用者量不斷翻番時,需求會倒逼著你優化HTTP協議。那麼,要想極限優化HTTP效能,應該從哪些維度出發呢?本文將由TVP陶輝老師,為大家分享四個全新維度。「TVP思享」專欄,凝結大咖思考,匯聚專家分享,收穫全新思想,歡迎長期關注。(編輯:雲加社群 尾尾)
作者簡介:陶輝,騰訊雲最具價值專家(TVP),杭州智鏈達資料有限公司 CTO及聯合創始人,曾就職於阿里雲、騰訊、華為、思科等公司,著有暢銷書《深入理解Nginx:模組開發與架構解析》,與極客時間合作暢銷視訊課程《Web協議詳解與抓包實戰》、《Nginx核心知識100講》。
無論你在做前端、後端還是運維,HTTP都是不得不打交道的網路協議。它是最常用的應用層協議,對它的優化,既能通過降低時延帶來更好的體驗性,也能通過降低資源消耗帶來更高的併發性。
可是,剛學HTTP不久的同學,很難全面說出HTTP協議的所有優化點。而當你要準備大廠的面試,或者要加入一個快速發展的專案的時候,你就有必要了解這一方面的內容了。因為當產品的使用者量不斷翻番時,需求會倒逼著你優化HTTP協議。
本文是陶輝老師在2019年GOPS全球運維大會上海站的演講重新提煉後的總結,希望能從四個全新的維度,帶你覆蓋絕大部分的HTTP優化技巧。這樣,即使不需要極致方法去解決當前的效能瓶頸,也能知道優化方向在哪,當需求來臨時,能夠到Google上定向查閱資料。
一、編碼效率優化
第一個維度,是從編碼效率上,更快速地把訊息轉換成更短的字元流。這是最直接的效能優化點。
如果你對HTTP/1.1協議做過抓包分析,就會發現它是用“whitespace-delimited”方式編碼的。用空格、回車這些符號來編碼,是因為HTTP在誕生之初追求可讀性,這樣更有利於它的推廣。
然而在當下,這種編碼方式已經嚴重影響效能了,所以2009年Google推出了基於二進位制的SPDY協議,大幅提升了編碼效率。2015年,稍做改進後它被確定為HTTP/2協議,現在50%以上的站點都在使用它。
這是編碼優化的大方向,包括即將推出的HTTP/3。
然而這些新技術到底是怎樣提升效能的呢?我們需要拆開了來看,先從資料的壓縮談起。
抓包看到的是資料,它並不等於資訊。資料其實是資訊和冗餘資料之和,而壓縮技術,就是儘量地去除冗餘資料。
壓縮分為無失真壓縮和有失真壓縮。針對圖片、音視訊,我們每天都在與有失真壓縮打交道。比如,當瀏覽器只需要縮圖時,就沒有必要浪費頻寬傳輸高清圖片。
而高清視訊做過有失真壓縮後,在肉眼無法分清時,已經被壓縮了上千倍。這是因為,聲音、視訊都可以做增量壓縮。
還記得曾經的VCD嗎?當光碟有劃痕時,整張盤都無法播放,就是因為那時的視訊做了增量壓縮,而且關鍵幀太少,導致關鍵幀損壞時,後面的增量幀全部無法播放了。
再來看無失真壓縮,你肯定用過gzip,它讓http body實現了無損壓縮。肉眼閱讀壓縮後的報文全是亂碼,但接收端解壓後,可以看到傳送端的原文。然而,gzip的效率其實並不高,以Google推出的brotli做對比,你就知道它的缺陷了:
評價壓縮演算法時,我們重點看兩個指標:壓縮率和壓縮速度。上圖中可以看到,無論用gzip 9個壓縮級別中的哪一個,它的壓縮率都低於brotli(相比gzip,壓縮級別它還可以配置為10),壓縮速度也更慢。
所以,如果可以,應該儘快更新你的gzip壓縮演算法了。
說完對body的壓縮,再來看HTTP header的壓縮。對於HTTP/1.x來說,header就是效能殺手。特別是當下cookie氾濫的時代,每次請求都要攜帶幾個KB的頭部,很浪費頻寬、CPU、記憶體!
HTTP2通過HPACK技術大幅度降低了header編碼後的體積,這也是HTTP3的演進方向。HPACK到底是怎樣實現header壓縮的呢?
HPACK通過Huffman演算法、靜態表、動態表對三種header都做了壓縮。比如上圖中,method GET存在於靜態表,用1個位元組表示的整數2表達即可;user-agent Mozilla這行頭部非常長,當它第2次出現時,用2個位元組的整數62表示即可;即使它第1次出現時,也可以用Huffman演算法壓縮Mozilla這段很長的瀏覽器識別符號,可以獲得最多5/8的壓縮率。
靜態表中只存放最常見的header,有的只有name,有的同時包括name和value。靜態表的大小很有限,目前只有61個元素。
動態表應用了增量編碼的思想,即,第1次出現時加入動態表,第2次出現的時候,傳輸它在動態表中的序號即可。
Huffman編碼在winrar等壓縮軟體中廣為使用,但HPACK中的Huffman有所不同,它使用的是靜態huffman編碼。
它統計了網際網路上幾年內的HTTP頭部,按照每個字元出現的概率,重建huffman樹。這樣,根據規則,出現次數最多的a、c、e或者1、2、3這些字元就只用5個bit位表示,而很少出現的字元則用幾十個bit位表示。
說完header,再來看http body的編碼。這裡舉3個例子:
第一:只有幾十位元組的小圖示,沒有必要用獨立的HTTP請求傳輸,根據RFC2397的規則,可以把它直接嵌入到HTML或者CSS檔案中,而瀏覽器在解析時會識別出它們,就像下圖中的頭像:
第二:JS原始碼檔案中,可能有許多小檔案,這些檔案中也有許多空行、註釋,通過WebPack工具,先在伺服器端打包為一個檔案,並去除冗餘的字元,編碼效果也很好。
第三:在表單中,可以一次傳輸多個元素,比如既有複選框,也可以有檔案。這就減少了HTTP請求的個數。
可見,HTTP協議從header到 body,都有許多編碼手段,可以讓傳輸的報文更短小,既節省了頻寬,也降低了時延。
二、通道利用率優化
編碼效率優化完後,再來看“通道”。這雖然是通訊領域的詞彙,但用來概括HTTP的優化點非常合適,這裡就借用下了。
通道利用率包括3個優化點,第一個優化點是多路複用!高速的低層通道上,可以跑許多低速的高層通道。
比如,主機上只有一塊網絡卡,卻能同時讓瀏覽器、微信、釘釘收發訊息;一個程序可以同時服務幾萬個TCP連線;一個TCP連線上可以同時傳遞多個HTTP2 STREAM訊息。
其次,為了讓通道有更高的利用率,還得及時恢復錯誤。所以,TCP工作的很大一部分,都是在及時的發現丟包、亂序報文,並快速的處理它們。
最後,就像經濟學裡說的,資源總是稀缺的。有限的頻寬下,如何公平的對待不同的連線、使用者和物件呢?
比如下載頁面時,如果把CSS和圖片以同等優先順序下載就有問題,圖片晚點顯示沒關係,但CSS沒獲取到頁面就無法顯示。
另外,傳輸訊息時,報文頭報並不承載目標資訊,但它又是必不可少的,如何降低這些控制資訊的佔比呢?
我們先從多路複用談起。廣義上來說,多執行緒、協程都屬於多路複用,但這裡我主要指HTTP2的stream。因為HTTP協議被設計為client先發request,server才能回覆response,這樣收發訊息,是沒辦法跑滿頻寬的。
最有效率的方式是,傳送端源源不斷地發請求、接收端源源不斷地發響應,這對於長肥網路尤為有效:
HTTP2的stream就是這樣複用連線的。我們知道,chrome對一個站點最多同時建立6個連線,而有了HTTP2後,只需要一個連線就能高效的傳輸頁面上的數百個物件。
我特意讓我的個人站點www.taohui.pub同時支援HTTP1和HTTP2,下圖是連線視角上HTTP2和HTTP1的區別。
熟悉chrome Network網路面板的同學,肯定很熟悉waterfall,它可以幫助你分析HTTP請求到底慢在哪裡,是請求發出的慢,還是響應接收的慢,又或者是解析得太慢了。下圖還是我的站點在waterfall視角下的對比。
從這兩張圖可以看出,HTTP2全面優於HTTP1。
再來看網路錯誤的恢復。在應用層,lingering_time通過延遲關閉連線來避免瀏覽器因RST錯誤收不到http response,而timeout則是用定時器及時發現錯誤並釋放資源。
在傳輸層,通過timestamp=1可以讓TCP更精準的測量出定時器的超時時間RTO。當然,timestamp還有一個用途,就是防止長肥網路中的序列號迴繞。
什麼是序列號迴繞呢?我們知道,TCP每個報文都有序列號,它不是指報文的次序,而是已經發送的位元組數。由於它是32位整數,所以最多可以處理232也就是4.2GB的飛行中報文。
像上圖中,當1G-2G這些報文在網路中飛行時間過長時,就會與5G-6G報文重疊,引發錯誤。
網路錯誤還有很多種,比如報文的次序也是無法保證的。開啟tcp_sack可以減少亂序時的重發報文量,降低頻寬消耗。
用Chrome瀏覽器直接下載大檔案時,網路不好時,一出錯就得全部重傳,體驗很差。
改用迅雷下載就快了很多。這是因為迅雷把大檔案拆成很多小塊,可以多執行緒下載,而且每個小塊出錯後,重新下載這一個塊即可,效率很高。
這個斷點續傳、多執行緒下載技術,就是HTTP的Range協議。如果你的服務是快取,也可以使用Range協議,比如Nginx的Slice模組就做了這件事。
實際上對於網路錯誤恢復,最精妙的演算法是擁塞控制,它可以全面提升網路效能。有同學會問,TCP不是有流量控制,為什麼還會發生網路擁塞呢?這是因為,TCP鏈路中的各個路由器,處理能力並不互相匹配。
就像上圖,R1的峰值網路是700M/s,R2的峰值網路是600M/s,它們都需要通過R3才能到達R4。然而,R3的最大頻寬只有1000M/s!當R1、R2中的TCP全速使用各自頻寬時,就會引發R3丟包。擁塞控制就是解決丟包問題的。
自1982年TCP誕生起,就在使用傳統的擁塞控制演算法,它是發現丟包後再剎車減速,效果很不好。
為什麼呢?你可以觀察下圖,路由器中會有緩衝佇列,當佇列為空時,ping的時延最短;當佇列將滿時,ping的時延很大,但還未發生丟包;當佇列已滿時,丟包才會發生。
所以,當隊列出現積壓時,丟包沒有發生。雖然此時峰值頻寬不會減少,但網路時延變大了,這是要避免的。
而測量驅動的擁塞控制演算法,就在佇列剛出現積壓這個點上開始剎車減速。在當今記憶體越來越便宜,佇列越來越大的年代,新演算法尤為有效。
當Linux核心更新到4.9版本時,原先的CUBIC擁塞控制演算法就被替換為Google的BBR演算法了。
從下圖中可以看到,當丟包率達到0.01%時,CUBIC就沒法用了,而BBR並沒有問題,直到丟包率達到5%時BBR的頻寬才劇烈下降。
再來看資源的平衡分配。為了公平的對待連線、使用者,伺服器會做限速。比如下圖中的Leacky Bucket演算法,它能夠平滑突增的流量,更公平的分配頻寬。
再比如HTTP2中的優先順序功能。一個頁面上有幾百個物件,這些物件的重要性不同,有些之間還互相依賴。比如,有些JS檔案會包含jQuery.js,如果同等對待的話,即使先下載完前者,也無法使用。
HTTP2允許瀏覽器下載物件時,根據解析規則,在stream中設定每一個物件的weight優先順序(255最大,0最小)。而各代理、資源伺服器都會根據優先順序,分配記憶體和頻寬,提升網路效率。
最後看下TCP的報文效率,它也會影響HTTP效能。比如開啟Nagle演算法後,網路中的小報文數量大幅減少,考慮到40位元組的報文頭部,資訊佔比更高。
Cork演算法與Nagle演算法相似,但會更激進的控制小報文。Cork與Nagle是從傳送端控制小報文,quickack則從接收端控制純ack小報文的數量,提高資訊佔比。
三、傳輸路徑優化
說完相對微觀一些的通道,我們再來從巨集觀上看第三個優化點:傳輸路徑的優化。
傳輸路徑的第一個優化點是快取,瀏覽器、CDN、負載均衡等元件中,快取無處不在。
快取的基本用法你大概很熟悉了,這裡我們講過期快取的用法。把過期快取直接丟掉是很浪費的,因為“過期”是客戶端的定時器決定的,並不代表資源真正失效。
所以,可以把它的識別符號帶給源伺服器,伺服器會判斷快取是否仍然有效,如果有效,直接返回304和空body就可以了,非常節省頻寬。
對於負載均衡而言,過期快取還能夠保護源伺服器,限制回源請求。當源伺服器掛掉後,還能以過期快取給使用者帶來降級後的服務體驗,這比返回503要好得多。
傳輸路徑的第二個優化點是慢啟動。系統自帶的TCP協議棧,為了避免瓶頸路由器丟包,會緩緩加大傳輸速度。它的起始速度就叫做初始擁塞視窗。
早期的初始擁塞視窗是1個MSS(通常是576位元組),後來改到3個MSS(Linux 2.5.32),在Google的建議下又改到10個MSS(Linux 3.0)。
之所以要不斷提升起始視窗,是因為隨著網際網路的發展,網頁越來越豐富,體積也越來越大。起始視窗太小,就需要更長的時間下載第一個網頁,體驗很差。
當然,修改起始視窗很簡單,下圖中是Linux下調整視窗的方法。
修改起始視窗是常見的效能優化手段,比如CDN廠商都改過起始視窗,下圖是主流CDN廠商2014和2017年的起始視窗大小。
可見,有些視窗14年調得太大了,17年又縮回去了。所以,起始視窗並不是越大越好,它會增加瓶頸路由器的壓力。
再來看傳輸路徑上,如何從拉模式升級到推模式。
比如 index.html 檔案中包含<LINK href=”some.css”>,在HTTP/1中,必須先下載完index.html,才能去下載some.css,這是兩個RTT的時間。但在HTTP/2中,伺服器可以通過2個stream,同時並行傳送index.html和some.css,節約了一半的時間。
其實當出現丟包時,HTTP2的stream並行傳送會嚴重退化,因為TCP的隊頭阻塞問題沒有解決。
上圖中的SPDY與HTTP2是等價的。在紅綠色這3個stream併發傳輸時,TCP層仍然會序列化,假設紅色的stream在最先發送的,如果紅色報文丟失,那麼即使接收端已經收到了完整的藍、綠stream,TCP也不會把它交給HTTP2,因為TCP自身必須保證報文有序。這樣併發就沒有保證了,這就是隊頭阻塞問題。
解決隊頭阻塞的辦法就是繞開TCP,使用UDP協議實現HTTP,比如Google的GQUIC協議就是這麼做的,B站在幾年前就使用它提供服務了。
UDP協議自身是不能保證可靠傳輸的,所以GQUIC需要重新在UDP之上實現TCP曾經做過的事。這是HTTP的發展方向,所以目前HTTP3就基於GQUIC在制定標準。
四、資訊保安優化
最後,再從網路資訊保安的角度,談談如何做優化。它實際上與編碼、通道、傳輸路徑都有關聯,但其實又是獨立的環節,所以放在最後討論。
網際網路世界的資訊保安,始於1995年的SSL3.0。到現在,許多大型網站都更新到2018年推出的TLS1.3了。
TLS1.2有什麼問題呢?最大問題就是,它支援古老的金鑰協商協議,這些協議現在已經不安全了。比如2015年出現的FREAK中間人攻擊,就可以用Amazon上的虛擬機器,分分鐘攻陷支援老演算法的伺服器。
TLS1.3針對這一情況,取消了在當前的計算力下,數學上已經不再安全的非對稱金鑰協商演算法。在Openssl的最新實現中,僅支援5種安全套件:
TLS1.3的另一個優勢是握手速度。在TLS1.2中,由於需要2個RTT才能協商完金鑰,才誕生了session cache和session ticket這兩個工具,它們都把協商金鑰的握手降低為1個RTT。但是,這兩種方式都無法應對重放攻擊。
而TLS1.2中的安全套件協商、ECDHE公鑰交換這兩步,在TLS1.3中被合併成一步,這大大提升了握手速度。
如果你還在使用TLS1.2,儘快升級到1.3吧,除了安全性,還有效能上的收益。
五、小結
HTTP的效能優化手段眾多,從這四個維度出發,可以建立起樹狀的知識體系,囊括絕大部分的HTTP優化點。
編碼效率優化包括http header和body ,它可以使傳輸的資料更短小緊湊,從而獲得更低的時延和更高的併發。同時,好的編碼演算法也可以減少編解碼時的CPU消耗。
通道利用率的優化,可以從多路複用、錯誤發現及恢復、資源分配這3個角度出發,讓快速的底層通道,有效的承載慢速的應用層通道。
傳輸路徑的優化,包括各級快取、慢啟動、訊息傳送模式等,它能夠讓訊息更及時的發給瀏覽器,提升使用者體驗。
當下網際網路中的資訊保安,主要還是建立在TLS協議之上的。TLS1.3從安全性、效能上都有很大的提升,我們應當及時的升級。
希望這些知識能夠幫助你全面、高效地優化HTTP協議!
騰訊雲TVP
騰訊雲最具價值專家,簡稱TVP(Tencent Cloud Valuable Professional),是騰訊雲頒發給技術專家的一項榮譽認證,以此感謝他們為推動雲端計算的發展所作出的貢獻。這些技術專家來自於各個技術領域和行業,他們熱衷實踐、樂於分享,為技術社群的建設和推動雲端計算的傳播做出了卓越的貢獻。