HTTP1 到 HTTP3 的工程優化

HTTP/1.0

存在的問題

  1. 默認爲短連接,連接無法複用,網頁中的每個資源都會發起新的 TCP 連接

  2. 隊列頭部請求阻塞 (head of line blocking), 一個 HTTP 請求響應結束之後,才能發起下一個 HTTP 請求 (如果沒有某個特別慢的請求,就卡頓了 ...)

  3. 不支持範圍數據請求,即使只是需要某個資源的一部分內容 (例如視頻的某一段幀),也會將整個資源發送過來

這幾個問題已經全部消失在歷史長河中了,這裏簡單回顧下,不做詳細介紹了。

HTTP/1.1

首先增加了以下特性解決了 HTTP/1.0 存在的問題:

此外,還新增了以下新特性:

依然存在的問題

HTTP/2

HTTP/1 是文本協議,其中 Header 頭信息是文本數據,內容體數據可以是文本格式,也可以是二進制格式。HTTP/2 是二進制協議,Header 頭信息和內容體數據都是二進制的。

二進制分幀層

HTTP/2 在 應用層 (HTTP/2) 和傳輸層 (TCP or UDP) 之間增加一個二進制分幀層。在不改動 HTTP/1.1 的語義、方法、狀態碼、URI 以及頭部字段的情況下, 解決了 HTTP1.1 的性能限制,改進傳輸性能,實現低延遲和高吞吐量。

HTTP/2 將一個 HTTP 請求劃分爲 3 個部分:

  1. 幀 (Frame) : 一段二進制數據,是 HTTP/2 傳輸的最小單位,每個幀包含一個幀頭,用於標識該幀所屬的流,來自不同數據流的幀可以交錯發送,然後再根據每個幀頭的數據流標識符重新組裝數據

  2. 消息 (Message) : 和請求或響應對應的多個幀序列

  3. 流 (Stream) : 已建立的連接內的雙向數據字節流,可以承載一條或多條消息,每個流都有一個唯一標識符和可選的優先級信息,由客戶端發起的流必須使用奇數編號作爲標識符;由服務器發起的必須使用偶數編號作爲標識符,流標識符零 (0x0) 用於連接控制消息

請求 / 響應 多路複用

在整個通信過程中,只會有一個 TCP 連接存在,它承載了任意數量的雙向數據流 (Stream)。

如圖所示,客戶端正在向服務端傳輸 stream 5,服務端正在向客戶端交替傳輸 stream 1 和 stream 3, 因此存在 3 個並行的流 (3 個流位於雙向的一條 TCP 連接上面)。

這種單連接多資源的方式,減少服務端的鏈接壓力 (主要是握手和開啓 HTTPS 後的驗證), 內存佔用更少 (連接隊列), 連接吞吐量更大;而且由於 TCP 連接的減少而使網絡擁塞狀況得以改善 (減少 TCP 三次握手、開啓 HTTPS 後的 TLS 握手), 同時慢啓動時間的減少, 使擁塞和丟包恢復速度更快 (因爲當發生丟包時,TCP 擁塞窗口大小會因爲擁塞避免機制而減小,從而降低整個連接的最大吞吐量)。

通過二進制分幀層,可以解決 HTTP/1.1 中依然存在的 隊列頭部請求阻塞 (head of line blocking) 和 客戶端需要多個連接 兩個問題。

💡 該方案的本質是多路複用,這裏的「多路」指多個資源請求,「複用」指在同一個 TCP 連接上傳輸。

在 HTTP/1.1 協議中瀏覽器客戶端在同一時間,針對同一域名下的請求有一定數量限制。超過限制數目的請求會被阻塞。這也是爲何一些站點會有多個靜態資源 CDN 域名的原因之一,而 HTTP/2 的多路複用 (Multiplexing) 則允許同時通過單一的 HTTP/2 連接發起多重的請求 - 響應消息。因此 HTTP/2 可以很容易地去實現多流並行而不用依賴建立多個 TCP 連接,HTTP/2 把 HTTP 協議通信的基本單位縮小爲一個一個的幀,這些幀對應着邏輯流中的消息。並行地在同一個 TCP 連接上雙向交換消息。

爲什麼只有一個 TCP 連接?

在 HTTP/1.1 協議中瀏覽器打開單個域名網站可能會使用多個連接 (實現多站點傳輸),結果就是單個站點頁面加載時會打開數十個 TCP 連接。一個應用程序打開如此多的 TCP 連接,這已經遠遠超出了最初的 TCP 設計理念,而且由於每個 TCP 連接都會響應大量的數據,會增加網絡緩衝區的溢出風險,導致網絡擁塞事件並重新開始數據傳輸。

請求優先級

多個 HTTP 請求同時發送時,會產生多個數據流,每個數據流中有一個優先級的標識,服務器端可以根據這個標識來決定響應的優先順序。

  1. 每個數據流可以分配一個 1 到 256 之間的整數作爲其權重值

  2. 每個數據流可以被指定對另一個數據流的顯式依賴

對於瀏覽器來說,並非所有資源都擁有同樣的優先級 (例如大多數情況下 HTML 文件應該比 CSS 文件擁有更高的優先級),爲了加速頁面訪問,現代瀏覽器都會根據資源的具體類型和在頁面上的位置進行優先級排序, 甚至會根據歷史訪問響應時間記錄來學習優先級,例如某個資源在之前訪問時被阻塞了,那麼這個資源在將來的訪問中會獲得更好的優先級。

通過設置合理的請求優先級,可以有效緩解 隊列頭部請求阻塞 (head of line blocking) 和 TCP 連接的利用率低 兩個問題。默認情況下,瀏覽器的優先級機制已經優化的足夠好,無需在代碼層面設置資源優先級。

服務端推送

HTTP/2.0 在客戶端請求一個資源時,會把相關的資源一起發送給客戶端,客戶端就不需要再次發起請求了。

例如客戶端請求 page.html 頁面,服務端順帶着就把 script.js 和 style.css 等相關的資源一起發給客戶端。

推送的資源有如下特點:

PUSH_PROMISE 幀

服務端的所有推送都是通過 PUSH_PROMISE 幀發起的,它表示服務端會將資源推送到客戶端,並且允許服務端在客戶端請求之前就發送相關資源給客戶端。這些資源可能是客戶端未直接請求的,但服務端認爲客戶端可能會需要的資源。通過推送可以避免客戶端發起額外的請求來獲取這些資源,從而加快頁面加載速度。PUSH_PROMISE 幀包含了推送資源的相關信息,如資源的 URL、HTTP 頭部等,客戶端接收到 PUSH_PROMISE 幀,可以選擇拒絕流 (通過 RST_STREAM 幀),例如資源已經存在於客戶端的緩存中。

HPACK 壓縮

HTTP/2 要求客戶端和服務器同時維護和更新一個包含之前見過的頭部字段表,從而避免了重複傳輸。HTTP/2 中通信雙方各自緩存一份頭部字段表,如:把 Content-Type:text/html 存入索引表中,後續如果要用到這個頭,只需要發送對應的索引號就可以了。

通過頭部壓縮,解決了 HTTP/1.1 中頭部重複導致的不必要的數據傳輸問題。

作爲進一步的優化,HPACK 壓縮上下文 由靜態和動態表組成:

流控制

流控制是一種發送方與接收方之間的協商機制,防止雙方向對方發送大量數據時,造成對方負載過重,或者限制特定資源的流量速率。例如客戶端請求了一個高優先級的大視頻流,但是用戶觀看幾秒後暫停了,此時客戶端應該暫停或限制其從服務器端的數據傳輸,避免請求和緩衝不必要的數據。

本質上這是一個流量控制問題,也許你會想到 TCP 中的流量控制機制,但是 HTTP/2 是使用單個 TCP 連接進行多路複用的,這樣一來, TCP 傳輸層流量控制既沒有足夠的流量控制粒度 (沒有辦法以 HTTP 請求資源爲粒度進行控制,因爲得不償失,會直接浪費掉多路複用帶來的所有好處), 也沒有必要提供應用層的 API 來控制單個流的傳輸控制。爲了解決這個問題,HTTP/2 提供了一組簡單的構建塊,允許客戶端和服務端實現自己的連接控制和流控制。

HTTP/2 流控制具體的規則如下:

HTTP/2 沒有指定任何特定的算法來實現流量控制,它僅提供了簡單的構建塊,並將實現委託給客戶端和服務器,客戶端和服務器可以使用它實現自定義策略調節資源分配。應用層流量控制允許瀏覽器只獲取特定資源的一部分,通過將流量控制窗口 (WINDOW_UPDATE) 減少到零來實現暫停資源獲取,然後在合適的時間再進行恢復。例如獲取圖像的預覽圖 (該圖像內容的一部分),顯示預覽圖的同時允許其他高優先級的請求繼續獲取,並在優先級更高的資源完成加載後恢復繼續圖像的獲取請求。

一次 HTTP/2 通信示例

  1. 首次訪問時,瀏覽器請求頭部加上 upgrade: h2c 標識,聲明客戶端支持 HTTP/2,詢問服務器要不要更換協議

  2. 瀏覽器同時發送 HTTP/2-Settings 頭部,帶上 base64 編碼的 SETTINGS frame

  3. 對於 HTTPS 請求,是在 TLS 握手階段進行協商,瀏覽器發送 ClientHello 時,帶上 h2 標誌,表明客戶端支持 HTTP/2

  4. 如果服務器不支持,則忽略 upgrade 頭部,正常響應。如果支持,則發送 101 響應,以空行結束響應,並開始發送 HTTP/2 幀

  5. 服務器要先響應 connection preface,帶上 SETTINGS frame

  6. 服務器創建新流,推送 a.js。然後繼續發送 index.html 文件和 a.js 文件的 response header、response body

  7. 瀏覽器收到 PUSH_PROMISE 幀,發現服務器要推送的內容已經在瀏覽器緩存裏了,發送 RST_STREAM 拒絕推送

  8. 服務器收到 RST_STREAM 幀後,不再推送 a.js 文件剩餘的數據

  9. 服務器想要關閉連接,發送 GOAWAY 幀

檢測是否支持 HTTP/2

通過 CURL 命令來檢測網站是否支持 HTTP/2 協議。

$ curl -I "https://dbwu.tech"

# 輸出如下

HTTP/2 200
date: Sun, 22 Jan 2023 04:15:29 GMT
content-type: text/html; charset=utf-8
...

直接使用 --http/2 參數指定 CURL 請求使用 HTTP/2 協議。

$ curl --http/2 "https://dbwu.tech"

也可以通過 在線工具 [1] 進行檢測。

HTTP/1 升級後過時的優化方案

升級到 HTTP/2 之後,很多 HTTP/1 中的優化方案,在 HTTP/2 中就沒有存在的必要了,例如下面這些曾經的 “經典” 優化方案:

HTTP/1 升級後仍然有效的的優化方案

除了上述升級到 HTTP/2 失效的優化方案外,大部分在 HTTP/1 優化的方案在 HTTP/2 中仍然有效嗎,例如下面這些方案:

HTTP/2 的優化空間

HTTP/2 針對基於 TCP 協議棧的 HTTP 優化,幾乎上已經達到了最大化,如果需要繼續深入優化,只能從協議棧本身的架構做調整,當然也就是 HTTP/3 協議主要做的工作。

優化核心目標依然是 TCP 的可靠性機制導致的傳輸效率和延遲問題:

HTTP/3

HTTP/3 是基於 QUIC(Quick UDP Internet Connections)協議的新一代 HTTP 協議。

QUIC

QUIC 是由 Google 提出的基於 UDP 進行多路複用的傳輸協議,是一個 UDP 版的 TCP + TLS + HTTP/2 替代方案實現。QUIC 沒有連接的概念,不需要三次握手,在應用程序層面,實現了 TCP 的可靠性,TLS 的安全性和 HTTP2 的併發性。在設備支持層面,只需要客戶端和服務端的應用程序支持 QUIC 協議即可,無操作系統和中間設備的限制。

QUIC 丟掉了 TCP 的包袱,基於 UDP,實現了一個安全高效可靠的 HTTP 通信協議。憑藉着 0-RTT 建立連接、傳輸層多路複用、連接遷移、改進的擁塞控制、流量控制等特性,QUIC 在絕大多數場景下獲得了比 HTTP/2 更好的效果。

題外話

爲什麼不發明一個新的傳輸協議

事實上,面對傳統傳輸層 TCP 和 UDP 協議的各種問題和不足,創新型的傳輸層協議最終都沒有能成爲行業標準。因爲這不單單是技術問題, 客戶端與服務端之間要經過網絡中的運營商防火牆、路由器、NAT 等,這些設備中很多默認只支持 TCP 和 UDP 協議,那麼可想而知,新型協議根本無法在互聯網普及。而且,即使上述所有的中間設備想要支持新型協議,那麼更新和部署新的網絡協議棧、操作系統內核等基礎設施和軟件,必然是一個十分緩慢的過程,在此期間造成的停機等問題引起的經濟損失可能是無法估量的。

核心優化

最重要的優化就是使用 QUIC 協議代替了 HTTP/2 中的依賴的 TCP 協議棧。

0-RTT

QUIC 協議可以實現 0-RTT 建立連接 (0-RTT 是指通信雙方發起通信連接時,第一個數據包就可以攜帶有效的業務數據),而 TCP 需要 3-RTT 建立連接,這個優勢不止體現在初始建立連接時,在網絡發生變化時同樣適用。

關於 0-RTT,需要說明的是: 如果客戶端和服務器是第一次通信,那麼需要經過 1-RTT (主要是客戶端獲取服務端加密配置),如果已經有過一次通信之後, 後續客戶端和服務端的通信連接就是 0-RTT。限於篇幅,第一次客戶端連接到服務端獲取密鑰及加密配置的過程,本文不再展開描述。

多路複用

QUIC 中的每個 stream 之間是相互獨立的,單個 stream 丟失了,不會影響到其他 stream,QUIC 協議在發送數據時會拆分爲多個包, 這樣就完全解決了 隊列頭部請求阻塞 (head of line blocking) 問題。儘管 QUIC 消除了 HTTP/2 的隊列頭部請求阻塞問題,但其依賴的 UDP 本身是無序交付的,也就是數據不一定按照發送時的順序到達, (所以並不是切換到 HTTP/3 就萬事大吉了,客戶端和服務端必須要根據實際業務場景,嘗試做更多的優化工作)。

單調遞增的序列號

TCP 中,每一個數據包都有一個序列號標識(seq),如果接收端超時沒有收到,就會要求重發標識爲 seq 的包,如果此時恰好接受到了超時的包, 則無法區分哪個是超時的包,哪個是重傳的包。

如果客戶端認爲收到的包是重傳包,但是實際是超時的包,這樣就會導致計算出來的 RTT 值偏小,反之計算出來的 RTT 值偏大。

在上面的示例圖中,RTT 計算出來的 RTT 值比實際值要小。

在上面的示例圖中,RTT 計算出來的 RTT 值比實際值要大。

QUIC 中的每一個包的標識(Packet Number)都是單調遞增的,重傳的序號一定大於超時的序號,這樣就能有效地區分超時和重傳。

禁止 Reneging

TCP 中,如果接收方內存不夠或 Buffer 溢出,則可能會把已接收的包丟棄,這種行爲對數據重傳產生了很大的干擾,在 QUIC 中是明確禁止的。在 QUIC 中,一個包只要被 ACK ,就認爲一定會被正確接收。

批量 ACK

TCP 中每收到 3 個數據包就要返回一個 ACK,而 QUIC 最多可以收到 256 個包之後,才返回 ACK。在丟包率比較嚴重的網絡下,更多的 ACK 塊可以減少重傳量,提升網絡效率。

ACK Delay

TCP 計算 RTT 時沒有考慮接收方接收到數據到發送確認消息之間的延遲,也就是所謂的 ACK Delay。QUIC 充分考慮到 ACK Delay,這樣 RTT 的計算會更加準確。

流量控制

TCP 通過滑動窗口來控制流量,如果某一個包丟失了,滑動窗口並不能跨過丟失的包繼續滑動,而是會卡在丟失的位置,等待數據重傳後,才能繼續滑動。

QUIC 流量控制的核心是:不能建立太多的連接,以免響應端處理不過來;不能讓某一個連接佔用大量的資源,讓其他連接沒有資源可用。爲此 QUIC 流量控制分爲 連接級別和 Stream 級別 :

連接遷移

TCP 連接是由(源 IP,源端口,目的 IP,目的端口)組成,這個四元組中一旦有一項值發生改變,這個連接也就不能用了。如果我們從 wifi 網絡切換到 4G 網絡,IP 地址就會改變,這個時候 TCP 連接也自然斷掉了。

QUIC 使用客戶端生成的 64 位 ID 來表示一條連接,只要 ID 不變,這條連接也就一直維持着,不會中斷。

前向糾錯機制

QUIC 使用前向糾錯 (FEC,Forward Error Correction) 技術增加協議的容錯性。

如圖所示,一段數據被切分爲 10 個包後,依次對每個包進行異或運算,運算結果會作爲 FEC 包與數據包一起被傳輸,如果不幸在傳輸過程中有一個數據包丟失, 那麼就可以根據剩餘 9 個包以及 FEC 包推算出丟失的那個包的數據,這樣就大大增加了協議的容錯性。

這是符合現階段網絡技術的一種方案,現階段帶寬已經不是網絡傳輸的瓶頸,往返時間纔是,所以新的網絡傳輸協議可以適當增加數據冗餘,減少重傳操作。當然這種情況只適用於丟失一個包的情況下,如果丟失了多個包,就只能進行重傳了。

QPACK 壓縮

QPACK 壓縮是 HTTP/3 中使用的字段壓縮格式,可以使 HTTP/2 中使用的 HPACK 壓縮格式與 QUIC 協議兼容,兩種壓縮格式通過使用不同的機制來滿足傳輸層協議的要求。

那麼 HTTP/3 中爲什麼不能繼續使用 HPACK 壓縮格式呢

因爲 HPACK 格式是爲 TCP 協議創建的格式,這種格式工作的前提就是默認數據字節流按照順序達到,如果 HTTP/3 中繼續使用 HPACK 壓縮格式, 就會導致額外的 隊列頭部請求阻塞 (head of line blocking) 問題,因爲 HPACK 依賴於對已經到達字段的引用。但是,HTTP/3 中的 QUIC 的傳輸層協議爲 UDP, 數據字節不會按照順序到達,因此 HPACK 格式中的動態表中可能會包含還未達到的數據的引用,於是就會阻塞直到被引用的數據到達。

爲了解決這個問題,QPACK 引入了兩種單向流類型: 編碼器流和解碼器流,除了傳遞 HTTP/3 消息的雙向字節流之外,客戶端和服務端可以選擇性地打開這兩個單向編碼流, 將具體的指令傳輸給對方。因爲流是單向的,所以發送方只需要發送數據即可,無需等到接收方的響應。

最終,雖然增加額外的單向字節流會帶來一定的性能開銷,但是卻可以徹底解決 隊列頭部請求阻塞 (head of line blocking) 問題,而且 QPACK 格式規範爲客戶端和服務器實現提供了很高的自由度, 可以由實現方來決定具體的問題重要程度和方案傾向性: 緩解並解決隊列頭部請求阻塞還是實現更高級別的壓縮效果。

QUIC 相比 TCP 的優勢

QUIC 存在的限制

是否支持 HTTP/3

網站檢測

可以通過 在線工具 [2] 進行檢測。

瀏覽器支持情況

可以通過 瀏覽器在線工具 [3] 進行檢測,下面是筆者的 Chrome 支持情況。

小結

本文介紹了從 HTTP/1 到 HTTP/3 中間四次比較大的協議升級變更,着重分析了每次升級前後的性能差異及相關特性變更 (安全方面的變更未分析,感興趣的讀者可以自行閱讀相關 RFC)。作爲開發者,平時可能不會去關注這些網絡底層細節,但是深入理解整個 HTTP 協議的升級變遷過程,可以幫助我們理解這背後的工程挑戰以及解決思路,這纔是最有價值的部分。

終極用戶體驗模式

瀏覽器作爲操作系統,站點作爲應用軟件,這算不算是從 B/S 架構又回到了 C/S 架構?

Reference

擴展閱讀

鏈接

[1]

在線工具: https://http2.pro/check

[2]

在線工具: https://domsignal.com/http3-test

[3]

瀏覽器在線工具: https://caniuse.com/http3

[4]

Hypertext Transfer Protocol Version 2 (HTTP/2): https://datatracker.ietf.org/doc/html/rfc7540

[5]

QUIC: A UDP-Based Multiplexed and Secure Transport: https://datatracker.ietf.org/doc/html/rfc9000

[6]

HTTP/3: https://datatracker.ietf.org/doc/rfc9114/

[7]

HTTP/2 explained: https://daniel.haxx.se/http2/

[8]

HTTP/2: https://hpbn.co/http2/

[9]

Introduction to HTTP/2: https://web.dev/performance-http2/

[10]

what-is-http3: https://www.cloudflare.com/zh-cn/learning/performance/what-is-http3/

[11]

HTTP/2 is here, let’s optimize!: https://docs.google.com/presentation/d/1r7QXGYOLCh4fcUq0jDdDwKJWNqWK1o4xMtYpKZCJYjM/edit?pli=1#slide=id.p19

[12]

HTTP Documentation Core Specifications: https://httpwg.org/specs/

[13]

HTTP/2 服務器推送(Server Push)教程: https://www.ruanyifeng.com/blog/2018/03/http2_server_push.html

[14]

HTTP/2 For Web Developers: https://blog.cloudflare.com/http-2-for-web-developers/

[15]

Web Almanac: https://github.com/HTTPArchive/almanac.httparchive.org

[16]

HTTP/2 HPACK 實際應用舉例: https://halfrost.com/http2-hpack-example/

[17]

詳解 HTTP/2 頭壓縮算法 —— HPACK: https://halfrost.com/http2-header-compression/

[18]

QUIC 協議原理分析: https://zhuanlan.zhihu.com/p/32553477

[19]

HTTP/3 From A To Z: Core Concepts: https://www.smashingmagazine.com/2021/08/http3-core-concepts-part1/

[20]

A Comprehensive Guide To HTTP/3 And QUIC: https://www.debugbear.com/blog/http3-quic-protocol-guide

[21]

A Comparison between SCTP and QUIC: https://datatracker.ietf.org/doc/html/draft-joseph-quic-comparison-quic-sctp-00

[22]

HTTP RFCs have evolved: A Cloudflare view of HTTP usage trends: https://blog.cloudflare.com/cloudflare-view-http3-usage/

[23]

Introducing HTTP/3 Prioritization: https://blog.cloudflare.com/better-http-3-prioritization-for-a-faster-web/

[24]

Caddy: https://github.com/caddyserver/caddy

[25]

Report: State of the Web: https://httparchive.org/reports/state-of-the-web

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/fla7cnUktoXo_nQ7v2rU9Q