QUIC 協議原理淺解

導語 | QUIC,HTTP3 的傳輸層實現,是近年來誕生的非常強悍的傳輸協議,它利用 UDP 解決了當前基於 TCP 協議的 HTTP 的許多問題,提升了在弱網環境下的網絡通信體驗,下面讓我們來一探究竟。文章作者:江煒隆,騰訊雲 CDN 產品研發工程師。

一 、QUIC 究竟是什麼

1. 什麼是 QUIC?

QUIC(Quick UDP Internet Connection) 是谷歌推出的一套基於 UDP 的傳輸協議,它實現了 TCP + HTTPS + HTTP/2 的功能,目的是保證可靠性的同時降低網絡延遲。因爲 UDP 是一個簡單傳輸協議,基於 UDP 可以擺脫 TCP 傳輸確認、重傳慢啓動等因素,建立安全連接只需要一的個往返時間,它還實現了 HTTP/2 多路複用、頭部壓縮等功能。

衆所周知 UDP 比 TCP 傳輸速度快,TCP 是可靠協議,但是代價是雙方確認數據而衍生的一系列消耗。其次 TCP 是系統內核實現的,如果升級 TCP 協議,就得讓用戶升級系統,這個的門檻比較高,而 QUIC 在 UDP 基礎上由客戶端自由發揮,只要有服務器能對接就可以。

圖 1 HTTP 與 QUIC

(圖引自《淺談 QUIC 協議原理與性能分析及部署方案》-by 周陸軍)

2. HTTP 協議發展

(1)HTTP 歷史進程

(2)HTTP1.0 和 HTTP1.1

(3)HTTP2

解決 HTTP1 的一些問題,但是解決不了底層  TCP 協議層面上的隊頭阻塞問題。

缺點:HTTP 2 中,多個請求在一個 TCP 管道中的,出現了丟包時,HTTP 2 的表現反倒不如 HTTP 1.1 了。因爲 TCP 爲了保證可靠傳輸,有個特別的 “丟包重傳” 機制,丟失的包必須要等待重新傳輸確認,HTTP 2 出現丟包時,整個 TCP 都要開始等待重傳,那麼就會阻塞該 TCP 連接中的所有請求。而對於 HTTP 1.1 來說,可以開啓多個  TCP 連接,出現這種情況反倒只會影響其中一個連接,剩餘的 TCP 連接還可以正常傳輸數據。

(4)HTTP3 —— HTTP Over QUIC

HTTP 是建立在 TCP 協議之上,所有 HTTP 協議的瓶頸及其優化技巧都是基於 TCP 協議本身的特性,HTTP2 雖然實現了多路複用,底層 TCP 協議層面上的問題並沒有解決(HTTP 2.0 同一域名下只需要使用一個 TCP 連接。但是如果這個連接出現了丟包,會導致整個 TCP 都要開始等待重傳,後面的所有數據都被阻塞了),而 HTTP3 的 QUIC 就是爲解決 HTTP2 的 TCP 問題而生。

二、QUIC 的關鍵特性

關於 QUIC 的原理,相關介紹的文章很多,這裏再列舉一下 QUIC 的重要特性。這些特性是 QUIC 得以被廣泛應用的關鍵。不同業務也可以根據業務特點利用 QUIC 的特性去做一些優化。同時,這些特性也是我們去提供 QUIC 服務的切入點。

1. 連接遷移

(1)TCP 的連接重連之痛

一條 TCP 連接是由四元組標識的(源 IP,源端口,目的 IP,目的端口)。什麼叫連接遷移呢?就是當其中任何一個元素髮生變化時,這條連接依然維持着,能夠保持業務邏輯不中斷。當然這裏面主要關注的是客戶端的變化,因爲客戶端不可控並且網絡環境經常發生變化,而服務端的 IP 和端口一般都是固定的。

比如大家使用手機在 WIFI 和 4G 移動網絡切換時,客戶端的 IP 肯定會發生變化,需要重新建立和服務端的 TCP 連接。

又比如大家使用公共 NAT 出口時,有些連接競爭時需要重新綁定端口,導致客戶端的端口發生變化,同樣需要重新建立 TCP 連接。

所以從 TCP 連接的角度來講,這個問題是無解的。

(2)基於 UDP 的 QUIC 的連接遷移實現

當用戶的地址發生變化時,如 WIFI 切換到 4G 場景,基於 TCP 的 HTTP 協議無法保持連接的存活。QUIC 基於連接 ID 唯一識別連接。當源地址發生改變時,QUIC 仍然可以保證連接存活和數據正常收發。

那 QUIC 是如何做到連接遷移呢?很簡單,QUIC 是基於 UDP 協議的,任何一條 QUIC 連接不再以 IP 及端口四元組標識,而是以一個 64 位的隨機數作爲 ID 來標識,這樣就算 IP 或者端口發生變化時,只要 ID 不變,這條連接依然維持着,上層業務邏輯感知不到變化,不會中斷,也就不需要重連。

由於這個 ID 是客戶端隨機產生的,並且長度有 64 位,所以衝突概率非常低。

圖 2 TCP 和 QUIC 在 Wi-Fi 和 cellular 切換時,唯一標識的不同情況

(圖引自《跟堅哥學 QUIC 系列:連接遷移(Connection Migration)》- by  Xiaojian Hong)

2. 低連接延時

(1)TLS 的連接時延問題

以一次簡單的瀏覽器訪問爲例,在地址欄中輸入:https://www.abc.com,實際會產生以下動作:

所以,對於數據量小的請求而言,單一次的請求握手就佔用了大量的時間,對於用戶體驗的影響非常大。同時,在用戶網絡不佳的情況下,RTT 延時會變得較高,極其影響用戶體驗。

下圖對比了 TLS 各版本與場景下的延時對比:

圖 2-2 tls 各個版本握手時延

(圖引自《QUIC 0-RTT 實現簡析及一種分佈式的 0-RTT 實現方案》)

從對比我們可以看到,即使用上了 TLS 1.3,精簡了握手過程,最快能做到 0-RTT 握手 (首次是 1-RTT);但是對用戶感知而言, 還要加上 1 RTT 的 TCP 握手開銷。Google 有提出 Fastopen 的方案來使得 TCP 非首次握手就能附帶用戶數據,但是由於 TCP 實現僵化,無法升級應用,相關 RFC 到現今都是 experimental 狀態。

這種分層設計帶來的延時, 有沒有辦法進一步降低呢? QUIC 通過合併加密與連接管理解決了這個問題,我們來看看其是如何實現真正意義上的 0-RTT 的握手, 讓與 server 進行第一個數據包的交互就能帶上用戶數據。

(2)真 ·0-RTT 的 QUIC 握手

QUIC 由於基於 UDP,無需 TCP 連接,在最好情況下,短連接下 QUIC 可以做到 0RTT 開啓數據傳輸。而基於 TCP 的 HTTPS,即使在最好的 TLS1.3 的 early data 下仍然需要 1RTT 開啓數據傳輸。而對於目前線上常見的 TLS1.2 完全握手的情況,則需要 3RTT 開啓數據傳輸。對於 RTT 敏感的業務,QUIC 可以有效的降低連接建立延遲。

究其原因一方面是 TCP 和 TLS 分層設計導致的:分層的設計需要每個邏輯層次分別建立自己的連接狀態。另一方面是 TLS 的握手階段複雜的密鑰協商機制導致的。要降低建連耗時,需要從這兩方面着手。

QUIC 具體握手過程如下:

  1. 客戶端判斷本地是否已有服務器的全部配置參數(證書配置信息),如果有則直接跳轉到 (5),否則繼續 。

  2. 客戶端向服務器發送 inchoate client hello(CHLO) 消息,請求服務器傳輸配置參數。

  3. 服務器收到 CHLO,回覆 rejection(REJ) 消息,其中包含服務器的部分配置參數

  4. 客戶端收到 REJ,提取並存儲服務器配置參數,跳回到 (1) 。

  5. 客戶端向服務器發送 full client hello 消息,開始正式握手,消息中包括客戶端選擇的公開數。此時客戶端根據獲取的服務器配置參數和自己選擇的公開數,可以計算出初始密鑰 K1。

  6. 服務器收到 full client hello,如果不同意連接就回復 REJ,同 (3);如果同意連接,根據客戶端的公開數計算出初始密鑰 K1,回覆 server hello(SHLO) 消息, SHLO 用初始密鑰 K1 加密,並且其中包含服務器選擇的一個臨時公開數。

  7. 客戶端收到服務器的回覆,如果是 REJ 則情況同 (4);如果是 SHLO,則嘗試用初始密鑰 K1 解密,提取出臨時公開數。

  8. 客戶端和服務器根據臨時公開數和初始密鑰 K1,各自基於 SHA-256 算法推導出會話密鑰 K2。

  9. 雙方更換爲使用會話密鑰 K2 通信,初始密鑰 K1 此時已無用,QUIC 握手過程完畢。之後會話密鑰 K2 更新的流程與以上過程類似,只是數據包中的某些字段略有不同。

圖 2-3 quic 0-rtt 握手

(圖引自《QUIC 0-RTT 實現簡析及一種分佈式的 0-RTT 實現方案》)

3. 可自定義的擁塞控制

QUIC 使用可插拔的擁塞控制,相較於 TCP,它能提供更豐富的擁塞控制信息。比如對於每一個包,不管是原始包還是重傳包,都帶有一個新的序列號 (seq),這使得 QUIC 能夠區分 ACK 是重傳包還是原始包,從而避免了 TCP 重傳模糊的問題。QUIC 同時還帶有收到數據包與發出 ACK 之間的時延信息。這些信息能夠幫助更精確的計算 RTT。

此外,QUIC 的 ACK Frame 支持 256 個 NACK 區間,相比於 TCP 的 SACK(Selective Acknowledgment) 更彈性化,更豐富的信息會讓 client 和 server  知曉哪些包已經被對方收到。

QUIC 的傳輸控制不再依賴內核的擁塞控制算法,而是實現在應用層上,這意味着我們根據不同的業務場景,實現和配置不同的擁塞控制算法以及參數。Google 提出的 BBR  擁塞控制算法與 CUBIC 是思路完全不一樣的算法,在弱網和一定丟包場景,BBR 比 CUBIC 更不敏感,性能也更好。在 QUIC 下我們可以根據業務隨意指定擁塞控制算法和參數,甚至同一個業務的不同連接也可以使用不同的擁塞控制算法。

圖 2-4 BBR 擁塞弱網下算法效果對比

(圖引自《TCP BBR - Exploring TCP congestion control》-by Andree Toonk)

4. 無隊頭阻塞

(1)TCP 的隊頭阻塞問題

雖然 HTTP2 實現了多路複用,但是因爲其基於面向字節流的 TCP,因此一旦丟包,將會影響多路複用下的所有請求流。QUIC 基於 UDP,在設計上就解決了隊頭阻塞問題。

TCP 隊頭阻塞的主要原因是數據包超時確認或丟失阻塞了當前窗口向右滑動,我們最容易想到的解決隊頭阻塞的方案是不讓超時確認或丟失的數據包將當前窗口阻塞在原地。QUIC 也正是採用上述方案來解決 TCP 隊頭阻塞問題的。

TCP 爲了保證可靠性,使用了基於字節序號的 Sequence Number 及 Ack 來確認消息的有序到達。

圖 2-5 HTTP2 隊頭阻塞

(圖引自《科普:QUIC 協議原理分析》)

如上圖,應用層可以順利讀取 stream1 中的內容,但由於 stream2 中的第三個 segment 發生了丟包,TCP 爲了保證數據的可靠性,需要發送端重傳第 3 個  segment 才能通知應用層讀取接下去的數據。所以即使 stream3、stream4 的內容已順利抵達,應用層仍然無法讀取,只能等待 stream2 中丟失的包進行重傳。

在弱網環境下,HTTP2 的隊頭阻塞問題在用戶體驗上極爲糟糕。

(2)QUIC 的無隊頭阻塞解決方案

QUIC 同樣是一個可靠的協議,它使用 Packet Number 代替了 TCP 的 Sequence Number,並且每個 Packet Number 都嚴格遞增,也就是說就算 Packet N 丟失了,重傳的 Packet N 的 Packet Number 已經不是 N,而是一個比 N 大的值,比如 Packet N+M。

QUIC 使用的 Packet Number 單調遞增的設計,可以讓數據包不再像 TCP 那樣必須有序確認,QUIC 支持亂序確認,當數據包 Packet N 丟失後,只要有新的已接收數據包確認,當前窗口就會繼續向右滑動。待發送端獲知數據包 Packet N 丟失後,會將需要重傳的數據包放到待發送隊列,重新編號比如數據包 Packet N+M 後重新發送給接收端,對重傳數據包的處理跟發送新的數據包類似,這樣就不會因爲丟包重傳將當前窗口阻塞在原地,從而解決了隊頭阻塞問題。

那麼,既然重傳數據包的 Packet N+M 與丟失數據包的 Packet N 編號並不一致,我們怎麼確定這兩個數據包的內容一樣呢?

QUIC 使用 Stream ID 來標識當前數據流屬於哪個資源請求,這同時也是數據包多路複用傳輸到接收端後能正常組裝的依據。重傳的數據包 Packet N+M 和丟失的數據包 Packet N 單靠 Stream ID 的比對一致仍然不能判斷兩個數據包內容一致,還需要再新增一個字段 Stream Offset,標識當前數據包在當前 Stream ID 中的字節偏移量。

有了 Stream Offset 字段信息,屬於同一個 Stream ID 的數據包也可以亂序傳輸了(HTTP/2 中僅靠 Stream ID 標識,要求同屬於一個 Stream ID 的數據幀必須有序傳輸),通過兩個數據包的 Stream ID 與 Stream Offset 都一致,就說明這兩個數據包的內容一致。

圖 2-6 QUIC 無隊頭阻塞

(圖引自《科普:QUIC 協議原理分析》)

三、QUIC 協議組成

QUIC 的 Packet 除了個別報文比如 PUBLIC_RESET 和 CHLO,所有報文頭部都是經過認證的,報文 Body 都是經過加密的。這樣只要對 QUIC 報文任何修改,接收端都能夠及時發現,有效地降低了安全風險。

如下圖所示,紅色部分是 Stream Frame 的報文頭部,有認證。綠色部分是報文內容,全部經過加密。

圖 3-1 QUIC 的協議組成

(圖引自《科普:QUIC 協議原理分析》)

QUIC 報文的大小需要滿足路徑 MTU 的大小以避免被分片。當前 QUIC 在 IPV6 下的最大報文長度爲 1350,IPV4 下的最大報文長度爲 1370。

四、結語

QUIC 具有衆多優點,它融合了 UDP 協議的速度、性能與 TCP 的安全與可靠,同時也解決了 HTTP1、HTTP1.1、HTTP2 中引入的一些缺點,大大優化了互聯網傳輸體驗。

騰訊雲 CDN 也緊跟技術浪潮,於今年年初迭代中加入了 QUIC 的功能支持,目前正在內測當中。相關介紹以及內測申請可以戳如下這個鏈接:

https://cloud.tencent.com/document/product/228/51800

參考資料:

[1] QUIC 在 Facebook 是如何部署的?

https://mp.weixin.qq.com/s/Zjzp_rYj_XSnFZnoNf02Iw

_[_2] STGW 下一代互聯網標準傳輸協議 QUIC 大規模運營之路:

https://mp.weixin.qq.com/s/ciR-1N4z0zvGOJSoyrvMUA

_[_3] QUIC 0-RTT 實現簡析及一種分佈式的 0-RTT 實現方案:

https://cloud.tencent.com/developer/article/1594468

[4_] 科普:QUIC 協議原理分析:_

https://zhuanlan.zhihu.com/p/32553477

[5] TCP BBR - Exploring TCP congestion control:

https://atoonk.medium.com/tcp-bbr-exploring-tcp-congestion-control-84c9c11dc3a9

[6] 淺談 QUIC 協議原理與性能分析及部署方案:

https://zhuanlan.zhihu.com/p/146473513

[7] QUIC 是如何解決 TCP 性能瓶頸的?

https://blog.csdn.net/m0_37621078/article/details/106506532

[8] QUIC 協議 和 TCP/UDP 協議:

https://nolaaaaa.github.io/2019/04/11/QUIC%E5%8D%8F%E8%AE%AE-%E5%92%8C-TCP-UDP-%E5%8D%8F%E8%AE%AE/

[9] QUIC 的那些事 | 包類型及格式:

https://blog.csdn.net/u014023993/article/details/86432341

[10] 跟堅哥學 QUIC 系列:連接遷移(Connection Migration)

https://zhuanlan.zhihu.com/p/311221111

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