HTTP2 規範在2015年5月正式釋出,至今大多數瀏覽器和伺服器已經對此協議提供了支援:
(2018-04-09)作為一個對 HTTP1.x 進行了加強、補充和完善的更好的協議,值得我們好好的去了解它,然後使用它做出更美妙的事情。
1 過去和現在
HTTP1.1 自從1997年釋出1999年最後一次修改以來,我們已經使用 HTTP1.x 相當長一段時間了,但是隨著近十年網際網路的爆炸式發展,當時協議規定的某些特性,已經無法滿足現代網路的需求了。
HTTP1.x 有以下幾點致命缺陷:(以瀏覽器至伺服器為例)
- 協議規定客戶端對同一域的併發連線最多隻能2個(瀏覽器實現一般是2~8個),但是現代網頁平均一個頁面需要載入 40個資源
- 線頭阻塞(Head of line blocking)問題:同一個連線中的請求,需要一個接一個序列傳送和接收
- 基於文字協議,請求和響應的頭資訊非常大,並且無法壓縮。
- 不能控制響應優先順序,必須按照請求順序響應。
- 只能單向請求,也就是客戶端請求什麼,伺服器只能返回什麼。
就是以上問題嚴重影響了現代網際網路資訊互動的效率和靈活性,此時更現代更高效的通訊協議 HTTP2 應運而生。
HTTP2 使用了多路複用
、HPACK頭壓縮
、流 + 二進位制幀
和流優先順序
等技術手段解決上述問題。
2 HTTP2
HTTP2 的前身是 SPDY協議(一個 Google 主導推行的應用層協議,作為對 HTTP1 的增強),第一版草稿就是基於 SPDY3 規範修改制定而來。HTTP2必須在維持原來 HTTP 的正規化(不改動 HTTP/1.x 的語義、方法、狀態碼、URI 以及首部欄位等等)前提下,實現突破效能限制,改進傳輸效能,實現低延遲和高吞吐量。
HTTP2 的特性包括:
- 傳輸內容使用二進位制協議
- 使用幀作為最小傳輸單位
- 多路複用
- 頭壓縮
- 伺服器推送
- 優先順序與依賴性
- 可重置
- 流量控制
- HTTPS rfc 規範並沒有要求 HTTP2 強制使用 TLS,但是目前世界所有瀏覽器和伺服器實現都基於 HTTPS 來實現 HTTP2
2.1 二進位制
在 HTTP1.x 時代,無論是傳輸內容還是頭資訊,都是文字/ASCII編碼的,雖然這有利於直接從請求從觀察出內容,但是卻使得想要實現併發傳輸異常困難(存在空格或其他字元,很難判斷訊息的起始和結束)。使用二進位制傳輸可以避免這個問題,因為傳輸內容只有1和0,通過下面第二點的“幀”規範規定格式,即可輕易識別出不同型別內容。同時使用二進位制有一個顯而易見的好處是:更小的傳輸體積。
2.2 二進位制分幀
HTTP2 在維持原有 HTTP 正規化的前提下,實現突破效能限制,改進傳輸效能,實現低延遲和高吞吐量的其中一個關鍵是:在應用層(HTTP2)和傳輸層(TCP or UDP)之間增加了二進位制分幀層
。
幀(Frame)是 HTTP2 通訊中的最小傳輸單位,所有幀以固定的 9 個八位位元組頭部開頭,隨後是一個可變長度的有效載荷
幀結構圖
+-----------------------------------------------+
| 長度Length (24) |
+---------------+---------------+---------------+
| 型別Type (8) | 標誌Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| 流識別符號Stream Identifier (31) |
+=+=============================================================+
| 幀載荷Frame Payload (0...) ...
+---------------------------------------------------------------+
複製程式碼
規範中一共定義了 10 種不同的幀,其中最基礎的兩種分別對應於 HTTP1.x 的 DATA 和 HEADERS。
一個真正的 HTTP2 請求類似下圖:
2.3 多路複用(Multiplexing)和流
上一節提到的
Stream Identifier
將 HTTP2 連線上傳輸的每個幀都關聯到一個“流”。流是一個獨立的,雙向的幀序列,可以通過一個 HTTP2 的連線在服務端與客戶端之間不斷的交換資料。
每個單獨的 HTTP2 連線都可以包含多個併發的流,這些流中交錯的包含著來自兩端的幀。流既可以被客戶端/伺服器端單方面的建立和使用,也可以被雙方共享,或者被任意一邊關閉。在流裡面,每一幀傳送的順序非常關鍵。接收方會按照收到幀的順序來進行處理。
上面是《HTTP2 講解》對流的解釋,下面接著是一個小火車的例子,但是個人覺得這個例子有一定的偏差,並且並不能讓人直觀的理解 幀-流-連線
之間的關係,以下是個人理解:
A "stream" is an independent, bidirectional sequence of frames exchanged between the client and server within an HTTP/2 connection. --rfc7540 StreamsLayer
“流”是一個邏輯上的概念(沒有真正傳輸流這麼個東西),是 HTTP2 連線中在客戶端和伺服器之間交換的獨立雙向幀序列,這就是為什麼在規範中的 stream 也是用雙引號括起來的原因。從上一節我們可以知道,HTTP2 的傳輸單位是幀,流其實就是一個幀的分組集合的概念,為什麼需要這個邏輯集合呢?答案就在多路複用
。
多路複用是解決 HTTP1.x 缺陷第一點(併發問題)和第二點(HOLB線頭問題)的核心技術點。這裡需要舉個?來說明:
假設已經建立了 TCP 連線,現在需要客戶端發起了兩個請求,從流的角度看是這樣的:
但是實際 TCP 連線只有一個,兩個幀是不可能真的“同時”到達伺服器的,多路複用更像是 CPU 處理任務概念中的 併發
,而不是並行,從規範中使用術語 Stream Concurrency 而不是 Stream Parallelism
也可得出此結論,所以實際傳輸時是下圖:
上圖需要注意三點:
- 同一個流中的幀是交錯傳輸的!
- Header 幀必須在 data 幀前面,因為無論是客戶端還是服務端,都依賴 header 幀的資訊解析 data 幀的資料!
- 先到的幀不一定先返回,快的可以先返回!
正是由於上述第一點特性,解釋了為什麼需要“流”這個邏輯集合。同時,通過這種 幀-流-連線
的組合,解決了請求併發(一次連線多個請求)和HOLB線頭問題(併發傳送,非同步響應)。
進入 HTTP/2: the Future of the Internet 由 Akamai 公司建立的官方 Demo,可以看出 HTTP2 相比於之前的 HTTP1.1 在效能上的大幅度提升。
HTTP1.1
HTTP2
在過去,我們發現 HTTP 效能優化的關鍵不在於高寬頻,而是低延遲。
從上圖可見,當頻寬到達一定的速度之後,對頁面載入速度的提升已經很少了,但是隨著延遲的減少,頁面載入時間會對應持續的減少。由於 TCP 連線存在一種稱為「調諧」的慢啟動(slow start)特性,讓原本就具有突發性和短時性的 HTTP 連線變的十分低效。而 HTTP2 通過讓所有資料流共用同一個連線,可以更有效地使用 TCP 連線,讓高頻寬也能真正的服務於 HTTP 的效能提升。
以上純屬個人理解,如有不對請不吝指出共同討論,感謝!
2.4 頭壓縮
我們都知道 HTTP協議本身是無狀態(stateless) 的:每個請求之間互不關聯,每個請求都需要攜帶伺服器所需要的所有細節資訊。比如說請求1傳送給伺服器資訊“我是使用者A”,然後請求二傳送資訊“修改我的使用者名稱為XX”,這時如果請求二沒有攜帶“我是使用者A”的資訊,那麼伺服器是不知道要修改哪個使用者的使用者名稱的。
這顯然是不符合當前 web 應用系統架構的,因為一般系統都需要進行鑑權,日誌記錄,安全校驗等限制,所以需要獲取當前操作使用者的資訊,出於安全和效能考慮我們不能在訊息體中明文包含這些資訊,HTTP2 之前的解決方案一般是使用 Cookies 頭、伺服器session 等方式模擬出“狀態”。而使用 Cookies 頭的缺點就是每個請求都需要攜帶龐大的重複的資訊並且無法壓縮,假設一個請求的 header 是2kb,那麼一百個請求就是重複的 200Kb 資訊,這是一個巨大的頻寬浪費。
HTTP2 增加了兩個特性解決上述問題:
- HPACK,專門為頭部壓縮設計的演算法,還被指定成單獨的草案中。
- 首部表,HTTP2 在戶端和伺服器端使用“首部表”來跟蹤和儲存之前傳送的鍵-值對,對於相同的資料,不再通過每次請求和響應傳送;通訊期間幾乎不會改變的通用鍵-值對(使用者代理、可接受的媒體型別,等等)只需傳送一次。
2.5 伺服器推送
這個功能通常被稱作“快取推送(cache push)”。主要的思想是:當一個客戶端請求資源X,而伺服器知道它很可能也需要資源Z的情況下,伺服器可以在客戶端傳送請求Z前,主動將資源Z推送給客戶端。這個功能幫助客戶端將Z放進快取以備將來之需。
伺服器推送需要客戶端顯式的允許伺服器提供該功能。但即使如此,客戶端依然能自主選擇是否需要中斷該推送的流。如果不需要的話,客戶端可以通過傳送一個 RST_STREAM
幀來中止推送。
我們來看一下實際場景:現在我們訪問一個網站,第一個請求一般是獲取 Document 頁面,然後瀏覽器解析這個頁面,在遇到需要資源獲取的時候(css、js、圖片等),再去發起資源獲取請求,如下圖:
【傳統做法】
為了加速這個過程,減少白屏時間,傳統的做法是把首頁需要的資源都內聯到 Document 中,還有合併資源比如 CSS sprites,js 壓縮合並等。如下圖:
【HTTP2】
在 HTTP2 的場景下,客戶端在請求 Document 的時候,伺服器如果知道頁面需要的資源有哪些,就可以把那些資源也一同返回了:
注意:主動推送的資源是能被瀏覽器快取的!
那麼問題來了,如果客戶端已經快取了資源,此時伺服器每次都還推送同樣的資源給客戶端,這不是很大的浪費嗎?
答:原來確實會存在這種情況,所以 IETF 小組正在擬定一個名為 cache-digest的技術規範,用於幫助客戶端主動告訴服務端哪些資源已經快取了,不需要重複傳送。
關於服務端推送對網頁效能的影響,和對於 CDN 的使用的比較,可以參考下面兩篇文章:
- measuring-server-push-performance
- http://www.ruanyifeng.com/blog/2018/03/http2_server_push.html
結論:使用 HTTP2 的多路複用和伺服器推送功能,並不意味著可以減少甚至拋棄使用 CDN,因為 CDN 帶來的現實地理位置上延遲減少是 HTTP2 所不能解決的,反而我們應該思考的是如何把 HTTP2 和 CDN 結合起來,進一步提升網路服務的效率和穩定性,減少延遲。
2.6 優先順序與依賴性
每個流都包含一個優先順序(也就是“權重”),它被用來告訴對端哪個流更重要。當資源有限的時候,伺服器會根據優先順序來選擇應該先傳送哪些流。
藉助於PRIORITY幀,客戶端同樣可以告知伺服器當前的流依賴於其他哪個流。該功能讓客戶端能建立一個優先順序“樹”,所有“子流”會依賴於“父流”的傳輸完成情況。
優先順序和依賴關係可以在傳輸過程中被動態的改變。這樣當使用者滾動一個全是圖片的頁面的時候,瀏覽器就能夠指定哪個圖片擁有更高的優先順序。或者是在你切換標籤頁的時候,瀏覽器可以提升新切換到頁面所包含流的優先順序。
2.7 可重置
HTTP1.x 的有一個缺點是:當一個含有確切值的 Content-Length
的 HTTP 訊息被送出之後,你就很難中斷它了。當然,通常你可以斷開整個 TCP 連結(但也不總是可以這樣),但這樣導致的代價就是需要通過三次握手來重新建立一個新的TCP連線。
一個更好的方案是隻終止當前傳輸的訊息並重新傳送一個新的。在http2裡面,我們可以通過傳送 RST_STREAM 幀來實現這種需求,從而避免浪費頻寬和中斷已有的連線。
2.8 流量控制
每個http2流都擁有自己的公示的流量視窗,它可以限制另一端傳送資料。如果你正好知道SSH的工作原理的話,這兩者非常相似。
對於每個流來說,兩端都必須告訴對方自己還有足夠的空間來處理新的資料,而在該視窗被擴大前,另一端只被允許傳送這麼多資料。
只有資料幀會受到流量控制。
總結
我們來歸納一下使用 HTTP2 能帶來的好處:
- 更小的傳輸體積,更小或者省略重複的頭訊息
- 突破原有的 TCP 連線併發限制,使用一個 TCP 連線即可實現多請求併發,單連結也能減輕服務端的壓力(更少的記憶體和 CPU 使用)
- 解決 HOLB 線頭問題,慢的請求或者先傳送的請求不會阻塞其他請求的返回
- 結合 CDN 提供實時性更高,延遲更低的內容分發代理服務,大大減少白屏時間
- 資料傳輸優先順序可控,使網站可以實現更靈活和強大的頁面控制
- 能在不中斷 TCP 連線的情況下停止(重置)資料的傳送
本文主要是學習筆記和個人理解,首發於 xlaoyu.info, 部分圖片和文字引用網路,侵刪。
參考文章:
- Ideal HTTP Performance 作者是 Mark Nottingham,IETF HTTP Working Group 的主席,Akamai 公司的首席架構師。
- TCP那些事
- HTTP2概述
- HTTP2講解
- HTTP/2 is here, let’s optimize! Ilya Grigorik, Velocity SC 2015
- rfc7540
- HTTP2的總結
- HTTP2.0 的奇妙日常 -- alloyteam