QUIC/HTTP3 協議簡析
從 HTTP 的進化歷史講起,細說使用協議的變遷,瞭解原因發現問題,解碼 QUIC 在 HTTP3 中的支撐作用,共同探討 HTTP3 的未來。
HTTP、HTTP2 和 HTTP3
先和大家來回顧一下 HTTP 的歷史,看看 HTTP3 相比 HTTP、HTTP2 都有哪些改進和升級的地方。
HTTP VS HTTP2
多路複用:多路複用時,多檔案傳輸有時只需維護一個 TCP 連線。如果是 HTTP1 協議下,每份資源的傳輸對應一個 TCP 連線,一般最多隻能開啟 6 個 TCP 連線來傳輸多路資料,後續每增加一個新連結就會因為擁堵問題卡死,進而導致整個程式無法執行。因此HTTP/2解決了 HTTP 的隊頭阻塞問題。
頭部壓縮和 Server Push:HTTP2 會通過 HPACK 做頭部壓縮。同時 HTTP2 是二進位制協議,在解析上相比基於文字的 HTTP解析效率上有所提升,並且 HTTP2 還增加了 Server Push。
在 TCP 下,依然無法解決延遲問題,比如為防止初始阻塞而引入的慢啟動;TCP 隊頭阻塞,比如由於發生丟包,整個連線涉及的傳輸資料都需要重傳而引起的阻塞。
儘管 HTTP2 相比 HTTP 已經有了改進的地方,但是如果你有 2% 的丟包率,那 HTTP2 在效能上就沒有優勢了。
上圖是造成 TCP 隊頭擁塞(Head of line blocking)的原因。HTTP2 協議是基於 TCP 的,但是 TCP 本身是無法解決隊頭擁塞,為什麼呢?因為 HTTP2 會把一次傳輸所有的檔案都放在一個 TCP 連線中,只要這個 TCP 中發生一個丟包,連線就必須重新建立,之前所有傳輸內容進行必須重傳,從而造成擁塞。
HTTP3 VS HTTP2
HTTP3 本質不是對 HTTP 協議本身的改進,它主要是集中在如何提高傳輸效率。上圖是相比 HTTP2 而言 HTTP3 提升的點:
HTTP3 使用 stream 進一步擴充套件了 HTTP2 的多路複用。在 HTTP3 模式下,一般傳輸多少個檔案就會產生對應數量的 stream。當這些檔案中的其中一個發生丟包時,你只需要重傳丟包檔案的對應 stream 即可。
HTTP3 不再是基於 TCP 建立的,而是通過 UDP 建立,在使用者空間保證傳輸的可靠性,相比 TCP,UDP 之上的 QUIC 協議提高了連線建立的速度,降低了延遲。
通過引入 Connection ID,使得 HTTP3 支援連線遷移以及 NAT 的重繫結。
HTTP3 含有一個包括驗證、加密、資料及負載的 built-in 的TLS安全機制。
擁塞控制。TCP 是在核心區實現的,而 HTTP3 將擁塞控制移出了核心,通過使用者空間來實現。這樣做的好處就是不再需要等待核心更新可以實現很方便的進行快速迭代。
頭部壓縮。HTTP2 使用的 HPACK,HTTP3 更換成了相容 HPACK 的 QPACK 壓縮方案。QPACK 優化了對亂序傳送的支援,也優化了壓縮率。
為什麼選擇 QUIC
從圖上可以看到 QUIC 協議層就實現了可靠的資料傳輸,擁塞控制,加密,多路資料流。
至於 QUIC 為什麼使用了 UDP 的問題,在瞭解這個之前,我們需要先知道一個事情。頻繁的使用者態和核心態切換會效率問題。理論上說,將應用層的東西遷移到核心從而提升效率是可行的,但是這麼做會影響作業系統的穩定性。另一方面,我們可以選擇將這部分內容遷移到使用者空間。比如目前流行的 DPDK,當網路卡將資料包傳輸過來時,它是繞過核心在使用者空間進行控制和應用。目前又拍雲的 DNS 就進行了這樣的處理,讓又拍雲的整體效率提升了5-10 倍。
接下來我們來正式說一下 QUIC 為什麼使用了 UDP 的問題,是因為以下幾點:
避免 ossification(僵化):QUIC 協議加密負載,也是避免協議僵化一種方式,比如當中間層處理 UDP 資料時,只需要按照資料包的方式去處理即可,不需要去關注內部層的具體資訊。
放棄改進 TCP 本身
創新方向:QUIC 是由谷歌提出的,所以 UDP 是以瀏覽器為出發點,從協議、從瀏覽器方向來進行創新。
TLS 1.3 Vs TLS 1.2
TLS 1.3 跟 1.2 的一些提升主要有上圖幾點,大家可以大致看一下:
TLS 1.3 採用了新的加密套件
TLS 1.3 定義了一些新的證書型別以及祕鑰交換機制。在 TLS 1.3 你不再需要去特別指定,它可以根據祕鑰套件配件進行證書型別的自主推導。
QUIC 存在的問題
接下來說一下 QUIC 目前存在的問題。
首先是因為這些年效能的優化提升都針對 TCP ,使得 UDP 效能沒有任何改進。當然隨著 QUIC3 的釋出,相信後續應該會有相對的投入。
其次是安全問題,也就是反射攻擊,即偽造原地址。這個指傳送資料包時的原地址是偽造的,不是真正的地址,會引起放大攻擊。原因是 QUIC 握手過程是不對稱的,特別是第一次請求時,客戶端只需要傳送幾個位元組的資訊到伺服器,而伺服器則需要把證書等很多東西返還給客戶端,這個不對稱的機會造成了放大。草案 27 定義了兩個規則和機制來限制反射攻擊:客戶端傳送Initial包,即第一個資料包時,其長度必須在 1200 bytes以上,不足部分用 Padding 幀填充,同時,當服務端不確定客戶端可靠性時,可以傳送 Retry 包要求客戶端再次提供驗證資訊。
開源 QUIC 的實現
接下來我們簡單說一下目前開源的使用情況:
quiche:這個是用 Rust 做的庫,通過 Nginx 呼叫。google 自己的庫也叫 quiche,C++寫的。
ATS:Apache Traffic Server
golang:Caddy;
python+C,aioquic
微軟msquic
開源 QUIC 的實現有很多,上面只是其中的一部分,同時我選擇了quiche 和 aioquic 做了一些簡單測試。
上圖展示的是從 cloudflare 提供支援 HTTP3 的 curl ,可以看到這個返回的值就是 HTTP/3 200。其中 alternative service 段,指示為 h3—27,表示支援 http3 draft-27 的服務跑在 UDP 443 埠。這個 alt-svc 是 HTTP2 時代就存在,在 HTTP3 也持續使用,因為有些時候瀏覽器並不知道伺服器是否支援 QUIC,所以通過 TCP 發起請求,確定有 H3 支援後,再通過 UDP連線。
這個是 HTTP2 的,目前可能是因為本身庫的問題,使用 curl 打不開谷歌,但是從資訊上可以看到 27、25 這些都是支援的。
如何部署以及達成 HTTP3 的 QUIC 實現
目前主要有兩種方式來實現,一種是代理,第二種是通過 Nginx。
騰訊是通過整合到 Nginx 利用它來實現框架的。同時因為 QUIC 每一條請求的含有頭的資料都會經過加密,騰訊有一個單獨的硬體加密群,如果你使用騰訊,那麼你所有的加解密都會通過他們的硬體來加速。
從騰訊這個可以看出加解密部分有著很可觀的 CPU 佔用率,如果後續所有的請求都是通過 HTTP3 來進行的話,在提升這塊佔用率上需要未雨綢繆。當然就像前面提到的 DPDK,也就是把資料丟到 FPGA 內去加解密是一個可以考慮的解決方案。
通過代理來實現的這種方式,目前官方暫時還沒有訊息。我可以像 cloudflare 那樣,先在外面進行整合,然後再講整合連結轉到 Nginx 內。
又拍雲目前有 LBS 和 Marco ,這個是因為 LBS 只認 TCP/UDP,它看不到 HTTP。也就是我們做了一個負載均衡的叢集,通過這個叢集在轉到 Nginx 上。而使用 UDP 則相當於已經走過了一個四層的負載均衡,那麼後續可以嘗試將 QUIC 的基線提取出來使用,在代理上做加解密,從而提高效率。