HTTP1 到 HTTP3 的工程優(yōu)化
HTTP/1.0
存在的問(wèn)題
- 默認(rèn)為短連接,連接無(wú)法復(fù)用,網(wǎng)頁(yè)中的每個(gè)資源都會(huì)發(fā)起新的 TCP 連接
- 隊(duì)列頭部請(qǐng)求阻塞 (head of line blocking), 一個(gè) HTTP 請(qǐng)求響應(yīng)結(jié)束之后,才能發(fā)起下一個(gè) HTTP 請(qǐng)求 (如果沒(méi)有某個(gè)特別慢的請(qǐng)求,就卡頓了 ...)
- 不支持范圍數(shù)據(jù)請(qǐng)求,即使只是需要某個(gè)資源的一部分內(nèi)容 (例如視頻的某一段幀),也會(huì)將整個(gè)資源發(fā)送過(guò)來(lái)
這幾個(gè)問(wèn)題已經(jīng)全部消失在歷史長(zhǎng)河中了,這里簡(jiǎn)單回顧下,不做詳細(xì)介紹了。
HTTP/1.1
首先增加了以下特性解決了 HTTP/1.0 存在的問(wèn)題:
- 默認(rèn)啟用長(zhǎng)連接
- 支持同時(shí)打開(kāi)多個(gè) TCP 連接,采用 Pipeline 請(qǐng)求方式,多個(gè)請(qǐng)求可以通過(guò)多個(gè)連接串行化請(qǐng)求
- 支持資源分塊范圍數(shù)據(jù)傳輸
此外,還新增了以下新特性:
- 支持虛擬主機(jī)
- 新增 Cache-Control、E-Tag, max-age 緩存處理指令
- 新增 PUT、PATCH、HEAD、OPTIONS、DELETE 請(qǐng)求方法
依然存在的問(wèn)題
- 雖然 TCP 連接可以復(fù)用,但是服務(wù)端響應(yīng)只能按照客戶端請(qǐng)求順序返回,隊(duì)列頭部請(qǐng)求阻塞 (head of line blocking) 并沒(méi)有完全解決
- 客戶端需要使用多個(gè)連接才能實(shí)現(xiàn)并發(fā)和縮短延遲
- 無(wú)法壓縮請(qǐng)求和響應(yīng)頭部,導(dǎo)致不必要的數(shù)據(jù)傳輸流量
- 不支持有效的資源優(yōu)先級(jí),導(dǎo)致 TCP 連接的利用率低
HTTP/2
HTTP/1 是文本協(xié)議,其中 Header 頭信息是文本數(shù)據(jù),內(nèi)容體數(shù)據(jù)可以是文本格式,也可以是二進(jìn)制格式。HTTP/2 是二進(jìn)制協(xié)議,Header 頭信息和內(nèi)容體數(shù)據(jù)都是二進(jìn)制的。
二進(jìn)制分幀層
HTTP/2 在 應(yīng)用層(HTTP/2)和傳輸層(TCP or UDP)之間增加一個(gè)二進(jìn)制分幀層。在不改動(dòng) HTTP/1.1 的語(yǔ)義、方法、狀態(tài)碼、URI 以及頭部字段的情況下, 解決了 HTTP1.1 的性能限制,改進(jìn)傳輸性能,實(shí)現(xiàn)低延遲和高吞吐量。

HTTP/2 將一個(gè) HTTP 請(qǐng)求劃分為 3 個(gè)部分:
- 幀 (Frame) : 一段二進(jìn)制數(shù)據(jù),是 HTTP/2 傳輸?shù)淖钚挝唬總€(gè)幀包含一個(gè)幀頭,用于標(biāo)識(shí)該幀所屬的流,來(lái)自不同數(shù)據(jù)流的幀可以交錯(cuò)發(fā)送,然后再根據(jù)每個(gè)幀頭的數(shù)據(jù)流標(biāo)識(shí)符重新組裝數(shù)據(jù)
- 消息 (Message) : 和請(qǐng)求或響應(yīng)對(duì)應(yīng)的多個(gè)幀序列
- 流 (Stream) : 已建立的連接內(nèi)的雙向數(shù)據(jù)字節(jié)流,可以承載一條或多條消息,每個(gè)流都有一個(gè)唯一標(biāo)識(shí)符和可選的優(yōu)先級(jí)信息,由客戶端發(fā)起的流必須使用奇數(shù)編號(hào)作為標(biāo)識(shí)符;由服務(wù)器發(fā)起的必須使用偶數(shù)編號(hào)作為標(biāo)識(shí)符,流標(biāo)識(shí)符零(0x0)用于連接控制消息

請(qǐng)求/響應(yīng) 多路復(fù)用

在整個(gè)通信過(guò)程中,只會(huì)有一個(gè) TCP 連接存在,它承載了任意數(shù)量的雙向數(shù)據(jù)流 (Stream)。

如圖所示,客戶端正在向服務(wù)端傳輸 stream 5,服務(wù)端正在向客戶端交替?zhèn)鬏?stream 1 和 stream 3, 因此存在 3 個(gè)并行的流 (3 個(gè)流位于雙向的一條 TCP 連接上面)。
這種單連接多資源的方式,減少服務(wù)端的鏈接壓力 (主要是握手和開(kāi)啟 HTTPS 后的驗(yàn)證), 內(nèi)存占用更少 (連接隊(duì)列), 連接吞吐量更大;而且由于 TCP 連接的減少而使網(wǎng)絡(luò)擁塞狀況得以改善 (減少 TCP 三次握手、開(kāi)啟 HTTPS 后的 TLS 握手), 同時(shí)慢啟動(dòng)時(shí)間的減少,使擁塞和丟包恢復(fù)速度更快 (因?yàn)楫?dāng)發(fā)生丟包時(shí),TCP 擁塞窗口大小會(huì)因?yàn)閾砣苊鈾C(jī)制而減小,從而降低整個(gè)連接的最大吞吐量)。
通過(guò)二進(jìn)制分幀層,可以解決 HTTP/1.1 中依然存在的 隊(duì)列頭部請(qǐng)求阻塞 (head of line blocking) 和 客戶端需要多個(gè)連接 兩個(gè)問(wèn)題。
?? 該方案的本質(zhì)是多路復(fù)用,這里的「多路」指多個(gè)資源請(qǐng)求,「復(fù)用」指在同一個(gè) TCP 連接上傳輸。
在 HTTP/1.1 協(xié)議中瀏覽器客戶端在同一時(shí)間,針對(duì)同一域名下的請(qǐng)求有一定數(shù)量限制。超過(guò)限制數(shù)目的請(qǐng)求會(huì)被阻塞。這也是為何一些站點(diǎn)會(huì)有多個(gè)靜態(tài)資源 CDN 域名的原因之一,而 HTTP/2 的多路復(fù)用(Multiplexing) 則允許同時(shí)通過(guò)單一的 HTTP/2 連接發(fā)起多重的請(qǐng)求-響應(yīng)消息。因此 HTTP/2 可以很容易地去實(shí)現(xiàn)多流并行而不用依賴建立多個(gè) TCP 連接,HTTP/2 把 HTTP 協(xié)議通信的基本單位縮小為一個(gè)一個(gè)的幀,這些幀對(duì)應(yīng)著邏輯流中的消息。并行地在同一個(gè) TCP 連接上雙向交換消息。
為什么只有一個(gè) TCP 連接?
在 HTTP/1.1 協(xié)議中瀏覽器打開(kāi)單個(gè)域名網(wǎng)站可能會(huì)使用多個(gè)連接 (實(shí)現(xiàn)多站點(diǎn)傳輸),結(jié)果就是單個(gè)站點(diǎn)頁(yè)面加載時(shí)會(huì)打開(kāi)數(shù)十個(gè) TCP 連接。一個(gè)應(yīng)用程序打開(kāi)如此多的 TCP 連接,這已經(jīng)遠(yuǎn)遠(yuǎn)超出了最初的 TCP 設(shè)計(jì)理念,而且由于每個(gè) TCP 連接都會(huì)響應(yīng)大量的數(shù)據(jù),會(huì)增加網(wǎng)絡(luò)緩沖區(qū)的溢出風(fēng)險(xiǎn),導(dǎo)致網(wǎng)絡(luò)擁塞事件并重新開(kāi)始數(shù)據(jù)傳輸。

請(qǐng)求優(yōu)先級(jí)
多個(gè) HTTP 請(qǐng)求同時(shí)發(fā)送時(shí),會(huì)產(chǎn)生多個(gè)數(shù)據(jù)流,每個(gè)數(shù)據(jù)流中有一個(gè)優(yōu)先級(jí)的標(biāo)識(shí),服務(wù)器端可以根據(jù)這個(gè)標(biāo)識(shí)來(lái)決定響應(yīng)的優(yōu)先順序。
- 每個(gè)數(shù)據(jù)流可以分配一個(gè) 1 到 256 之間的整數(shù)作為其權(quán)重值
- 每個(gè)數(shù)據(jù)流可以被指定對(duì)另一個(gè)數(shù)據(jù)流的顯式依賴
對(duì)于瀏覽器來(lái)說(shuō),并非所有資源都擁有同樣的優(yōu)先級(jí) (例如大多數(shù)情況下 HTML 文件應(yīng)該比 CSS 文件擁有更高的優(yōu)先級(jí)),為了加速頁(yè)面訪問(wèn),現(xiàn)代瀏覽器都會(huì)根據(jù)資源的具體類型和在頁(yè)面上的位置進(jìn)行優(yōu)先級(jí)排序, 甚至?xí)鶕?jù)歷史訪問(wèn)響應(yīng)時(shí)間記錄來(lái)學(xué)習(xí)優(yōu)先級(jí),例如某個(gè)資源在之前訪問(wèn)時(shí)被阻塞了,那么這個(gè)資源在將來(lái)的訪問(wèn)中會(huì)獲得更好的優(yōu)先級(jí)。
通過(guò)設(shè)置合理的請(qǐng)求優(yōu)先級(jí),可以有效緩解 隊(duì)列頭部請(qǐng)求阻塞 (head of line blocking) 和 TCP 連接的利用率低 兩個(gè)問(wèn)題。默認(rèn)情況下,瀏覽器的優(yōu)先級(jí)機(jī)制已經(jīng)優(yōu)化的足夠好,無(wú)需在代碼層面設(shè)置資源優(yōu)先級(jí)。
服務(wù)端推送
HTTP/2.0 在客戶端請(qǐng)求一個(gè)資源時(shí),會(huì)把相關(guān)的資源一起發(fā)送給客戶端,客戶端就不需要再次發(fā)起請(qǐng)求了。
例如客戶端請(qǐng)求 page.html 頁(yè)面,服務(wù)端順帶著就把 script.js 和 style.css 等相關(guān)的資源一起發(fā)給客戶端。

推送的資源有如下特點(diǎn):
- 可以被客戶端 (一般指瀏覽器) 緩存
- 可以被不同的頁(yè)面進(jìn)行復(fù)用
- 可以被服務(wù)端確定優(yōu)先級(jí)
- 客戶端完全控制服務(wù)端的推送行為: 限制并發(fā)推送流的數(shù)量、調(diào)整初始流量控制窗口、控制流首次打開(kāi)時(shí)推送的數(shù)據(jù)量、或者完全禁用服務(wù)端推送,這些首選項(xiàng)在 HTTP/2 建立連接時(shí)通過(guò)設(shè)置幀進(jìn)行傳遞,并且可以隨時(shí)更新
- 每個(gè)被推送的資源都是一個(gè)流,這允許客戶端單獨(dú)對(duì)其進(jìn)行多路復(fù)用、優(yōu)先級(jí)排序和數(shù)據(jù)處理,當(dāng)然,推送的資源必須遵守同源策略
PUSH_PROMISE 幀
服務(wù)端的所有推送都是通過(guò) PUSH_PROMISE 幀發(fā)起的,它表示服務(wù)端會(huì)將資源推送到客戶端,并且允許服務(wù)端在客戶端請(qǐng)求之前就發(fā)送相關(guān)資源給客戶端。這些資源可能是客戶端未直接請(qǐng)求的,但服務(wù)端認(rèn)為客戶端可能會(huì)需要的資源。通過(guò)推送可以避免客戶端發(fā)起額外的請(qǐng)求來(lái)獲取這些資源,從而加快頁(yè)面加載速度。PUSH_PROMISE 幀包含了推送資源的相關(guān)信息,如資源的 URL、HTTP 頭部等,客戶端接收到 PUSH_PROMISE 幀,可以選擇拒絕流 (通過(guò) RST_STREAM 幀),例如資源已經(jīng)存在于客戶端的緩存中。
HPACK 壓縮
HTTP/2 要求客戶端和服務(wù)器同時(shí)維護(hù)和更新一個(gè)包含之前見(jiàn)過(guò)的頭部字段表,從而避免了重復(fù)傳輸。HTTP/2 中通信雙方各自緩存一份頭部字段表,如:把 Content-Type:text/html 存入索引表中,后續(xù)如果要用到這個(gè)頭,只需要發(fā)送對(duì)應(yīng)的索引號(hào)就可以了。
通過(guò)頭部壓縮,解決了 HTTP/1.1 中頭部重復(fù)導(dǎo)致的不必要的數(shù)據(jù)傳輸問(wèn)題。

作為進(jìn)一步的優(yōu)化,HPACK 壓縮上下文 由靜態(tài)和動(dòng)態(tài)表組成:
- 靜態(tài)表中定義了規(guī)范標(biāo)準(zhǔn),主要提供連接可能會(huì)使用到的常見(jiàn) HTTP 報(bào)頭字段 (例如 host, path, method ...)
- 動(dòng)態(tài)表初始時(shí)為空,但是會(huì)根據(jù)特定連接內(nèi)的不同字段值進(jìn)行更新,通過(guò)對(duì)新出現(xiàn)的字段值使用
Huffman編碼,以及對(duì) 客戶端/服務(wù)端 雙方靜態(tài)表或動(dòng)態(tài)表中已經(jīng)存在的字段值更新索引,可以減少每個(gè)請(qǐng)求的大小

流控制
流控制是一種發(fā)送方與接收方之間的協(xié)商機(jī)制,防止雙方向?qū)Ψ桨l(fā)送大量數(shù)據(jù)時(shí),造成對(duì)方負(fù)載過(guò)重,或者限制特定資源的流量速率。例如客戶端請(qǐng)求了一個(gè)高優(yōu)先級(jí)的大視頻流,但是用戶觀看幾秒后暫停了,此時(shí)客戶端應(yīng)該暫?;蛳拗破鋸姆?wù)器端的數(shù)據(jù)傳輸,避免請(qǐng)求和緩沖不必要的數(shù)據(jù)。
本質(zhì)上這是一個(gè)流量控制問(wèn)題,也許你會(huì)想到 TCP 中的流量控制機(jī)制,但是 HTTP/2 是使用單個(gè) TCP 連接進(jìn)行多路復(fù)用的,這樣一來(lái), TCP 傳輸層流量控制既沒(méi)有足夠的流量控制粒度 (沒(méi)有辦法以 HTTP 請(qǐng)求資源為粒度進(jìn)行控制,因?yàn)榈貌粌斒?,?huì)直接浪費(fèi)掉多路復(fù)用帶來(lái)的所有好處), 也沒(méi)有必要提供應(yīng)用層的 API 來(lái)控制單個(gè)流的傳輸控制。為了解決這個(gè)問(wèn)題,HTTP/2 提供了一組簡(jiǎn)單的構(gòu)建塊,允許客戶端和服務(wù)端實(shí)現(xiàn)自己的連接控制和流控制。
HTTP/2 流控制具體的規(guī)則如下:
- 流控制是定向的,接收方 (客戶端/服務(wù)端都是彼此的接收方) 可以選擇為每個(gè)流和整個(gè)連接設(shè)置窗口大小
- 流控制是基于窗口機(jī)制的,接收方會(huì)其初始連接和流控制窗口(以字節(jié)為單位),當(dāng)發(fā)送方發(fā)出數(shù)據(jù)幀時(shí),該窗口就會(huì)減少,當(dāng)發(fā)送方接收到來(lái)自接收方發(fā)送的 WINDOW_UPDATE 幀時(shí),該窗口就會(huì)增加
- 無(wú)法禁用流控制,當(dāng)建立 HTTP/2 連接時(shí),客戶端和服務(wù)器交換 SETTINGS 幀,用來(lái)設(shè)置兩個(gè)方向的流量控制窗口大小,流量控制窗口的默認(rèn)值設(shè)置為 65535 字節(jié), 最大窗口大小為(2^31 - 1 字節(jié)),并通過(guò)在接收到數(shù)據(jù)時(shí)發(fā)送 WINDOW_UPDATE 幀來(lái)更新
HTTP/2 沒(méi)有指定任何特定的算法來(lái)實(shí)現(xiàn)流量控制,它僅提供了簡(jiǎn)單的構(gòu)建塊,并將實(shí)現(xiàn)委托給客戶端和服務(wù)器,客戶端和服務(wù)器可以使用它實(shí)現(xiàn)自定義策略調(diào)節(jié)資源分配。應(yīng)用層流量控制允許瀏覽器只獲取特定資源的一部分,通過(guò)將流量控制窗口 (WINDOW_UPDATE) 減少到零來(lái)實(shí)現(xiàn)暫停資源獲取,然后在合適的時(shí)間再進(jìn)行恢復(fù)。例如獲取圖像的預(yù)覽圖 (該圖像內(nèi)容的一部分),顯示預(yù)覽圖的同時(shí)允許其他高優(yōu)先級(jí)的請(qǐng)求繼續(xù)獲取,并在優(yōu)先級(jí)更高的資源完成加載后恢復(fù)繼續(xù)圖像的獲取請(qǐng)求。
一次 HTTP/2 通信示例

- 首次訪問(wèn)時(shí),瀏覽器請(qǐng)求頭部加上 upgrade: h2c 標(biāo)識(shí),聲明客戶端支持 HTTP/2,詢問(wèn)服務(wù)器要不要更換協(xié)議
- 瀏覽器同時(shí)發(fā)送 HTTP/2-Settings 頭部,帶上 base64 編碼的 SETTINGS frame
- 對(duì)于 HTTPS 請(qǐng)求,是在 TLS 握手階段進(jìn)行協(xié)商,瀏覽器發(fā)送 ClientHello 時(shí),帶上 h2 標(biāo)志,表明客戶端支持 HTTP/2
- 如果服務(wù)器不支持,則忽略 upgrade 頭部,正常響應(yīng)。如果支持,則發(fā)送 101 響應(yīng),以空行結(jié)束響應(yīng),并開(kāi)始發(fā)送 HTTP/2 幀
- 服務(wù)器要先響應(yīng) connection preface,帶上 SETTINGS frame
- 服務(wù)器創(chuàng)建新流,推送 a.js。然后繼續(xù)發(fā)送 index.html 文件和 a.js 文件的 response header、response body
- 瀏覽器收到 PUSH_PROMISE 幀,發(fā)現(xiàn)服務(wù)器要推送的內(nèi)容已經(jīng)在瀏覽器緩存里了,發(fā)送 RST_STREAM 拒絕推送
- 服務(wù)器收到 RST_STREAM 幀后,不再推送 a.js 文件剩余的數(shù)據(jù)
- 服務(wù)器想要關(guān)閉連接,發(fā)送 GOAWAY 幀
檢測(cè)是否支持 HTTP/2
通過(guò) CURL 命令來(lái)檢測(cè)網(wǎng)站是否支持 HTTP/2 協(xié)議。
$ 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 參數(shù)指定 CURL 請(qǐng)求使用 HTTP/2 協(xié)議。
$
curl --http/2 "https://dbwu.tech"
也可以通過(guò) 在線工具[1] 進(jìn)行檢測(cè)。

HTTP/1 升級(jí)后過(guò)時(shí)的優(yōu)化方案
升級(jí)到 HTTP/2 之后,很多 HTTP/1 中的優(yōu)化方案,在 HTTP/2 中就沒(méi)有存在的必要了,例如下面這些曾經(jīng)的 “經(jīng)典” 優(yōu)化方案:
- Sprites: 將很多小圖合并成一張大圖,再利用 CSS 和 JavaScript 將小圖定位并切割出來(lái) (常見(jiàn)的業(yè)務(wù)場(chǎng)景如 Logo 圖集、網(wǎng)頁(yè)游戲道具等)

- Inlining: 將圖片的原始數(shù)據(jù) base64 編碼之后嵌入到 CSS 屬性中 (可以節(jié)約一次 HTTP 請(qǐng)求)

- Concatenation: 將許多小的靜態(tài)文件合并到一個(gè)大的靜態(tài)文件中


- Sharding: 將靜態(tài)資源分發(fā)到不同的域名 (即使沒(méi)有 HTTP/2, 這個(gè)方案也可以使用 CDN 來(lái)替代,方案的核心在于并發(fā) TCP 連接以及優(yōu)化 cookies)

HTTP/1 升級(jí)后仍然有效的的優(yōu)化方案
除了上述升級(jí)到 HTTP/2 失效的優(yōu)化方案外,大部分在 HTTP/1 優(yōu)化的方案在 HTTP/2 中仍然有效嗎,例如下面這些方案:
- 降低 DNS 輪詢 (可以參考主流云計(jì)算廠商提供的解決方案及實(shí)現(xiàn)原理)
- 使用 CDN (并且根據(jù)業(yè)務(wù)場(chǎng)景和服務(wù)進(jìn)行設(shè)計(jì)拆分)
- 盡可能使用瀏覽器緩存機(jī)制
- 盡可能優(yōu)化請(qǐng)求響應(yīng)時(shí)間和響應(yīng)內(nèi)容大小
- 盡可能消除重定向
HTTP/2 的優(yōu)化空間
HTTP/2 針對(duì)基于 TCP 協(xié)議棧的 HTTP 優(yōu)化,幾乎上已經(jīng)達(dá)到了最大化,如果需要繼續(xù)深入優(yōu)化,只能從協(xié)議棧本身的架構(gòu)做調(diào)整,當(dāng)然也就是 HTTP/3 協(xié)議主要做的工作。
優(yōu)化核心目標(biāo)依然是 TCP 的可靠性機(jī)制導(dǎo)致的傳輸效率和延遲問(wèn)題:
- TCP 建立連接時(shí)的三次握手,增加了請(qǐng)求延時(shí)
- TCP 內(nèi)部的擁塞控制、慢啟動(dòng)、擁塞避免等機(jī)制,可能導(dǎo)致傳輸效率不足
- HTTP/2 雖然解決了 HTTP/1 協(xié)議中的 隊(duì)列頭部請(qǐng)求阻塞 (head of line blocking) 問(wèn)題,但是 TCP 協(xié)議也存在類似的問(wèn)題: TCP 在傳輸時(shí)使用序列號(hào)標(biāo)識(shí)數(shù)據(jù)的順序,一旦某個(gè)數(shù)據(jù)丟失,后面的數(shù)據(jù)需要等待這個(gè)數(shù)據(jù)重傳后才能進(jìn)行下一步處理
- 根據(jù)測(cè)試表明,在較差的網(wǎng)絡(luò)環(huán)境中 (丟包率 >= 2%),HTTP/2 的性能甚至不如 HTTP/1, 因?yàn)?HTTP/1 一般會(huì)打開(kāi)多個(gè) TCP 連接,即使其中一個(gè)或多個(gè)連接出現(xiàn)丟包,剩下的連接依然可以進(jìn)行數(shù)據(jù)傳輸
HTTP/3
HTTP/3 是基于 QUIC(Quick UDP Internet Connections)協(xié)議的新一代 HTTP 協(xié)議。
QUIC
QUIC 是由 Google 提出的基于 UDP 進(jìn)行多路復(fù)用的傳輸協(xié)議,是一個(gè) UDP 版的 TCP + TLS + HTTP/2 替代方案實(shí)現(xiàn)。QUIC 沒(méi)有連接的概念,不需要三次握手,在應(yīng)用程序?qū)用?,?shí)現(xiàn)了 TCP 的可靠性,TLS 的安全性和 HTTP2 的并發(fā)性。在設(shè)備支持層面,只需要客戶端和服務(wù)端的應(yīng)用程序支持 QUIC 協(xié)議即可,無(wú)操作系統(tǒng)和中間設(shè)備的限制。
QUIC 丟掉了 TCP 的包袱,基于 UDP,實(shí)現(xiàn)了一個(gè)安全高效可靠的 HTTP 通信協(xié)議。憑借著 0-RTT 建立連接、傳輸層多路復(fù)用、連接遷移、改進(jìn)的擁塞控制、流量控制等特性,QUIC 在絕大多數(shù)場(chǎng)景下獲得了比 HTTP/2 更好的效果。

題外話
為什么不發(fā)明一個(gè)新的傳輸協(xié)議?
事實(shí)上,面對(duì)傳統(tǒng)傳輸層 TCP 和 UDP 協(xié)議的各種問(wèn)題和不足,創(chuàng)新型的傳輸層協(xié)議最終都沒(méi)有能成為行業(yè)標(biāo)準(zhǔn)。因?yàn)檫@不單單是技術(shù)問(wèn)題, 客戶端與服務(wù)端之間要經(jīng)過(guò)網(wǎng)絡(luò)中的運(yùn)營(yíng)商防火墻、路由器、NAT 等,這些設(shè)備中很多默認(rèn)只支持 TCP 和 UDP 協(xié)議,那么可想而知,新型協(xié)議根本無(wú)法在互聯(lián)網(wǎng)普及。而且,即使上述所有的中間設(shè)備想要支持新型協(xié)議,那么更新和部署新的網(wǎng)絡(luò)協(xié)議棧、操作系統(tǒng)內(nèi)核等基礎(chǔ)設(shè)施和軟件,必然是一個(gè)十分緩慢的過(guò)程,在此期間造成的停機(jī)等問(wèn)題引起的經(jīng)濟(jì)損失可能是無(wú)法估量的。
核心優(yōu)化
最重要的優(yōu)化就是使用 QUIC 協(xié)議代替了 HTTP/2 中的依賴的 TCP 協(xié)議棧。
0-RTT
QUIC 協(xié)議可以實(shí)現(xiàn) 0-RTT 建立連接 (0-RTT 是指通信雙方發(fā)起通信連接時(shí),第一個(gè)數(shù)據(jù)包就可以攜帶有效的業(yè)務(wù)數(shù)據(jù)),而 TCP 需要 3-RTT 建立連接,這個(gè)優(yōu)勢(shì)不止體現(xiàn)在初始建立連接時(shí),在網(wǎng)絡(luò)發(fā)生變化時(shí)同樣適用。

關(guān)于 0-RTT,需要說(shuō)明的是: 如果客戶端和服務(wù)器是第一次通信,那么需要經(jīng)過(guò) 1-RTT (主要是客戶端獲取服務(wù)端加密配置),如果已經(jīng)有過(guò)一次通信之后, 后續(xù)客戶端和服務(wù)端的通信連接就是 0-RTT。限于篇幅,第一次客戶端連接到服務(wù)端獲取密鑰及加密配置的過(guò)程,本文不再展開(kāi)描述。
多路復(fù)用

QUIC 中的每個(gè) stream 之間是相互獨(dú)立的,單個(gè) stream 丟失了,不會(huì)影響到其他 stream,QUIC 協(xié)議在發(fā)送數(shù)據(jù)時(shí)會(huì)拆分為多個(gè)包, 這樣就完全解決了 隊(duì)列頭部請(qǐng)求阻塞 (head of line blocking) 問(wèn)題。盡管 QUIC 消除了 HTTP/2 的隊(duì)列頭部請(qǐng)求阻塞問(wèn)題,但其依賴的 UDP 本身是無(wú)序交付的,也就是數(shù)據(jù)不一定按照發(fā)送時(shí)的順序到達(dá), (所以并不是切換到 HTTP/3 就萬(wàn)事大吉了,客戶端和服務(wù)端必須要根據(jù)實(shí)際業(yè)務(wù)場(chǎng)景,嘗試做更多的優(yōu)化工作)。
單調(diào)遞增的序列號(hào)
TCP 中,每一個(gè)數(shù)據(jù)包都有一個(gè)序列號(hào)標(biāo)識(shí)(seq),如果接收端超時(shí)沒(méi)有收到,就會(huì)要求重發(fā)標(biāo)識(shí)為 seq 的包,如果此時(shí)恰好接受到了超時(shí)的包, 則無(wú)法區(qū)分哪個(gè)是超時(shí)的包,哪個(gè)是重傳的包。
- RTT: Round Trip Time, 往返事件
- RTO: Retransmission Timeout, 超時(shí)重傳時(shí)間
如果客戶端認(rèn)為收到的包是重傳包,但是實(shí)際是超時(shí)的包,這樣就會(huì)導(dǎo)致計(jì)算出來(lái)的 RTT 值偏小,反之計(jì)算出來(lái)的 RTT 值偏大。

在上面的示例圖中,RTT 計(jì)算出來(lái)的 RTT 值比實(shí)際值要小。

在上面的示例圖中,RTT 計(jì)算出來(lái)的 RTT 值比實(shí)際值要大。
QUIC 中的每一個(gè)包的標(biāo)識(shí)(Packet Number)都是單調(diào)遞增的,重傳的序號(hào)一定大于超時(shí)的序號(hào),這樣就能有效地區(qū)分超時(shí)和重傳。
禁止 Reneging
TCP 中,如果接收方內(nèi)存不夠或 Buffer 溢出,則可能會(huì)把已接收的包丟棄,這種行為對(duì)數(shù)據(jù)重傳產(chǎn)生了很大的干擾,在 QUIC 中是明確禁止的。在 QUIC 中,一個(gè)包只要被 ACK ,就認(rèn)為一定會(huì)被正確接收。
批量 ACK
TCP 中每收到 3 個(gè)數(shù)據(jù)包就要返回一個(gè) ACK,而 QUIC 最多可以收到 256 個(gè)包之后,才返回 ACK。在丟包率比較嚴(yán)重的網(wǎng)絡(luò)下,更多的 ACK 塊可以減少重傳量,提升網(wǎng)絡(luò)效率。
ACK Delay
TCP 計(jì)算 RTT 時(shí)沒(méi)有考慮接收方接收到數(shù)據(jù)到發(fā)送確認(rèn)消息之間的延遲,也就是所謂的 ACK Delay。QUIC 充分考慮到 ACK Delay,這樣 RTT 的計(jì)算會(huì)更加準(zhǔn)確。

流量控制
TCP 通過(guò)滑動(dòng)窗口來(lái)控制流量,如果某一個(gè)包丟失了,滑動(dòng)窗口并不能跨過(guò)丟失的包繼續(xù)滑動(dòng),而是會(huì)卡在丟失的位置,等待數(shù)據(jù)重傳后,才能繼續(xù)滑動(dòng)。
QUIC 流量控制的核心是:不能建立太多的連接,以免響應(yīng)端處理不過(guò)來(lái);不能讓某一個(gè)連接占用大量的資源,讓其他連接沒(méi)有資源可用。為此 QUIC 流量控制分為 連接級(jí)別和 Stream 級(jí)別 :
- Stream 級(jí)別流量控制中,接收窗口 = 最大接收窗口 - 已接收數(shù)據(jù)
- 連接級(jí)別流量控制中,接收窗口 = Stream1 接收窗口 + Stream2 接收窗口 + ... + StreamN 接收窗口
連接遷移
TCP 連接是由(源 IP,源端口,目的 IP,目的端口)組成,這個(gè)四元組中一旦有一項(xiàng)值發(fā)生改變,這個(gè)連接也就不能用了。如果我們從 wifi 網(wǎng)絡(luò)切換到 4G 網(wǎng)絡(luò),IP 地址就會(huì)改變,這個(gè)時(shí)候 TCP 連接也自然斷掉了。
QUIC 使用客戶端生成的 64 位 ID 來(lái)表示一條連接,只要 ID 不變,這條連接也就一直維持著,不會(huì)中斷。
前向糾錯(cuò)機(jī)制
QUIC 使用前向糾錯(cuò)(FEC,F(xiàn)orward Error Correction)技術(shù)增加協(xié)議的容錯(cuò)性。

如圖所示,一段數(shù)據(jù)被切分為 10 個(gè)包后,依次對(duì)每個(gè)包進(jìn)行異或運(yùn)算,運(yùn)算結(jié)果會(huì)作為 FEC 包與數(shù)據(jù)包一起被傳輸,如果不幸在傳輸過(guò)程中有一個(gè)數(shù)據(jù)包丟失, 那么就可以根據(jù)剩余 9 個(gè)包以及 FEC 包推算出丟失的那個(gè)包的數(shù)據(jù),這樣就大大增加了協(xié)議的容錯(cuò)性。
這是符合現(xiàn)階段網(wǎng)絡(luò)技術(shù)的一種方案,現(xiàn)階段帶寬已經(jīng)不是網(wǎng)絡(luò)傳輸?shù)钠款i,往返時(shí)間才是,所以新的網(wǎng)絡(luò)傳輸協(xié)議可以適當(dāng)增加數(shù)據(jù)冗余,減少重傳操作。當(dāng)然這種情況只適用于丟失一個(gè)包的情況下,如果丟失了多個(gè)包,就只能進(jìn)行重傳了。
QPACK 壓縮
QPACK 壓縮是 HTTP/3 中使用的字段壓縮格式,可以使 HTTP/2 中使用的 HPACK 壓縮格式與 QUIC 協(xié)議兼容,兩種壓縮格式通過(guò)使用不同的機(jī)制來(lái)滿足傳輸層協(xié)議的要求。
那么 HTTP/3 中為什么不能繼續(xù)使用 HPACK 壓縮格式呢?
因?yàn)?HPACK 格式是為 TCP 協(xié)議創(chuàng)建的格式,這種格式工作的前提就是默認(rèn)數(shù)據(jù)字節(jié)流按照順序達(dá)到,如果 HTTP/3 中繼續(xù)使用 HPACK 壓縮格式, 就會(huì)導(dǎo)致額外的 隊(duì)列頭部請(qǐng)求阻塞 (head of line blocking) 問(wèn)題,因?yàn)?HPACK 依賴于對(duì)已經(jīng)到達(dá)字段的引用。但是,HTTP/3 中的 QUIC 的傳輸層協(xié)議為 UDP, 數(shù)據(jù)字節(jié)不會(huì)按照順序到達(dá),因此 HPACK 格式中的動(dòng)態(tài)表中可能會(huì)包含還未達(dá)到的數(shù)據(jù)的引用,于是就會(huì)阻塞直到被引用的數(shù)據(jù)到達(dá)。
為了解決這個(gè)問(wèn)題,QPACK 引入了兩種單向流類型: 編碼器流和解碼器流,除了傳遞 HTTP/3 消息的雙向字節(jié)流之外,客戶端和服務(wù)端可以選擇性地打開(kāi)這兩個(gè)單向編碼流, 將具體的指令傳輸給對(duì)方。因?yàn)榱魇菃蜗虻?,所以發(fā)送方只需要發(fā)送數(shù)據(jù)即可,無(wú)需等到接收方的響應(yīng)。
最終,雖然增加額外的單向字節(jié)流會(huì)帶來(lái)一定的性能開(kāi)銷,但是卻可以徹底解決 隊(duì)列頭部請(qǐng)求阻塞 (head of line blocking) 問(wèn)題,而且 QPACK 格式規(guī)范為客戶端和服務(wù)器實(shí)現(xiàn)提供了很高的自由度, 可以由實(shí)現(xiàn)方來(lái)決定具體的問(wèn)題重要程度和方案傾向性: 緩解并解決隊(duì)列頭部請(qǐng)求阻塞還是實(shí)現(xiàn)更高級(jí)別的壓縮效果。
QUIC 相比 TCP 的優(yōu)勢(shì)
- QUIC 在用戶空間實(shí)現(xiàn)而非內(nèi)核,這樣可以快速部署最新版本或自定義版本,TCP 在內(nèi)核中實(shí)現(xiàn),基本不太可能隨時(shí)部署新版本
- QUIC 具有更高級(jí)別的加密 (例如大部分 QUIC Header 都是加密的),TCP 本身不做數(shù)據(jù)加密,需要依賴于 TLS
- QUIC 建立在傳輸層協(xié)議棧上,意味著可以隨時(shí)切換傳輸協(xié)議,有著非常高的靈活性 (例如可以將底層的 UDP 切換到 TCP)
QUIC 存在的限制
- 性能提升很大程度上取決于 QUIC 方案的實(shí)施,這包括操作系統(tǒng)發(fā)行版、協(xié)議棧實(shí)現(xiàn)版本、QUIC 的實(shí)現(xiàn)版本等因素
- 使用 HTTP/3 之前需要進(jìn)行 HTTP 版本協(xié)商,瀏覽器默認(rèn)不支持 HTTP/3, 通常需要基于建立在 TCP 上的 HTTP/1 或 HTTP/2 來(lái)發(fā)送協(xié)商請(qǐng)求
- 增加網(wǎng)絡(luò)管理復(fù)雜度,由于 QUIC 會(huì)加密大部分?jǐn)?shù)據(jù),因此排查網(wǎng)絡(luò)錯(cuò)誤、優(yōu)化網(wǎng)絡(luò)性能和安全性、設(shè)置報(bào)警規(guī)則等工作都變得更加困難,由于這些原因,很多防火墻還未支持 QUIC
- 許多網(wǎng)絡(luò)攻擊都是利用 UDP 發(fā)起的,據(jù)統(tǒng)計(jì)大概有 3% - 5% 的網(wǎng)絡(luò)會(huì)直接過(guò)濾 UDP 請(qǐng)求 (DNS 等網(wǎng)絡(luò)基礎(chǔ)協(xié)議除外)
是否支持 HTTP/3
網(wǎng)站檢測(cè)
可以通過(guò) 在線工具[2] 進(jìn)行檢測(cè)。

瀏覽器支持情況
可以通過(guò) 瀏覽器在線工具[3] 進(jìn)行檢測(cè),下面是筆者的 Chrome 支持情況。

小結(jié)
本文介紹了從 HTTP/1 到 HTTP/3 中間四次比較大的協(xié)議升級(jí)變更,著重分析了每次升級(jí)前后的性能差異及相關(guān)特性變更 (安全方面的變更未分析,感興趣的讀者可以自行閱讀相關(guān) RFC)。作為開(kāi)發(fā)者,平時(shí)可能不會(huì)去關(guān)注這些網(wǎng)絡(luò)底層細(xì)節(jié),但是深入理解整個(gè) HTTP 協(xié)議的升級(jí)變遷過(guò)程,可以幫助我們理解這背后的工程挑戰(zhàn)以及解決思路,這才是最有價(jià)值的部分。
終極用戶體驗(yàn)?zāi)J?/h3>
瀏覽器作為操作系統(tǒng),站點(diǎn)作為應(yīng)用軟件,這算不算是從 B/S 架構(gòu)又回到了 C/S 架構(gòu)?
Reference
- Hypertext Transfer Protocol Version 2 (HTTP/2) [4]
- QUIC: A UDP-Based Multiplexed and Secure Transport [5]
- HTTP/3 [6]
- HTTP/2 explained [7]
- HTTP/2 [8]
- Introduction to HTTP/2 [9]
- what-is-http3 [10]
- HTTP/2 is here, let’s optimize! [11]
擴(kuò)展閱讀
- HTTP Documentation Core Specifications [12]
- HTTP/2 服務(wù)器推送(Server Push)教程 [13]
- HTTP/2 For Web Developers [14]
- Web Almanac [15]
- HTTP/2 HPACK 實(shí)際應(yīng)用舉例 [16]
- 詳解 HTTP/2 頭壓縮算法 —— HPACK [17]
- QUIC 協(xié)議原理分析 [18]
- HTTP/3 From A To Z: Core Concepts [19]
- A Comprehensive Guide To HTTP/3 And QUIC [20]
- A Comparison between SCTP and QUIC [21]
- HTTP RFCs have evolved: A Cloudflare view of HTTP usage trends [22]
- Introducing HTTP/3 Prioritization [23]
- Caddy [24]
- Report: State of the Web [25]
鏈接
[1]
瀏覽器作為操作系統(tǒng),站點(diǎn)作為應(yīng)用軟件,這算不算是從 B/S 架構(gòu)又回到了 C/S 架構(gòu)?
在線工具: 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 服務(wù)器推送(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 實(shí)際應(yīng)用舉例: https://halfrost.com/http2-hpack-example/
[17]詳解 HTTP/2 頭壓縮算法 —— HPACK: https://halfrost.com/http2-header-compression/
[18]QUIC協(xié)議原理分析: 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
