實戰(zhàn)?|?QUIC?協(xié)議在螞蟻集團落地
自 2015 年以來,QUIC 協(xié)議開始在 IETF 進行標準化并被國內(nèi)外各大廠商相繼落地。鑒于 QUIC 具備“0RTT 建聯(lián)”、“支持連接遷移”等諸多優(yōu)勢,并將成為下一代互聯(lián)網(wǎng)協(xié)議:HTTP3.0 的底層傳輸協(xié)議,螞蟻集團支付寶客戶端團隊與接入網(wǎng)關(guān)團隊于 2018 年下半年開始在移動支付、海外加速等場景落地 QUIC。
-
QUIC背景:簡單全面的介紹下 QUIC 相關(guān)的背景知識
-
方案選型設(shè)計:詳細介紹螞蟻的落地方案如何另辟蹊徑、優(yōu)雅的支撐 QUIC 的諸多特性,包括連接遷移等
-
落地場景:介紹 QUIC 在螞蟻的兩個落地場景,包括:支付寶客戶端鏈路以及海外加速鏈路
-
幾項關(guān)鍵技術(shù):介紹落地 QUIC 過程中核心需要解決的問題,以及我們使用的方案,包括:“支持連接遷移”、“提升 0RTT 比例", "支持 UDP 無損升級”以及“客戶端智能選路” 等
-
幾項關(guān)鍵的技術(shù)專利
QUIC 背景介紹
https://datatracker.ietf.org/wg/quic/documents/
一、QUIC 是什么?
簡單來說,QUIC (Quick UDP Internet Connections) 是一種基于 UDP 封裝的安全 可靠傳輸協(xié)議,他的目標是取代 TCP 并自包含 TLS 成為標準的安全傳輸協(xié)議。下圖是 QUIC 在協(xié)議棧中的位置,基于 QUIC 承載的 HTTP 協(xié)議進一步被標準化為 HTTP3.0。
二、為什么是 QUIC ?
在 QUIC 出現(xiàn)之前,TCP 承載了 90% 多的互聯(lián)網(wǎng)流量,似乎也沒什么問題,那又為何會出現(xiàn)革命者 QUIC 呢?這主要是因為發(fā)展了幾十年的 TCP 面臨 “協(xié)議僵化問題”,表現(xiàn)在幾方面:
- 網(wǎng)絡(luò)設(shè)備支持 TCP 時的僵化,表現(xiàn)在:對于一些防火墻或者 NAT 等設(shè)備,如果 TCP 引入了新的特性,比如增加了某些 TCP OPTION 等,可能會被認為是攻擊而丟包,導(dǎo)致新特性在老的網(wǎng)絡(luò)設(shè)備上無法工作。
- 網(wǎng)絡(luò)操作系統(tǒng)升級困難導(dǎo)致的 TCP 僵化,一些 TCP 的特性無法快速的被演進。
-
除此之外,當(dāng)應(yīng)用層協(xié)議優(yōu)化到 TLS1.3、 HTTP2.0 后, 傳輸層的優(yōu)化也提上了議程,QUIC 在 TCP 基礎(chǔ)上,取其精華去其糟粕具有如下的硬核優(yōu)勢:
三、QUIC 生態(tài)圈發(fā)展簡史
下圖是 QUIC 從創(chuàng)建到現(xiàn)在為止的一些比較重要的時間節(jié)點,2021 年,QUIC V1 即將成為 RFC,結(jié)束百花齊放的態(tài)勢。
- QUIC LB 組件:基于 NGINX 4層 UDP Stream 模塊開發(fā),用來基于 QUIC DCID 中攜帶的服務(wù)端信息進行路由,以支持連接遷移。
-
NGINX QUIC 服務(wù)器:開發(fā)了 NGINX_QUIC_MODULE,每個 Worker 監(jiān)聽兩種類型的端口:(1)BASE PORT ,每個 Worker 使用的相同的端口號,以 Reuseport 的形式監(jiān)聽,并暴露給 QUIC LB,用以接收客戶端過來的第一個 RTT 中的數(shù)據(jù)包,這類包的特點是 DCID 由客戶端生成,沒有路由信息。(2)Working PORT,每個 Worker 使用的不同的端口號,是真正的工作端口,用以接收第一個 RTT 之后的 QUIC 包,這類包的特定是 DCID 由服務(wù)端的進程生成攜帶有服務(wù)端的信息。
- 在不用修改內(nèi)核的情況下,完全在用戶態(tài)支持 QUIC 的連接遷移,以及連接遷移時 CID 的 Update
- 在不用修改內(nèi)核的情況下,完全在用戶態(tài)支持 QUIC 的無損升級以及其他運維問題
- 支持真正意義上的 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 鏈路。
- 在客戶端連接發(fā)生遷移的時候,可以不斷鏈繼續(xù)服務(wù)
- 客戶端在首次發(fā)起連接時,可以節(jié)省 TCP 三次握手的時間
-
對于弱網(wǎng)情況,QUIC 的傳輸控制可以帶來傳輸性能提升
- 通過 QUIC 長連接的上的 Stream 承載 TCP 請求,避免每次的跨海建聯(lián)。
- 對于跨海的網(wǎng)絡(luò),QUIC 的傳輸控制可以帶來傳輸性能提升。
專利一
我們將落地場景 2 中,通過 QUIC Stream 進行四層代理的手段來進行海外回源的加速方法進行專利保護,提出:“一種基于 QUIC 協(xié)議代理的的鏈路加速方法”,目前此專利已經(jīng)獲得美國專利授權(quán),專利號:CN110213241A。
專利二
將我們落地框架中的 QUIC LB 組件作為專利進行保護,提出:“一種無狀態(tài)、一致性、分布式的 QUIC 負載均衡設(shè)備”,目前此專利還在受理中。由于通過 QUIC LB 可以很好的支持 QUIC 協(xié)議的連接遷移問題,所以目前 IETF QUIC WG 上有關(guān)于 QUIC LB 相關(guān)的草案,我們目前已經(jīng)參與到 Draft 的討論和制定中,后序相關(guān)的方案也會繼續(xù)推廣到云上產(chǎn)品。
專利三
將我們解決的 UDP 的無損升級方法進行專利保護,提出 “一種 QUIC 服務(wù)器無損升級方案”,目前此專利還在受理中。由于 UDP 無損升級問題是一個業(yè)界難題,當(dāng)前有些手段需要在用戶態(tài)進行跳轉(zhuǎn),性能損失較大,我們的方案可以在我們的落地框架中解決當(dāng)前問題,關(guān)于這個方案的細節(jié)我們再后面的關(guān)鍵技術(shù)中進行介紹。
四項關(guān)鍵技術(shù)
技術(shù)點1.優(yōu)雅的支持連接遷移能力
先說 連接遷移面臨的問題 ,上文有提到,QUIC 有一項比較重要的功能是支持連接遷移。這里的連接遷移是指:如果客戶端在長連接保持的情況下切換網(wǎng)絡(luò),比如從 4G 切換到 Wifi , 或者因為 NAT Rebinding 導(dǎo)致五元組發(fā)生變化,QUIC 依然可以在新的五元組上繼續(xù)進行連接狀態(tài)。
QUIC 之所以能支持連接遷移,一個原因是 QUIC 底層是基于無連接的 UDP,另一個重要原因是因為 QUIC 使用唯一的 CID 來標識一個連接,而不是五元組。
如下圖所示,是 QUIC 支持連接的一個示意圖,當(dāng)客戶端出口地址從 A 切換成 B 的時候,因為 CID 保持不變,所以在 QUIC 服務(wù)器上,依然可以查詢到對應(yīng)的 Session 狀態(tài)。
再說 我們的解決方法,為了解決此問題,我們設(shè)計了開篇介紹的落地框架,這里我們將方案做一些簡化和抽象,整體思路如下圖所示:
- 在四層負載均衡上,我們設(shè)計了 QUIC LoadBalancer 的機制:
- 我們在 QUIC 的 CID 中擴展了一些字段(ServerInfo)用來關(guān)聯(lián) QUIC Server 的 IP 和 Working Port 信息。
- 在發(fā)生連接遷移的時候,QUIC LoadBalancer 可以依賴 CID 中的 ServerInfo 進行路由,避免依賴五元組關(guān)聯(lián) Session 導(dǎo)致的問題。
-
在 CID 需要 Update 的時候,NewCID 中的 ServerInfo 保留不變,這樣就避免在 CID 發(fā)生 Update 時,僅依賴 CID Hash 挑選后端導(dǎo)致的尋址不一致問題。
- 在 QUIC 服務(wù)器多進程工作模式上,我們突破了 NGINX 固有的多 Worker 監(jiān)聽在相同端口上的桎梏,設(shè)計了多端口監(jiān)聽的機制,每個 Worker 在工作端口上進行隔離,并將端口的信息攜帶在對 First Initial Packet 的回包的 CID 中,這樣代理的好處是:
- 無論是否連接遷移,QUIC LB 都可以根據(jù) ServerInfo,將報文轉(zhuǎn)發(fā)到正確的進程。
- 而業(yè)界普遍的方案是修改內(nèi)核,將 Reuse port 機制改為 Reuse CID 機制,即內(nèi)核根據(jù) CID 挑選進程。即便后面可以通過 ebpf 等手段支持,但我們認為這種修改內(nèi)核的機制對底層過于依賴,不利于方案的大規(guī)模部署和運維,尤其在公有云上。
-
使用獨立端口,也有利于多進程模式下,UDP 無損升級問題的解決,這個我們在技術(shù)點 3 中介紹。
這里先 介紹 QUIC 0RTT 原理。前文我們介紹過, QUIC 支持傳輸層握手和安全加密層握手都在一個 0RTT 內(nèi)完成。TLS1.3 本身就支持加密層握手的 0RTT,所以不足為奇。而 QUIC 如何實現(xiàn)傳輸層握手支持 0RTT 呢?
我們先看下傳輸層握手的目的,即:服務(wù)端校驗客戶端是真正想握手的客戶端,地址不存在欺騙,從而避免偽造源地址攻擊。在 TCP 中,服務(wù)端依賴三次握手的最后一個 ACK 來校驗客戶端是真正的客戶端,即只有真正的客戶端才會收到 Sever 的 syn_ack 并回復(fù)。
- 類似于 Session Ticket 原理,Server 會將客戶端的地址和當(dāng)前的 Timestamp 通過自己的 KEY 加密生成 STK。
- Client 下次握手的時候,將 STK 攜帶過來,由于 STK 無法篡改,所以 Server 通過自己的 KEY 解密,如果解出來的地址和客戶端此次握手的地址一致,且時間在有效期內(nèi),則表示客戶端可信,便可以建立連接。
-
由于客戶端第一次握手的時候,沒有這個 STK,所以服務(wù)度會回復(fù) REJ 這次握手的信息,并攜帶 STK。
-
因為 STK 是服務(wù)端加密的,所以如果下次這個客戶端路由到別的服務(wù)器上了,則這個服務(wù)器也需要可以識別出來。
- STK 中 encode 的是上一次客戶端的地址,如果下一次客戶端攜帶的地址發(fā)生了變化,則同樣會導(dǎo)致校驗失敗。此現(xiàn)象在移動端發(fā)生的概率非常大,尤其是 IPV6 場景下,客戶端的出口地址會經(jīng)常發(fā)生變化。
再介紹下我們的解決方法。第一個問題比較好解,我們只要保證集群內(nèi)的機器生成 STK 的秘鑰一致即可。第二個問題,我們的解題思路是:
- 我們在 STK 中擴展了一個 Client ID, 這個 Clinet ID 是客戶端通過無線保鏢黑盒生成并全局唯一不變的,類似于一個設(shè)備的 SIMID,客戶端通過加密的 Trasnport Parameter 傳遞給服務(wù)端,服務(wù)端在 STK 中包含這個 ID。
-
如果因為 Client IP 發(fā)生變化導(dǎo)致校驗 STK 校驗失敗,便會去校驗 Client ID,因為 ID 對于一個 Client 是永遠不變的,所以可以校驗成功,當(dāng)然前提是,這個客戶端是真實的。為了防止 Client ID 的泄露等,我們會選擇性對 Client ID 校驗?zāi)芰ψ鱿蘖鞅Wo。
技術(shù)點3. 支持 QUIC 無損升級
我們知道 UDP 無損升級是業(yè)界難題。無損升級是指在 reload 或者更新二進制時,老的進程可以處理完存量連接上的數(shù)據(jù)后優(yōu)雅退出。以 NGINX 為例,這里先介紹下 TCP 是如何處理無損升級的,主要是如下的兩個步驟:
- 老進程先關(guān)閉 listening socket,待存量連接請求都結(jié)束后,再關(guān)閉連接套接字
-
新進程從老進程繼承 listening socket , 開始 accept 新的請求
- 在熱升級的時候,old process fork 出 new process 后,new process 會繼承 listening socket 并開始 recv msg。
- 而 old process 此時如果關(guān)閉 listenging socket, 則在途的數(shù)據(jù)包便無法接收,達不到優(yōu)雅退出的目的。
-
而如果繼續(xù)監(jiān)聽,則新老進程都會同時收取新連接上的報文,導(dǎo)致老進程無法退出。
這里介紹下相關(guān)的解決方法。針對此問題,業(yè)界有一些方法,比如:在數(shù)據(jù)包中攜帶進程號,當(dāng)數(shù)據(jù)包收發(fā)錯亂后,在新老進程之間做一次轉(zhuǎn)發(fā)??紤]到接入層上的性能等原因,我們不希望數(shù)據(jù)再做一次跳轉(zhuǎn)。
結(jié)合我們的落地架構(gòu),我們設(shè)計了如下的 基于多端口輪轉(zhuǎn)的無損升級方案,簡單來說,我們讓新老進程監(jiān)聽在不同的端口組并攜帶在 CID 中,這樣 QUIC LB 就可以根據(jù)端口轉(zhuǎn)發(fā)到新老進程。為了便于運維,我們采用端口輪轉(zhuǎn)的方式,新老進程會在 reload N 次之后,重新開始之前選中的端口。如下圖所示:
- 無損升級期間,老進程的 Baseport 端口關(guān)閉,這樣不會再接受 first intial packet, 類似于關(guān)閉了 tcp 的 listening socket。
- 老進程的工作端口,繼續(xù)工作,用來接收當(dāng)前進程上殘余的流量。
- 新進程的 Baseport 開始工作,用來接收 first initial packet, 開啟新的連接,類似于開啟了 tcp 的 listening socket。
-
新進程的 working port = (I 1) mod N, N 是指同時支持新老進程的狀態(tài)的次數(shù),例如 N = 4, 表示可以同時 reload 四次,四種 Old, New1, New2, New3 四種狀態(tài)同時并存,I 是上一個進程工作的端口號,這里 1 是因為只有一個 worker, 如果 worker 數(shù)有 M 個,則加 M。
-
建好的連接便被 Load Balancer 轉(zhuǎn)移到新進程的監(jiān)聽端口的 Working Port 上。
- 在帶寬緊張的時候,UDP 會經(jīng)常被限流。
- 一些防火墻對于 UDP 包會直接 Drop。
- NAT 網(wǎng)關(guān)針對 UDP 的 Session 存活時間也較短。
做個總結(jié)
未來規(guī)劃
- 我們將利用 QUIC 在應(yīng)用層實現(xiàn)的優(yōu)勢,設(shè)計一套統(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ò)傳輸體驗。
- 將 gQUIC 切換成 IETF QUIC,推進標準的 HTTP3.0 在螞蟻的進一步落地。
- 將螞蟻的 QUIC LB 技術(shù)點向 IETF QUIC LB 進行推進,并最終演變?yōu)闃藴实?QUIC LB。
- 探索并落地 MPQUIC(多路徑 QUIC) 技術(shù),最大化在移動端的收益。
- 繼續(xù) QUIC 的性能優(yōu)化工作,使用 UDP GSO, eBPF,io_uring 等內(nèi)核技術(shù)。
- 探索 QUIC 在內(nèi)網(wǎng)承載東西向流量的機會。
作者:孔令濤- EOF -來源:金融級分布式架構(gòu)公眾號