積跬步至千里:QUIC 協議在螞蟻集團落地之綜述

阿里巴巴移動技術發表於2021-11-10

作者:孔令濤

自 2015 年以來,QUIC 協議開始在 IETF 進行標準化並被國內外各大廠商相繼落地。鑑於 QUIC 具備“0RTT 建聯”、“支援連線遷移”等諸多優勢,並將成為下一代網際網路協議:HTTP3.0 的底層傳輸協議,螞蟻集團支付寶客戶端團隊與接入閘道器團隊於 2018 年下半年開始在移動支付、海外加速等場景落地 QUIC。

本文是綜述篇,介紹 QUIC 在螞蟻的整體落地情況。之所以是綜述,是因為 QUIC 協議過於複雜,如果對標已有的協議,QUIC 近似等於 HTTP + TLS +TCP,無法詳細的畢其功於一役,因此我們通過綜述的方式將落地的重點呈現給讀者,主要介紹如下幾個部分:

  • QUIC背景:簡單全面的介紹下 QUIC 相關的背景知識;
  • 方案選型設計:詳細介紹螞蟻的落地方案如何另闢蹊徑、優雅的支撐 QUIC 的諸多特性,包括連線遷移等;
  • 落地場景:介紹 QUIC 在螞蟻的兩個落地場景,包括:支付寶客戶端鏈路以及海外加速鏈路;
  • 幾項關鍵技術:介紹落地 QUIC 過程中核心需要解決的問題,以及我們使用的方案,包括:“支援連線遷移”、“提升 0RTT 比例", "支援 UDP 無損升級”以及“客戶端智慧選路” 等;
  • 幾項關鍵的技術專利。

QUIC 背景介紹

鑑於讀者的背景可能不同,在開始本文之前,我們先簡單介紹下 QUIC 相關的背景知識,如果您對這個協議的更多設計細節感興趣,可以參見相關 Draft:https://datatracker.ietf.org/...

一、QUIC 是什麼?

簡單來說,QUIC (Quick UDP Internet Connections) 是一種基於 UDP 封裝的安全 可靠傳輸協議,他的目標是取代 TCP 並自包含 TLS 成為標準的安全傳輸協議。下圖是 QUIC 在協議棧中的位置,基於 QUIC 承載的 HTTP 協議進一步被標準化為 HTTP3.0。

二、為什麼是 QUIC ?

在 QUIC 出現之前,TCP 承載了 90% 多的網際網路流量,似乎也沒什麼問題,那又為何會出現革命者 QUIC 呢?這主要是因為發展了幾十年的 TCP 面臨 “協議僵化問題”,表現在幾方面:

  1. 網路裝置支援 TCP 時的僵化,表現在:對於一些防火牆或者 NAT 等裝置,如果 TCP 引入了新的特性,比如增加了某些 TCP OPTION 等,可能會被認為是攻擊而丟包,導致新特性在老的網路裝置上無法工作。
  2. 網路作業系統升級困難導致的 TCP 僵化,一些 TCP 的特性無法快速的被演進
  3. 除此之外,當應用層協議優化到 TLS1.3、 HTTP2.0 後, 傳輸層的優化也提上了議程,QUIC 在 TCP 基礎上,取其精華去其糟粕具有如下的硬核優勢:

三、QUIC 生態圈發展簡史

下圖是 QUIC 從建立到現在為止的一些比較重要的時間節點,2021 年,QUIC V1 成為 RFC,結束百花齊放的態勢。

介紹完 QUIC 相關背景,之後我們來介紹螞蟻的整個落地的內容,這裡為了便於闡述,我們用螞蟻 QUIC 的 一、二、三、四 來進行概括總結,即 “一套落地框架”、“兩個落地場景”、“三篇創新專利保護”、“四項關鍵技術”。

一套落地框架

螞蟻的接入閘道器是基於多程式的 NGINX 開發的 (內部稱為 Spanner,協議解除安裝的扳手),而 UDP 在多程式程式設計模型上存在諸多挑戰,典型的像無損升級等。為了設計一套完備的框架,我們在落地前充分考慮了服務端在雲上部署上的方便性、擴充套件性、以及效能問題,設計瞭如下的落地框架以支撐不同的落地場景:

在這套框架中,包括如下兩個元件:

  1. QUIC LB 元件:基於 NGINX 4層 UDP Stream 模組開發,用來基於 QUIC DCID 中攜帶的服務端資訊進行路由,以支援連線遷移;
  2. NGINX QUIC 伺服器:開發了 NGINX_QUIC_MODULE,每個 Worker 監聽兩種型別的埠:
  3. BASE PORT ,每個 Worker 使用的相同的埠號,以 Reuseport 的形式監聽,並暴露給 QUIC LB,用以接收客戶端過來的第一個 RTT 中的資料包,這類包的特點是 DCID 由客戶端生成,沒有路由資訊;
  4. Working PORT,每個 Worker 使用的不同的埠號,是真正的工作埠,用以接收第一個 RTT 之後的 QUIC 包,這類包的特定是 DCID 由服務端的程式生成攜帶有服務端的資訊。

當前框架支援的能力包括如下:

  1. 在不用修改核心的情況下,完全在使用者態支援 QUIC 的連線遷移,以及連線遷移時 CID 的 Update;
  2. 在不用修改核心的情況下,完全在使用者態支援 QUIC 的無損升級以及其他運維問題;
  3. 支援真正意義上的 0RTT ,並可提升 0RTT 的比例。

為何能支援上述能力,我們後面會展開敘述。

兩個落地場景

我們由近及遠的兩個落地場景如下:

場景一、支付寶移動端落地

如下為我們落地架構的示意圖,支付寶手機客戶端通過 QUIC 攜帶 HTTP 請求,通過 QUIC LB 等四層閘道器將請求轉發到 Spanner (螞蟻內部基於 NGINX 開發的 7 層閘道器),在 Spanner 上我們將 QUIC 請求 Proxy 成 TCP 請求,傳送給業務閘道器(RS)。

具體的方案選型如下:

  • 支援的 QUIC 版本是 gQUIC Q46;
  • NGINX QUIC MODULE 支援 QUIC 的接入和 PROXY 成 TCP 的能力;
  • 支援包括移動支付、基金、螞蟻森林在內的所有的 RPC 請求;
  • 當前選擇 QUIC 鏈路的方式有兩種 :

    • Backup 模式,即在 TCP 鏈路無法使用的情況下,降級到 QUIC 鏈路;
    • Smart 模式,即 TCP和 QUIC 競速,在 TCP 表現力弱於 QUIC 的情況下,下次請求主動使用 QUIC 鏈路。

在此場景下,通過使用 QUIC 可以獲得的紅利包括:

  1. 在客戶端連線發生遷移的時候,可以不斷鏈繼續服務;
  2. 客戶端在首次發起連線時,可以節省 TCP 三次握手的時間;
  3. 對於弱網情況,QUIC 的傳輸控制可以帶來傳輸效能提升。

場景二、海外加速落地

螞蟻集團從 2018 年開始自研了海外的動態加速平臺 AGNA(Ant Global Network Accelerator)以替換第三方廠商的加速服務。AGNA 通過在海外部署接入點:Local Proxy(LP) 以及在國內部署接出點:Remote Proxy (RP)的方式,將使用者的海外請求通過 LP 和 RP 的加速鏈路回源國內。如下圖所示,我們將 QUIC 部署在 LP 和 RP 之間的鏈路。

在海外接入點上(LP),每一個 TCP 連線都被 Proxy 成 QUIC 上的一個 Stream 進行承載,在國內接出點上(RP), 每一個 QUIC Stream 又被 Proxy 成一個 TCP 連線,LP 和 RP 之間使用 QUIC 長連線。

在此場景下,通過使用 QUIC 可以獲得的紅利包括:

  1. 通過 QUIC 長連線的上的 Stream 承載 TCP 請求,避免每次的跨海建聯;
  2. 對於跨海的網路,QUIC 的傳輸控制可以帶來傳輸效能提升。

三篇關鍵專利

到目前為止,我們把落地過程中一些創新的技術點通過申請專利進行了保護,並積極在 IETF 進行標準化分享我們的研究成果,包括:

專利一

我們將落地場景 2 中,通過 QUIC Stream 進行四層代理的手段來進行海外回源的加速方法進行專利保護,提出:“一種基於 QUIC 協議代理的的鏈路加速方法”,目前此專利已經獲得美國專利授權,專利號:CN110213241A。

專利二

將我們落地框架中的 QUIC LB 元件作為專利進行保護,提出:“一種無狀態、一致性、分散式的 QUIC 負載均衡裝置”,目前此專利還在受理中。由於通過 QUIC LB 可以很好的支援 QUIC 協議的連線遷移問題,所以目前 IETF QUIC WG 上有關於 QUIC LB 相關的草案,我們目前已經參與到 Draft 的討論和制定中,後序相關的方案也會繼續推廣到雲上產品。

專利三

將我們解決的 UDP 的無損升級方法進行專利保護,提出 “一種 QUIC 伺服器無損升級方案”,目前此專利還在受理中。由於 UDP 無損升級問題是一個業界難題,當前有些手段需要在使用者態進行跳轉,效能損失較大,我們的方案可以在我們的落地框架中解決當前問題,關於這個方案的細節我們再後面的關鍵技術中進行介紹。

四項關鍵技術

在整個的落地中,我們設計的方案圍繞解決幾個核心問題展開,形成了四項關鍵技術,分別如下:

技術點1:優雅的支援連線遷移能力

先說 連線遷移面臨的問題,上文有提到,QUIC 有一項比較重要的功能是支援連線遷移。這裡的連線遷移是指:如果客戶端在長連線保持的情況下切換網路,比如從 4G 切換到 Wifi , 或者因為 NAT Rebinding 導致五元組發生變化,QUIC 依然可以在新的五元組上繼續進行連線狀態。QUIC 之所以能支援連線遷移,一個原因是 QUIC 底層是基於無連線的 UDP,另一個重要原因是因為 QUIC 使用唯一的 CID 來標識一個連線,而不是五元組。

如下圖所示,是 QUIC 支援連線的一個示意圖,當客戶端出口地址從 A 切換成 B 的時候,因為 CID 保持不變,所以在 QUIC 伺服器上,依然可以查詢到對應的 Session 狀態。

然而,理論很豐滿,落地卻很艱難,在端到端的落地過程中,因為引入了負載均衡裝置,會導致在連線遷移時,所有依賴五元組 Hash 做轉發或者關聯 Session 的機制失效。以 LVS 為例,連線遷移後, LVS 依靠五元組定址會導致定址的伺服器存在不一致。即便 LVS 定址正確,當報文到達伺服器時,核心根據五元組關聯程式,依然會定址出錯。同時,IETF Draft 要求,連線遷移時 CID 需要更新掉,這就為僅依靠 CID 來轉發的計劃同樣變的不可行。

再說 我們的解決方法,為了解決此問題,我們設計了開篇介紹的落地框架,這裡我們將方案做一些簡化和抽象,整體思路如下圖所示:

1、在四層負載均衡上,我們設計了 QUIC LoadBalancer 的機制:

  • 我們在 QUIC 的 CID 中擴充套件了一些欄位(ServerInfo)用來關聯 QUIC Server 的 IP 和 Working Port 資訊;
  • 在發生連線遷移的時候,QUIC LoadBalancer 可以依賴 CID 中的 ServerInfo 進行路由,避免依賴五元組關聯 Session 導致的問題;
  • 在 CID 需要 Update 的時候,NewCID 中的 ServerInfo 保留不變,這樣就避免在 CID 發生 Update 時,僅依賴 CID Hash 挑選後端導致的定址不一致問題;

2、在 QUIC 伺服器多程式工作模式上,我們突破了 NGINX 固有的多 Worker 監聽在相同埠上的桎梏,設計了多埠監聽的機制,每個 Worker 在工作埠上進行隔離,並將埠的資訊攜帶在對 First Initial Packet 的回包的 CID 中,這樣代理的好處是:

  • 無論是否連線遷移,QUIC LB 都可以根據 ServerInfo,將報文轉發到正確的程式;
  • 而業界普遍的方案是修改核心,將 Reuse port 機制改為 Reuse CID 機制,即核心根據 CID 挑選程式。即便後面可以通過 ebpf 等手段支援,但我們認為這種修改核心的機制對底層過於依賴,不利於方案的大規模部署和運維,尤其在公有云上;
  • 使用獨立埠,也有利於多程式模式下,UDP 無損升級問題的解決,這個我們在技術點 3 中介紹。

技術點2:提升 0RTT 握手比例

這裡先 介紹 QUIC 0RTT 原理。前文我們介紹過, QUIC 支援傳輸層握手和安全加密層握手都在一個 0RTT 內完成。TLS1.3 本身就支援加密層握手的 0RTT,所以不足為奇。而 QUIC 如何實現傳輸層握手支援 0RTT 呢?我們先看下傳輸層握手的目的,即:服務端校驗客戶端是真正想握手的客戶端,地址不存在欺騙,從而避免偽造源地址攻擊。在 TCP 中,服務端依賴三次握手的最後一個 ACK 來校驗客戶端是真正的客戶端,即只有真正的客戶端才會收到 Sever 的 syn_ack 並回復。

QUIC 同樣需要對握手的源地址做校驗,否則便會存在 UDP 本身的 DDOS 問題,那 QUIC 是如何實現的?依賴 STK(Source Address Token) 機制。這裡我們先宣告下,跟 TLS 類似,QUIC 的 0RTT 握手,是建立在已經同一個伺服器建立過連線的基礎上,所以如果是純的第一次連線,仍然需要一個 RTT 來獲取這個 STK。如下圖所示,我們介紹下這個原理:

  1. 類似於 Session Ticket 原理,Server 會將客戶端的地址和當前的 Timestamp 通過自己的 KEY 加密生成 STK。
  2. Client 下次握手的時候,將 STK 攜帶過來,由於 STK 無法篡改,所以 Server 通過自己的 KEY 解密,如果解出來的地址和客戶端此次握手的地址一致,且時間在有效期內,則表示客戶端可信,便可以建立連線。
  3. 由於客戶端第一次握手的時候,沒有這個 STK,所以服務度會回覆 REJ 這次握手的資訊,並攜帶 STK。

理論上說,只要客戶端快取了這個 STK,下次握手的時候帶過來,服務端便可以直接校驗通過,即實現傳輸層的 0RTT。但是真實的場景卻存在如下兩個問題

  1. 因為 STK 是服務端加密的,所以如果下次這個客戶端路由到別的伺服器上了,則這個伺服器也需要可以識別出來;
  2. STK 中 encode 的是上一次客戶端的地址,如果下一次客戶端攜帶的地址發生了變化,則同樣會導致校驗失敗。此現象在移動端發生的概率非常大,尤其是 IPV6 場景下,客戶端的出口地址會經常發生變化。

再介紹下我們的解決方法。第一個問題比較好解,我們只要保證叢集內的機器生成 STK 的祕鑰一致即可。第二個問題,我們的解題思路是:

  1. 我們在 STK 中擴充套件了一個 Client ID, 這個 Clinet ID 是客戶端通過無線保鏢黑盒生成並全域性唯一不變的,類似於一個裝置的 SIMID,客戶端通過加密的 Trasnport Parameter 傳遞給服務端,服務端在 STK 中包含這個 ID;
  2. 如果因為 Client IP 發生變化導致校驗 STK 校驗失敗,便會去校驗 Client ID,因為 ID 對於一個 Client 是永遠不變的,所以可以校驗成功,當然前提是,這個客戶端是真實的。為了防止 Client ID 的洩露等,我們會選擇性對 Client ID 校驗能力做限流保護。

技術點3:支援 QUIC 無損升級

我們知道UDP 無損升級是業界難題。無損升級是指在 reload 或者更新二進位制時,老的程式可以處理完存量連線上的資料後優雅退出。以 NGINX 為例,這裡先介紹下 TCP 是如何處理無損升級的,主要是如下的兩個步驟:

  1. 老程式先關閉 listening socket,待存量連線請求都結束後,再關閉連線套接字;
  2. 新程式從老程式繼承 listening socket , 開始 accept 新的請求。

而 UDP 無法做到無損升級是因為 UDP 只有一個 listening socket 沒有類似 TCP 的連線套接字,所有的收發資料包都在這個 socket 上,導致下面的熱升級步驟會存在問題:

  1. 在熱升級的時候,old process fork 出 new process 後,new process 會繼承 listening socket 並開始 recv msg;
  2. 而 old process 此時如果關閉 listenging socket, 則在途的資料包便無法接收,達不到優雅退出的目的;
  3. 而如果繼續監聽,則新老程式都會同時收取新連線上的報文,導致老程式無法退出。

這裡介紹下相關的解決方法。針對此問題,業界有一些方法,比如:在資料包中攜帶程式號,當資料包收發錯亂後,在新老程式之間做一次轉發。考慮到接入層上的效能等原因,我們不希望資料再做一次跳轉。結合我們的落地架構,我們設計瞭如下的 基於多埠輪轉的無損升級方案,簡單來說,我們讓新老程式監聽在不同的埠組並攜帶在 CID 中,這樣 QUIC LB 就可以根據埠轉發到新老程式。為了便於運維,我們採用埠輪轉的方式,新老程式會在 reload N 次之後,重新開始之前選中的埠。如下圖所示:

  1. 無損升級期間,老程式的 Baseport 埠關閉,這樣不會再接受 first intial packet, 類似於關閉了 tcp 的 listening socket;
  2. 老程式的工作埠,繼續工作,用來接收當前程式上殘餘的流量;
  3. 新程式的 Baseport 開始工作,用來接收 first initial packet, 開啟新的連線,類似於開啟了 tcp 的 listening socket;
  4. 新程式的 working port = (I + 1) mod N, N 是指同時支援新老程式的狀態的次數,例如 N = 4, 表示可以同時 reload 四次,四種 Old, New1, New2, New3 四種狀態同時並存,I 是上一個程式工作的埠號,這裡 + 1 是因為只有一個 worker, 如果 worker 數有 M 個,則加 M;
  5. 建好的連線便被 Load Balancer 轉移到新程式的監聽埠的 Working Port 上。

技術點4:客戶端智慧選路

儘管落地 QUIC 的願望是好的,但是新事物的發展並不是一帆風順的。由於 QUIC 是基於 UDP 的,而 UDP 相比於 TCP 在運營商的支援上並非友好,表現在:

  1. 在頻寬緊張的時候,UDP 會經常被限流;
  2. 一些防火牆對於 UDP 包會直接 Drop;
  3. NAT 閘道器針對 UDP 的 Session 存活時間也較短。

同時,根據觀察發現,不同的手機廠商對於 UDP 的支援能力也不同,所以在落地過程中,如果盲目的將所有流量完全切為 QUIC 可能會導致一些難以預料的結果。為此,我們在客戶端上,設計了開篇介紹的 TCP 和 QUIC 相互 Backup 的鏈路,如下圖所示,我們實時探測 TCP 鏈路和 QUIC 鏈路的 RTT、丟包率、請求完成時間、錯誤率等指標情況,並根據一定的量化方法對兩種鏈路進行打分,根據評分高低,決定選擇走哪種鏈路,從而避免定址只走一條鏈路導致的問題。

做個總結

本文主要綜述性的介紹了 QUIC 在螞蟻的落地方案、場景以及一些關鍵技術。關鍵技術上,主要介紹了我們如何通過創造性的提出 QUIC LB 元件、以及多埠監聽的機制來優雅的支援 QUIC 的連線遷移機制、QUIC 服務端的無損升級等,依賴這套方案我們的接入閘道器不需要像業界一樣依賴底層核心的改動,這極大的方便了我們方案的部署,尤其在公有云場景下。除了連線遷移以外,我們還提出了 0RTT 建聯提升方案、客戶端智慧選路方案,以最大化 QUIC 在移動端上的收益。截止當前,QUIC 已經在支付寶移動端以及全球加速鏈路兩個場景上平穩執行,並帶來了較好的業務收益。

未來規劃

兩年來,我們主要以社群的 gQuic 為基礎,充分發揮 QUIC 的協議優勢,並結合螞蟻的業務特徵以最大化移動端收益為目標,創造性的提出了一些解決方案,並積極向社群和 IETF 進行推廣。在未來,隨著螞蟻在更多業務上的開展和探索以及HTTP3.0/QUIC 即將成為標準,我們會主要圍繞以下幾個方向繼續深挖 QUIC 的價值:

  1. 我們將利用 QUIC 在應用層實現的優勢,設計一套統一的具備自適應業務型別和網路型別的 QUIC 傳輸控制框架,對不同型別的業務和網路型別,做傳輸上的調優,以優化業務的網路傳輸體驗;
  2. 將 gQUIC 切換成 IETF QUIC,推進標準的 HTTP3.0 在螞蟻的進一步落地;
  3. 將螞蟻的 QUIC LB 技術點向 IETF QUIC LB 進行推進,並最終演變為標準的 QUIC LB;
  4. 探索並落地 MPQUIC(多路徑 QUIC) 技術,最大化在移動端的收益;
  5. 繼續 QUIC 的效能優化工作,使用 UDP GSO, eBPF,io_uring 等核心技術;
  6. 探索 QUIC 在內網承載東西向流量的機會。

關注我們,每週 3 篇移動技術實踐&乾貨給你思考!

相關文章