原來你是這樣的http2......

騰訊雲加社群發表於2018-09-05

歡迎大家前往騰訊雲+社群,獲取更多騰訊海量技術實踐乾貨哦~

本文由mariolu發表於雲+社群專欄

序言

目前HTTP/2.0(簡稱h2)已經在廣泛使用(截止2018年8月根據Alexa流行度排名的頭部1千萬網站中,h2佔比約29%,w3techs.com/technologie…

第一話、追蹤溯源

img

圖1、HTTP年鑑圖

早在1991年,伴隨WWW誕生之初,HTTP/0.9協議已經提出。HTTP0.9是簡單且應用受限的協議。支援去網路主機獲取對應路徑的資源。但是沒有擴充套件屬性。其協議之簡單甚至只用下面一個訪問谷歌主機的例子概括了HTTP/0.9的全部。如下所示,協議只支援GET,沒有http頭;響應只能是超文字。

telnet google.com 80

Connected to x.x.x.x

GET /about

(Hyper text)

(Conection closed)

隨著人們對富媒體資訊的渴望以及瀏覽器的普及,HTTP/1.0在1996年被提出來。HTTP/1.0的很多特性目前還被廣泛使用,但是仍然像HTTP/0.9一樣一次請求需要建立一次的tcp連線。隨即短短几年時間內,HTTP/1.1以RFC標準形式再次展現在人們眼前。此時的HTTP協議1.1版本已經重新設計了長連線、options請求方法、cache頭、upgrade頭、range頭、transfer-encoding頭, 以及pieline(in order)等概念。

而我們另一個所熟知的HTTPS的SSL/TLS技術各個版本差不多在後來的十年間逐漸被提出。出於安全考慮,網際網路通訊間的防火牆路由交換機等裝置,這些裝置一般僅會開發有限的埠(如80和443)。各種版本的通訊協議只能複用這些埠。HTTP1.1的80埠設計了upgrade請求頭升級到更高階的協議,而443埠為了避免多消耗個網路RTT,在tls握手過程中使用了NPN/ALPN技術直接在通訊之前保持CS兩端的協議一致。NPN/ALPN是是TLS協議擴充套件,其中NPN是Google為實現spdy提出的。由服務端提供可支援的協議,供客戶端選擇。ALPN則是更接近於HTTP互動的方式,由客戶端先發出使用某種協議的請求,由服務端確認是否支援協議。ALPN為了HTTP2誕生做鋪墊。

另外一個不得不提的是spdy協議。Spdy旨在解決HTTP1.1的線頭阻塞問題(後面章節有詳細討論)於2009被google提出。同時分別於2012提出了spdy3.0實現了流控制,2013-2014期間提出了流優先順序,server push等概念。Spdy的存在意義更像是http2.0的體驗服。它為探索HTTP繼續演進道路做了鋪墊。

第二話、人機互動

彙編是效率最高的語言之一,但是又是最晦澀難懂的語言之一。而人腦易懂的程式語言往往犧牲效能作為折衷。簡單說,HTTP/1.x協議就是為了人類語言習慣所設計的協議,但是轉換成機器執行協議並不是高效的。讓我們在回顧下計算機執行解析HTTP1.x的流程。

GET / HTTP/1.1<crlf>

Host: xxx.aa.com<crlf>

<crlf>

對應的解析虛擬碼是

loop

while(! CRLF)

read bytes

end while

if line 1:

parse line as Request-Line

else if empty line:

Break out and We have done

else If start with non-whitespace

parse header

else if space

continue with last heade

end if

end loop

在虛擬碼解析流程可以看到,肉眼看起來簡潔的協議解析起來是這麼的費勁。而且在HTTP伺服器中還要考慮這種問題:位元組行的長度是未知的,也不知道預先分配多大記憶體。

HTTP/2.0使用了計算機易懂的二進位制編碼資訊,而且得向上相容HTTP的涵義。具體我們來看下他是如何做到的。像大多數通訊協議一樣,楨是傳輸最小單位。楨分為資料幀和控制楨。資料幀作為資料的載體,控制楨控制通道的信令。h2楨的通用格式為首部9位元組+額外的字元。正如你能想到的那樣,楨的第一個部分是描述長度,第二個部分描述了楨的型別,第三個部分描述了標誌Flag,第四個部分是唯一序列號。這是所有楨的通用頭。通用頭緊接的是楨的實體。圖4展示了楨的結構。

img

圖2、通用楨的格式

這樣設計有什麼好處呢。再來看一下楨的解析流程,你就會發現對計算機來說更簡潔。

loop

read 9 byte

payload_Length=first 3 bytes

read payload

swith type:

Take action

end loop

HTTP2.0使用header楨表達HTTP header+request line,data楨表達Body。header楨和data楨使用相同的stream id組成一個完整的HTTP請求/響應包。這裡的stream描述了一次請求和響應,相當於完成了一次HTTP/1.x的短連線請求和響應。

第三話、並行不悖

上節講到我們用h2楨完整表達了HTTP/1.x。但是h2協議抱負遠不止於此。它的真正目的是解決之前HTTP1.x的線頭阻塞問題、改善網路延遲和頁面載入時間。

我們知道一個完整的網頁包含了主頁請求和數次或數十次的子請求。HTTP/1.1已經可以並行發出所有請求.但是HTTP本身是無狀態的協議,它依賴於時間的順序來識別請求和響應直接的對應關係。先來的請求必須先給響應。那麼如果後面的響應資源對瀏覽器構建DOM或者CSSOM更重要。那它必須阻塞等待前者完成。當然這也難不倒我們,我們可以多開幾條tcp連線(瀏覽器規定一個origin(協議+host+port)最多6個)或者合併資源來減少不必要的阻塞。這是有代價的。首先tcp建連的開銷,其實合併資源帶來一小塊子資源過期導致整個合併資源的快取過期。對此,h2有一攬子的解決方案,接下來一一道來。

h2在一個tcp連線建立多個流。每個流可以有從屬關係,比如說根據瀏覽器載入的優先順序順序(主請求>CSS>能改變DOM結構的JS檔案>圖片和字型資原始檔)建立一條依賴關係鏈。處於同一等級的依賴關係中可以設定權重。權重用於分配傳輸通道資源多少。

圖6例子說明了有一次主頁請求index.html、一次main.css,一次jq.js以及一些image檔案和字型檔案qq.tff

img

圖3、h2請求的依賴樹

HTML的優先順序最高,在HTML傳輸完成之前,其他檔案不會被傳輸。HTML傳輸完成後,JS和CSS根據其分配的權重佔比分配資訊傳輸資源。如果CSS傳輸完成後,TFF和PNG如果是相同權重,那麼他們將佔有1/4的通道資源。

這裡丟擲3個問題和答案。

  • 如果CSS被阻塞了,那麼 JS 得到本屬於CSS的通訊資源
  • 如果CSS傳輸完成但沒有被移依賴樹, TFF和PNG繼承CSS的通訊份額 (假設TFF和PNG權重一樣,那麼各分得1/4通訊資源).
  • 如果CSS在依賴數被移除,JS, TFF, PNG平分通訊資源(假設3個權重一樣,那麼三者各分得1/3通訊資源)

第四話、眾星捧月

HTTP2還設計了一系列方案來改善網路的效能、包括流量控制,HPack壓縮,Server Push。

什麼你說TCP已經有流量控制了,HTTP不是多此一舉嗎?沒錯,但是在單條TCP內部,各個流可是沒有流量控制。流量控制使用了Update Frame不斷告知傳送方更新傳送的視窗大小(上限)。流量控制一個現實用途是阻塞不重要的請求,以騰出更大的通訊資源給重要的請求使用。流量控制是不可以被關閉的,流量大小可以設定2的31次方-1(2GB)。不同的中間網路裝置有不一樣的吞吐能力。流控的另一個用途在於同步所有的中間裝置交換機最小的上限。流量視窗初始大小為65535(2的16次方-1)。

就像世界上的大多數財富聚集在少數人身上一樣。在一份對48,452,989個請求的統計中,以下11個頭佔據了99%的數量,依名次遞減分別是user-agent、accetp-encoding、accept-language、accept、referer、host、connection、cookie、origin、upgrade-inseure-request、content-type。http header的值也有很大相似性,比如說”/index.html“, “gzip, deflate”。Cookie也攜帶冗餘的資訊。

這些都組成了http header大量可以壓縮的內容。

而在一份GET請求和一個304響應或者content-length很少的響應中,這些頭佔據了很大比例的通訊資源。2016年釋出的一份HTTP報告中,請求頭大約在460bytes,對一個通常的網頁,平均會有140個請求物件。這些頭總共需要63KB。這些量很有可能會是首屏和頁面載入時間優化的瓶頸。

可能你會說用gzip等壓縮演算法這些請求頭,不就完了嗎?的確spdy就這樣幹過,直到2013年BREACH攻擊暴露了gzip壓縮在https應用的安全性,這種攻擊讓攻擊者很容易獲得session cookie等資料。於是才有了HPACK。

HPACK簡單來說就是索引表,包括靜態表和動態表。靜態表由RFC定義,從不改變,靜態表預留了62個表項。每個連線的通訊雙方維護著動態表。

H2協議使用索引號代表http中的name、value或者name-value。假設被索引的是name,value沒有索引,那麼value還可以用霍夫曼編碼壓縮。

  • 在預定的頭欄位靜態對映表 中已經有預定義的 Header Name 和 Header Value值,這時候的二進位制資料格式如圖4, 第一位固定為1, 後面7位為對映的索引值。圖8的83就是這樣的,83的二進位制位元組標示1000 0011,抹掉首位就是 3 , 對應的靜態對映表中的method:POST。

img

圖4、index索引name和value

img

圖5、抓包示意

  • 預定的頭欄位靜態對映表中有 name,需要設定新值。圖6所示例子,一個指定 path的Header,首字元 為 44 ,對應的二進位制位0100 0100。前兩個字元為 01 ,Index 為 4 ,即對應靜態對映表中的 path 頭。第二個字元為 95對應的二進位制位 1001 0101,排除首字元對應的 Value Length 為 十進位制的21。即 算上 44,一共23個字元來記錄這個資訊。

img

圖6、index索引name和自定義value

img

圖7、抓包示意

  • 預定的頭欄位靜態對映表中沒有 name,需要設定新name和新值。40 的二進位制是0100 0000,02 的二進位制是0000 0010,後七位的十進位制值是 2,86 的二進位制是1000 0110,後7位的十進位制值是6。

img

圖8、index索引自定義name和自定義value

img

圖9、抓包示意

  • 明確要求該請求頭不做hpack的index

HTTP2.0還有個大殺器是Server Push,Server Push利用閒置的頻寬資源可以向瀏覽器預推送頁面展示的關鍵資源,Server push有效得降低了頁面載入時間。具體詳情參考筆者的另一篇文章https://cloud.tencent.com/developer/article/1159626。

第五話、庖丁解牛

HTTP2相比HTTP1更適合計算機執行。但是其二進位制特性不易於人腦理解。這一話我們專門來講講關於http2的除錯工具。

  • Chrome(qq瀏覽器)可以按住F12檢視h2協議。圖13所示為瀏覽器網路時序圖,列出了具體協議名稱。chrome還可以在位址列敲入chrome://net-internals/#http2檢視到h2協議細節,如圖11所示。點選相應的host就可以看到h2協商過程,如圖12所示。

img

img

圖10、瀏覽器的網路時序圖

chrome://net-internals/#http2

img

圖11、chrome除錯h2

img

圖12、h2協商過程

  • wireshark(需和chrome或firefox搭配使用)。設定環境變數SSLKEYLOGFILE=c:\temp\sslkeylog.log。然後在Wireshark->Preferences->Protocols->SSL配置key所在路徑。

img

圖13、wireshark配置

img

img

img

圖14、wireshark抓h2包

  • Nghttp2是一個完整的http2協議實現的元件。作者也參與過spdy實現。目前nghttp2庫被很多知名軟體作為h2協議實現庫使用。另外nghttp2也自帶了h2協議的分析工具。圖18展示了在明文狀態使用upgrade頭升級到h2c。圖19展示了在https基礎上升級到h2。

img

圖15、明文狀態使用upgrade頭升級到h2c

img

圖16、展示了在https基礎上升級到h2

Curl的—http2選項(需要和nghttp2一起編譯)

img

img

圖17、支援h2的curl客戶端除錯

  • Github還有些實用的http2工具元件,諸如chrome-http2-log-parser、http2-push-manifest等元件,筆者後續會專門開篇文章介紹這些工具。對於移動端的除錯,ios可以用charles proxy做代理,android需要開發者模式使用移動端的chrome,筆者在移動端使用較少,這裡就不做展開。

第六話、雕欄玉砌

H2怎麼部署呢,目前主流服務端像nginx、apache都已經支援http2,主流的客戶端curl和各種瀏覽器(包括移動端safari和chrome-android)基本也支援http2。代理伺服器如ATS、Varnish,Akamai、騰訊雲等CDN服務也支援http2。那麼怎麼把一套網站部署到h2。或者說部署h2網站和之前h1網站有什麼不一樣?

如果是自己的源站,那麼請確保伺服器支援TLS1.2已經RFC7540所要求的加密套件,h2需要保證支援alpn。你可以使用ssllabs等網站檢查。對於h2伺服器的要求是h2必須瞭解如何設定流的優先順序,h2伺服器需要支援server push。h2客戶端需要儘量多的傳送請求。

如果你的網站是從http1.x遷移過來的,那麼之前對於http1.x所做的優化可能無任何幫助甚至更差。合併小檔案不在需要,因為額外的小檔案請求在h2看來只是開銷很少。並且如果大檔案的區域性更改使得整個大檔案快取失效。在http1.0時代使用多個域名來併發http連線,在http2也毫無必要,因為http2天生就是併發的。http1.x做的優化比如說圖片資原始檔不使用cookie來減少請求大小,http2的header壓縮功能也減少了這種影響。即使不做這種優化也亦可。像合併css、小圖片帶來的增益在http2.0也是可忽略的。

如果網頁使用第三方網站元件,那麼請儘可能減少使用第三方網站元件。第三方網站不能保證支援h2,所以它可能成為木桶理論的最大短板。

謹慎使用2.0-1.x的部署方案,h2流轉化成h1請求。因為這樣無法發揮h2效能。

img

圖18、2.0-1.x的部署方案

CDN代理伺服器的h2支援,可以遮蔽h2強制走tls的代理伺服器。如圖19,代理可以在與各種協議客戶端的網路環境下,切斷和客戶端的tls連線,和伺服器新建連線。也可以作為load balancer,相當於HTTP2.0使用者和HTTP2.x伺服器直接通訊。

img

圖19、帶tls客戶端功能的代理

圖20列舉如果繞過proxy到達h2伺服器。此時的proxy相當於tcp轉發的load balance功能的裝置。如果該proxy支援tls的alpn協議,那麼它也可以選擇HTTP代理功能,和h2伺服器可以建立加密連線。如果即不支援alpn,也不支援tcp轉發。那麼proxy只能用upgrade升級成h2協議。

img

圖20、經過代理伺服器的H2部署方案

第七話、十全九美

HTTP2.0是建立在TCP之上,所以TCP的所有缺點他都有,所以H2能發揮最大效能得益於調優的tcp協議棧。TCP的慢啟動特性,決定h2一開始的併發流量不會太大,TCP以及SSL的握手連線也會拖慢h2的首包網路耗時。QUIC則完全地拋棄TCP,在UDP基礎上實現了HTTP2的一系列特性。同時做了應用層的如TCP的可靠性保障。同時這些TLS1.3傳輸更快更簡潔。這些都為HTTP2.0進化到HTTP3.0提供了一些思路。

總結

以上內容都來源於筆者的實踐經驗和理論總結。篇幅所限不能涵蓋各個細節。具體可以繼續參考RFC7540和RFC7541協議。

問答

沒有“http | https”的網址怎麼實現?

相關閱讀

我是怎麼一步步用go找出壓測效能瓶頸

HTTP/2之伺服器推送(Server Push)最佳實踐

低於0.01%的極致Crash率是怎麼做到的?

【每日課程推薦】新加坡南洋理工大學博士,帶你深度學習NLP技術

此文已由作者授權騰訊雲+社群釋出,更多原文請點選

搜尋關注公眾號「雲加社群」,第一時間獲取技術乾貨,關注後回覆1024 送你一份技術課程大禮包!

海量技術實踐經驗,盡在雲加社群

相關文章