QUIC 協(xié)議在螞蟻落地綜述
積跬步至千里:QUIC 協(xié)議在螞蟻集團(tuán)落地之綜述
自 2015 年以來,QUIC 協(xié)議開始在 IETF 進(jìn)行標(biāo)準(zhǔn)化并被國內(nèi)外各大廠商相繼落地。鑒于 QUIC 具備“0RTT 建聯(lián)”、“支持連接遷移”等諸多優(yōu)勢,并將成為下一代互聯(lián)網(wǎng)協(xié)議:HTTP3.0 的底層傳輸協(xié)議,螞蟻集團(tuán)支付寶客戶端團(tuán)隊(duì)與接入網(wǎng)關(guān)團(tuán)隊(duì)于 2018 年下半年開始在移動支付、海外加速等場景落地 QUIC。
本文是綜述篇,介紹 QUIC 在螞蟻的整體落地情況。之所以是綜述,是因?yàn)?QUIC 協(xié)議過于復(fù)雜,如果對標(biāo)已有的協(xié)議,QUIC 近似等于 HTTP + TLS +TCP,無法詳細(xì)的畢其功于一役,因此我們通過綜述的方式將落地的重點(diǎn)呈現(xiàn)給讀者,主要介紹如下幾個部分:
QUIC背景:簡單全面的介紹下 QUIC 相關(guān)的背景知識
方案選型設(shè)計(jì):詳細(xì)介紹螞蟻的落地方案如何另辟蹊徑、優(yōu)雅的支撐 QUIC 的諸多特性,包括連接遷移等
落地場景:介紹 QUIC 在螞蟻的兩個落地場景,包括:支付寶客戶端鏈路以及海外加速鏈路
幾項(xiàng)關(guān)鍵技術(shù):介紹落地 QUIC 過程中核心需要解決的問題,以及我們使用的方案,包括:“支持連接遷移”、“提升 0RTT 比例", "支持 UDP 無損升級”以及“客戶端智能選路” 等
幾項(xiàng)關(guān)鍵的技術(shù)專利
本文也是 QUIC 協(xié)議介紹的第一篇,后續(xù)我們會把更多的落地細(xì)節(jié)、體驗(yàn)優(yōu)化手段、性能優(yōu)化手段、安全與高可用、QUIC 新技術(shù)等呈現(xiàn)給大家。
QUIC 背景介紹
一、QUIC 是什么?

二、為什么是 QUIC ?
網(wǎng)絡(luò)設(shè)備支持 TCP 時的僵化,表現(xiàn)在:對于一些防火墻或者 NAT 等設(shè)備,如果 TCP 引入了新的特性,比如增加了某些 TCP OPTION 等,可能會被認(rèn)為是攻擊而丟包,導(dǎo)致新特性在老的網(wǎng)絡(luò)設(shè)備上無法工作。 網(wǎng)絡(luò)操作系統(tǒng)升級困難導(dǎo)致的 TCP 僵化,一些 TCP 的特性無法快速的被演進(jìn)。 除此之外,當(dāng)應(yīng)用層協(xié)議優(yōu)化到 TLS1.3、 HTTP2.0 后, 傳輸層的優(yōu)化也提上了議程,QUIC 在 TCP 基礎(chǔ)上,取其精華去其糟粕具有如下的硬核優(yōu)勢:

三、QUIC 生態(tài)圈發(fā)展簡史

介紹完 QUIC 相關(guān)背景,之后我們來介紹螞蟻的整個落地的內(nèi)容,這里為了便于闡述,我們用螞蟻 QUIC 的 一、二、三、四 來進(jìn)行概括總結(jié),即 “一套落地框架”、“兩個落地場景”、“三篇創(chuàng)新專利保護(hù)”、“四項(xiàng)關(guān)鍵技術(shù)”。
一套落地框架
螞蟻的接入網(wǎng)關(guān)是基于多進(jìn)程的 NGINX 開發(fā)的 (內(nèi)部稱為 Spanner,協(xié)議卸載的扳手),而 UDP 在多進(jìn)程編程模型上存在諸多挑戰(zhàn),典型的像無損升級等。為了設(shè)計(jì)一套完備的框架,我們在落地前充分考慮了服務(wù)端在云上部署上的方便性、擴(kuò)展性、以及性能問題,設(shè)計(jì)了如下的落地框架以支撐不同的落地場景:

在這套框架中,包括如下兩個組件:
QUIC LB 組件:基于 NGINX 4層 UDP Stream 模塊開發(fā),用來基于 QUIC DCID 中攜帶的服務(wù)端信息進(jìn)行路由,以支持連接遷移。 NGINX QUIC 服務(wù)器:開發(fā)了 NGINX_QUIC_MODULE,每個 Worker 監(jiān)聽兩種類型的端口: (1)BASE PORT ,每個 Worker 使用的相同的端口號,以 Reuseport 的形式監(jiān)聽,并暴露給 QUIC LB,用以接收客戶端過來的第一個 RTT 中的數(shù)據(jù)包,這類包的特點(diǎn)是 DCID 由客戶端生成,沒有路由信息。 (2)Working PORT,每個 Worker 使用的不同的端口號,是真正的工作端口,用以接收第一個 RTT 之后的 QUIC 包,這類包的特定是 DCID 由服務(wù)端的進(jìn)程生成攜帶有服務(wù)端的信息。
在不用修改內(nèi)核的情況下,完全在用戶態(tài)支持 QUIC 的連接遷移,以及連接遷移時 CID 的 Update 在不用修改內(nèi)核的情況下,完全在用戶態(tài)支持 QUIC 的無損升級以及其他運(yùn)維問題 支持真正意義上的 0RTT ,并可提升 0RTT 的比例
兩個落地場景
場景一、支付寶移動端落地

具體的方案選型如下:
支持的 QUIC 版本是 gQUIC Q46。 NGINX QUIC MODULE 支持 QUIC 的接入和 PROXY 成 TCP 的能力。 支持包括移動支付、基金、螞蟻森林在內(nèi)的所有的 RPC 請求。 當(dāng)前選擇 QUIC 鏈路的方式有兩種 :
Backup 模式,即在 TCP 鏈路無法使用的情況下,降級到 QUIC 鏈路。 Smart 模式,即 TCP和 QUIC 競速,在 TCP 表現(xiàn)力弱于 QUIC 的情況下,下次請求主動使用 QUIC 鏈路。
在此場景下,通過使用 QUIC 可以獲得的紅利包括:
在客戶端連接發(fā)生遷移的時候,可以不斷鏈繼續(xù)服務(wù) 客戶端在首次發(fā)起連接時,可以節(jié)省 TCP 三次握手的時間 對于弱網(wǎng)情況,QUIC 的傳輸控制可以帶來傳輸性能提升

在海外接入點(diǎn)上(LP),每一個 TCP 連接都被 Proxy 成 QUIC 上的一個 Stream 進(jìn)行承載,在國內(nèi)接出點(diǎn)上(RP), 每一個 QUIC Stream 又被 Proxy 成一個 TCP 連接,LP 和 RP 之間使用 QUIC 長連接。
通過 QUIC 長連接的上的 Stream 承載 TCP 請求,避免每次的跨海建聯(lián)。 對于跨海的網(wǎng)絡(luò),QUIC 的傳輸控制可以帶來傳輸性能提升。
三篇關(guān)鍵專利
專利一
專利二
專利三
四項(xiàng)關(guān)鍵技術(shù)
技術(shù)點(diǎn)1.優(yōu)雅的支持連接遷移能力
先說 連接遷移面臨的問題 ,上文有提到,QUIC 有一項(xiàng)比較重要的功能是支持連接遷移。這里的連接遷移是指:如果客戶端在長連接保持的情況下切換網(wǎng)絡(luò),比如從 4G 切換到 Wifi , 或者因?yàn)?NAT Rebinding 導(dǎo)致五元組發(fā)生變化,QUIC 依然可以在新的五元組上繼續(xù)進(jìn)行連接狀態(tài)。QUIC 之所以能支持連接遷移,一個原因是 QUIC 底層是基于無連接的 UDP,另一個重要原因是因?yàn)?QUIC 使用唯一的 CID 來標(biāo)識一個連接,而不是五元組。

然而,理論很豐滿,落地卻很艱難,在端到端的落地過程中,因?yàn)橐肓素?fù)載均衡設(shè)備,會導(dǎo)致在連接遷移時,所有依賴五元組 Hash 做轉(zhuǎn)發(fā)或者關(guān)聯(lián) Session 的機(jī)制失效。以 LVS 為例,連接遷移后, LVS 依靠五元組尋址會導(dǎo)致尋址的服務(wù)器存在不一致。即便 LVS 尋址正確,當(dāng)報文到達(dá)服務(wù)器時,內(nèi)核根據(jù)五元組關(guān)聯(lián)進(jìn)程,依然會尋址出錯。同時,IETF Draft 要求,連接遷移時 CID 需要更新掉,這就為僅依靠 CID 來轉(zhuǎn)發(fā)的計(jì)劃同樣變的不可行。
再說 我們的解決方法,為了解決此問題,我們設(shè)計(jì)了開篇介紹的落地框架,這里我們將方案做一些簡化和抽象,整體思路如下圖所示:
在四層負(fù)載均衡上,我們設(shè)計(jì)了 QUIC LoadBalancer 的機(jī)制:
我們在 QUIC 的 CID 中擴(kuò)展了一些字段(ServerInfo)用來關(guān)聯(lián) QUIC Server 的 IP 和 Working Port 信息。 在發(fā)生連接遷移的時候,QUIC LoadBalancer 可以依賴 CID 中的 ServerInfo 進(jìn)行路由,避免依賴五元組關(guān)聯(lián) Session 導(dǎo)致的問題。 在 CID 需要 Update 的時候,NewCID 中的 ServerInfo 保留不變,這樣就避免在 CID 發(fā)生 Update 時,僅依賴 CID Hash 挑選后端導(dǎo)致的尋址不一致問題。
在 QUIC 服務(wù)器多進(jìn)程工作模式上,我們突破了 NGINX 固有的多 Worker 監(jiān)聽在相同端口上的桎梏,設(shè)計(jì)了多端口監(jiān)聽的機(jī)制,每個 Worker 在工作端口上進(jìn)行隔離,并將端口的信息攜帶在對 First Initial Packet 的回包的 CID 中,這樣代理的好處是:
無論是否連接遷移,QUIC LB 都可以根據(jù) ServerInfo,將報文轉(zhuǎn)發(fā)到正確的進(jìn)程。 而業(yè)界普遍的方案是修改內(nèi)核,將 Reuse port 機(jī)制改為 Reuse CID 機(jī)制,即內(nèi)核根據(jù) CID 挑選進(jìn)程。即便后面可以通過 ebpf 等手段支持,但我們認(rèn)為這種修改內(nèi)核的機(jī)制對底層過于依賴,不利于方案的大規(guī)模部署和運(yùn)維,尤其在公有云上。 使用獨(dú)立端口,也有利于多進(jìn)程模式下,UDP 無損升級問題的解決,這個我們在技術(shù)點(diǎn) 3 中介紹。

技術(shù)點(diǎn)2.提升 0RTT 握手比例
這里先 介紹 QUIC 0RTT 原理。前文我們介紹過, QUIC 支持傳輸層握手和安全加密層握手都在一個 0RTT 內(nèi)完成。TLS1.3 本身就支持加密層握手的 0RTT,所以不足為奇。而 QUIC 如何實(shí)現(xiàn)傳輸層握手支持 0RTT 呢?我們先看下傳輸層握手的目的,即:服務(wù)端校驗(yàn)客戶端是真正想握手的客戶端,地址不存在欺騙,從而避免偽造源地址攻擊。在 TCP 中,服務(wù)端依賴三次握手的最后一個 ACK 來校驗(yàn)客戶端是真正的客戶端,即只有真正的客戶端才會收到 Sever 的 syn_ack 并回復(fù)。

QUIC 同樣需要對握手的源地址做校驗(yàn),否則便會存在 UDP 本身的 DDOS 問題,那 QUIC 是如何實(shí)現(xiàn)的?依賴 STK(Source Address Token) 機(jī)制。這里我們先聲明下,跟 TLS 類似,QUIC 的 0RTT 握手,是建立在已經(jīng)同一個服務(wù)器建立過連接的基礎(chǔ)上,所以如果是純的第一次連接,仍然需要一個 RTT 來獲取這個 STK。如下圖所示,我們介紹下這個原理:
類似于 Session Ticket 原理,Server 會將客戶端的地址和當(dāng)前的 Timestamp 通過自己的 KEY 加密生成 STK。 Client 下次握手的時候,將 STK 攜帶過來,由于 STK 無法篡改,所以 Server 通過自己的 KEY 解密,如果解出來的地址和客戶端此次握手的地址一致,且時間在有效期內(nèi),則表示客戶端可信,便可以建立連接。 由于客戶端第一次握手的時候,沒有這個 STK,所以服務(wù)度會回復(fù) REJ 這次握手的信息,并攜帶 STK。

理論上說,只要客戶端緩存了這個 STK,下次握手的時候帶過來,服務(wù)端便可以直接校驗(yàn)通過,即實(shí)現(xiàn)傳輸層的 0RTT。但是真實(shí)的場景卻存在如下兩個問題:
因?yàn)?STK 是服務(wù)端加密的,所以如果下次這個客戶端路由到別的服務(wù)器上了,則這個服務(wù)器也需要可以識別出來。
STK 中 encode 的是上一次客戶端的地址,如果下一次客戶端攜帶的地址發(fā)生了變化,則同樣會導(dǎo)致校驗(yàn)失敗。此現(xiàn)象在移動端發(fā)生的概率非常大,尤其是 IPV6 場景下,客戶端的出口地址會經(jīng)常發(fā)生變化。
再介紹下我們的解決方法。第一個問題比較好解,我們只要保證集群內(nèi)的機(jī)器生成 STK 的秘鑰一致即可。第二個問題,我們的解題思路是:
我們在 STK 中擴(kuò)展了一個 Client ID, 這個 Clinet ID 是客戶端通過無線保鏢黑盒生成并全局唯一不變的,類似于一個設(shè)備的 SIMID,客戶端通過加密的 Trasnport Parameter 傳遞給服務(wù)端,服務(wù)端在 STK 中包含這個 ID。 如果因?yàn)?Client IP 發(fā)生變化導(dǎo)致校驗(yàn) STK 校驗(yàn)失敗,便會去校驗(yàn) Client ID,因?yàn)?ID 對于一個 Client 是永遠(yuǎn)不變的,所以可以校驗(yàn)成功,當(dāng)然前提是,這個客戶端是真實(shí)的。為了防止 Client ID 的泄露等,我們會選擇性對 Client ID 校驗(yàn)?zāi)芰ψ鱿蘖鞅Wo(hù)。

技術(shù)點(diǎn)3. 支持 QUIC 無損升級
我們知道 UDP 無損升級是業(yè)界難題。無損升級是指在 reload 或者更新二進(jìn)制時,老的進(jìn)程可以處理完存量連接上的數(shù)據(jù)后優(yōu)雅退出。以 NGINX 為例,這里先介紹下 TCP 是如何處理無損升級的,主要是如下的兩個步驟:
老進(jìn)程先關(guān)閉 listening socket,待存量連接請求都結(jié)束后,再關(guān)閉連接套接字 新進(jìn)程從老進(jìn)程繼承 listening socket , 開始 accept 新的請求
在熱升級的時候,old process fork 出 new process 后,new process 會繼承 listening socket 并開始 recv msg。 而 old process 此時如果關(guān)閉 listenging socket, 則在途的數(shù)據(jù)包便無法接收,達(dá)不到優(yōu)雅退出的目的。 而如果繼續(xù)監(jiān)聽,則新老進(jìn)程都會同時收取新連接上的報文,導(dǎo)致老進(jìn)程無法退出。

這里介紹下相關(guān)的解決方法。針對此問題,業(yè)界有一些方法,比如:在數(shù)據(jù)包中攜帶進(jìn)程號,當(dāng)數(shù)據(jù)包收發(fā)錯亂后,在新老進(jìn)程之間做一次轉(zhuǎn)發(fā)。考慮到接入層上的性能等原因,我們不希望數(shù)據(jù)再做一次跳轉(zhuǎn)。結(jié)合我們的落地架構(gòu),我們設(shè)計(jì)了如下的 基于多端口輪轉(zhuǎn)的無損升級方案,簡單來說,我們讓新老進(jìn)程監(jiān)聽在不同的端口組并攜帶在 CID 中,這樣 QUIC LB 就可以根據(jù)端口轉(zhuǎn)發(fā)到新老進(jìn)程。為了便于運(yùn)維,我們采用端口輪轉(zhuǎn)的方式,新老進(jìn)程會在 reload N 次之后,重新開始之前選中的端口。如下圖所示:
無損升級期間,老進(jìn)程的 Baseport 端口關(guān)閉,這樣不會再接受 first intial packet, 類似于關(guān)閉了 tcp 的 listening socket。 老進(jìn)程的工作端口,繼續(xù)工作,用來接收當(dāng)前進(jìn)程上殘余的流量。 新進(jìn)程的 Baseport 開始工作,用來接收 first initial packet, 開啟新的連接,類似于開啟了 tcp 的 listening socket。 新進(jìn)程的 working port = (I + 1) mod N, N 是指同時支持新老進(jìn)程的狀態(tài)的次數(shù),例如 N = 4, 表示可以同時 reload 四次,四種 Old, New1, New2, New3 四種狀態(tài)同時并存,I 是上一個進(jìn)程工作的端口號,這里 + 1 是因?yàn)橹挥幸粋€ worker, 如果 worker 數(shù)有 M 個,則加 M。
建好的連接便被 Load Balancer 轉(zhuǎn)移到新進(jìn)程的監(jiān)聽端口的 Working Port 上。

技術(shù)點(diǎn)4.客戶端智能選路
在帶寬緊張的時候,UDP 會經(jīng)常被限流。 一些防火墻對于 UDP 包會直接 Drop。 NAT 網(wǎng)關(guān)針對 UDP 的 Session 存活時間也較短。
同時,根據(jù)觀察發(fā)現(xiàn),不同的手機(jī)廠商對于 UDP 的支持能力也不同,所以在落地過程中,如果盲目的將所有流量完全切為 QUIC 可能會導(dǎo)致一些難以預(yù)料的結(jié)果。為此,我們在客戶端上,設(shè)計(jì)了開篇介紹的 TCP 和 QUIC 相互 Backup 的鏈路,如下圖所示,我們實(shí)時探測 TCP 鏈路和 QUIC 鏈路的 RTT、丟包率、請求完成時間、錯誤率等指標(biāo)情況,并根據(jù)一定的量化方法對兩種鏈路進(jìn)行打分,根據(jù)評分高低,決定選擇走哪種鏈路,從而避免尋址只走一條鏈路導(dǎo)致的問題。

做個總結(jié)
未來規(guī)劃
我們將利用 QUIC 在應(yīng)用層實(shí)現(xiàn)的優(yōu)勢,設(shè)計(jì)一套統(tǒng)一的具備自適應(yīng)業(yè)務(wù)類型和網(wǎng)絡(luò)類型的 QUIC 傳輸控制框架,對不同類型的業(yè)務(wù)和網(wǎng)絡(luò)類型,做傳輸上的調(diào)優(yōu),以優(yōu)化業(yè)務(wù)的網(wǎng)絡(luò)傳輸體驗(yàn)。 將 gQUIC 切換成 IETF QUIC,推進(jìn)標(biāo)準(zhǔn)的 HTTP3.0 在螞蟻的進(jìn)一步落地。 將螞蟻的 QUIC LB 技術(shù)點(diǎn)向 IETF QUIC LB 進(jìn)行推進(jìn),并最終演變?yōu)闃?biāo)準(zhǔn)的 QUIC LB。 探索并落地 MPQUIC(多路徑 QUIC) 技術(shù),最大化在移動端的收益。 繼續(xù) QUIC 的性能優(yōu)化工作,使用 UDP GSO, eBPF,io_uring 等內(nèi)核技術(shù)。 探索 QUIC 在內(nèi)網(wǎng)承載東西向流量的機(jī)會。
