導語:QUIC (Quick UDP Internet Connection遍希,快速UDP互聯(lián)網連接) 是一個新的基于UDP的多路復用且安全的傳輸協(xié)議。它從頭開始設計,完全棄用了TCP,使用UDP作為底層傳輸協(xié)議。QUIC提供了等價于HTTP/2 的多路復用和流控了赌,等價于 TLS 的安全機制,及等價于 TCP的連接語義玄糟、可靠性和擁塞控制勿她。據國際互聯(lián)網工程任務組(The Internet Engineering Task Force,簡稱 IETF )消息阵翎,HTTP-over-QUIC 實驗性協(xié)議將被重命名為 HTTP/3逢并,并有望成為 HTTP 協(xié)議的第三個正式版本。
本文將介紹QUIC協(xié)議在CDN技術上郭卫,騰訊多媒體的應用落地砍聊,以及相比于TCP所帶來的極快的優(yōu)化體驗,并嘗試分析QUIC到底快在哪里贰军?
1玻蝌、背景
在現有物理資源的情況下,CDN海外機房回源词疼、回傳延遲奇高俯树,RTT最高將近500ms,TCP丟包率和成功率表現不盡人意贰盗,一個大視頻分片的上傳下載需要到2秒級许饿。而QUIC的特性在網絡傳輸,尤其是弱網情況下舵盈,效果相比TCP好很多陋率。多段256 NACK、更加激進的Loss Detection丟包探測和重傳機制秽晚、以及0-RTT的握手建立瓦糟,使其相比TCP的傳輸延遲大大降低。
2赴蝇、封裝QUIC Client SDK
QUIC運行在用戶空間狸页,底層使用UDP作為傳輸協(xié)議〕对伲客戶端側主要涉及QuicConnection芍耘、QuicStream、QuicSession三個核心功能類熄阻。
QuicConnection:負責管理QUIC連接斋竞,創(chuàng)建Connectionid,完成QUIC的握手(1-RTT/0-RTT)秃殉,確保連接建立坝初。
QuicSession:會話管理,管理QUIC整個會話的生命周期钾军。包括QuicConnection鳄袍、各種定時器Alarm、以及多Stream的生命周期等吏恭。
QuicStream:QUIC真正進行數據傳輸的媒介拗小,支持多個QuicStream并發(fā)傳輸,Stream之間的數據丟包重傳等互不影響樱哼,真正消除TCP協(xié)議的隊頭阻塞缺陷哀九。
QUIC協(xié)議在應用層實現了原本TCP幾乎所有的特性:傳輸窗口、ACK搅幅、多路復用阅束、Loss Detecion、流量控制茄唐、擁塞控制等等息裸,非常復雜。所以在工程實現上我抽取了chromium的net庫等沪编,重載QuicStream呼盆、QuicSession等,封裝成易于理解的QUIC Client SDK漾抬,提供quic_socket宿亡、quic_connect、quic_send纳令、quic_recv等開發(fā)者比較習慣的socket使用方式API挽荠。
2、QUIC Client在MCP++框架上的應用
MCP++框架主要是DCC模塊負責對外的請求下載等平绩,在CDN上的應用的話圈匆,Front節(jié)點的回源、回傳都需要通過DCC去下載和上傳捏雌。
1)跃赚、QUIC由于其復雜的特性實現,運行在應用層的協(xié)議棧,需要大量的CPU計算纬傲。
2)满败、以及QUIC Client自成一體的UDP Socket收發(fā),DCC原本的epoll_wait單線程模型無法支持叹括,很難改造算墨。
故而決定采用 DCC線程 + QUIC線程 的模型支持QUIC協(xié)議,QUIC Client線程保持高度的自治汁雷,請求與響應皆自我完善净嘀。DCC原本的連接管理,CRawCache等需要自己實現侠讯。
圖2所示,DCC與QUIC交互主要有兩個流程厢漩,不破壞現有DCC框架:
1膜眠、send_mq_request_by_quic負責將來自mq的請求發(fā)給QUIC,由quic創(chuàng)建UDP socket發(fā)送出去袁翁。
2柴底、handle_network_recive_quic負責從隊列queue里取出QUIC接收完處理后的響應消息。
3粱胜、QUIC請求流程柄驻,以及失敗回退到TCP機制
由于QUIC底層通過UDP進行連接,在大規(guī)模服務場景下焙压,幾萬臺機器所處不同的運營商和網路環(huán)境鸿脓,無法保證各種中間設備UDP和端口都可用。所以在設計時不能完全依賴UDP可信涯曲,要考慮失敗回退TCP機制野哭。
DCC與QUIC的交互有send_mq_request_by_quic, handle_network_recive_quic發(fā)送和接收兩種場景幻件,分為發(fā)送失敗和接收失敗拨黔。
DCC->QUIC請求流程:
1. epoll_wait接收來自MCD的管道m(xù)q事件绰沥。
2. 判斷請求QUIC or TCP篱蝇,用QUIC 則send_mq_request_by_quic。
3. 判斷當前要請求的flow連接是否已回退到TCP徽曲,已回退的話這里直接走TCP零截。
4. QUIC,GetQuicConn(flow)獲取QUIC連接id秃臣,連接存在直接請求涧衙,不存在quic_connect()發(fā)起握手創(chuàng)建連接。
5. connect失敗(直接返回失敗,有些場景不會直接返回失敗)弧哎,flow連接回退到TCP(后續(xù)該連接其他消息30分鐘內走TCP)雁比,當前請求消息通過TCP重試。
6. connect成功傻铣,quic_send()發(fā)送數據:
a. 發(fā)送成功枷遂,SUCCESS流程結束数冬,異步等待queue響應。
b. 發(fā)送失敗俯画,flow連接回退到TCP蜕径,當前請求消息通過TCP重新嘗試两踏。
c. Stream busy,QUIC所有Stream都處在忙狀態(tài)兜喻,連接不回退梦染,只有當前請求使用tcp發(fā)送。
QUIC->DCC響應流程:
1帕识、 QUIC接收校驗完畢check_complete后,將響應消息入隊列queue遂铡。
2肮疗、DCC線程send_mq_request_by_quic從隊列Dequeue 取出響應消息。
a. QUIC_SUCCESS扒接,將響應消息enqueue 發(fā)給mcd伪货。
b. CONN_ERROR,連接中斷钾怔,將quic傳來的消息進行TCP重發(fā)碱呼,flow連接回退到TCP。
0-RTT場景下宗侦,quic_connect()發(fā)出去CLHO后client端即認為連接成功愚臀,調用quic_send()發(fā)送數據(數據緩存在QUIC底層buff), 實際握手可能會超時失敗矾利。OnConnectionClose 失敗后需要通過queue通知DCC線程做相應的處理姑裂。
4、0-RTT
QUIC比TCP快的一個很大特點是其握手使用了更少的RTT梦皮,甚至達到0-RTT握手炭分,直接進行數據傳輸,相比TCP大大減少了三次握手的等待時間剑肯。而我們在CDN上的應用更進一步捧毛,front->zone的分布式集群所有機器之間的請求,除了第一次,以后均可以實現0-RTT建鏈呀忧。
背景:QUIC handshake握手階段服務端REJ時會下發(fā)server_config和token給客戶端《耍客戶端下次握手時胰坟,如果攜帶上上次下發(fā)的server_config和token到服務端,服務端驗證通過泞辐,即可實現0-RTT笔横。
在當前的CDN架構上要實現0-RTT,有幾個問題需要解決:
問題:
問題1. 服務端front有多個ccd進程咐吼,每個進程生成的server_config都不一樣吹缔,客戶端攜帶上次的server_config請求到其他ccd進程時锯茄,服務端校驗不通過厢塘,無法0-RTT肌幽?
問題2. 客戶端ip不是固定的晚碾,4G/WIFI切換時每次都會變換ip喂急,服務端下發(fā)的token融合了客戶端的ip格嘁,客戶端的ip變化時,攜帶的token服務端校驗不通過煮岁,無法0-RTT讥蔽?
問題3. 服務端ip對于客戶端來說不是固定的,服務端是一個集群画机,請求新的服務器ip時,如果當前client沒有請求過該server_id步氏,本地沒有緩存過新server的cache(server_config等)响禽,就無法實現0-RTT荚醒。
解決方案:
解決1:服務端所有front集群固定生成同一個server_config_id芋类。
解決2:服務端關閉token驗證開關,QUIC提供關閉驗證的開關界阁,在應用層即可關閉侯繁。
解決3:客戶端每次構建新的請求連接時使用相同的server_id(host,port)。Server_id是緩存在客戶端本地泡躯,用來指引cache的key贮竟, 對于客戶端來說,這相當于把對端所有server集群看成同一個server咕别,對同一個server進行下次握手,就會直接發(fā)起full CLHO消息惰拱。服務端那邊校驗server_config通過雌贱,就能建立新的連接了偿短。
5欣孤、QUIC對比TCP的優(yōu)化效果
線上灰度觀察效果翔冀,QUIC優(yōu)化效果明顯导街。尤其是在弱網下纤子,傳輸時延大大降低:
a)、國內以長沙電信為例控硼,回源延時219ms -> 88ms,優(yōu)化效果能達到59.8%艾少,回傳原本延時就比較低了,延時優(yōu)化效果也能達到25%缚够。
b)、海外以美國機房為例谍椅,RTT比較高的場景196ms,QUIC的回源回傳效果更加明顯1.3s->339ms雏吭,優(yōu)化率達到73.9%。
6杖们、QUIC快在哪里?
QUIC的提升效果表現如此的好摘完,究竟比TCP快在什么地方呢姥饰?TCP協(xié)議經過幾十年的發(fā)展孝治,可以說構建了整個互聯(lián)網的基礎列粪。Google為了開發(fā)QUIC做了很多工作,基本上把整個TCP的協(xié)議棧都重新實現了一遍(取其精華篱竭,修復缺陷)。嘗試分析一下:
1. 0-RTT
TCP的三次握手會帶來必定的1-RTT消耗掺逼,在網絡狀況比較好的情況下,大部分的消耗可能都來自業(yè)務吕喘,不覺得RTT耗時很多赘那。但是在網路狀況差的情況下(比如上面的美國),1個RTT就帶來196ms的延時募舟,使用QUIC優(yōu)化后,可以看出整體耗時只有339ms闻察,所以0-RTT對于秒開業(yè)務來說是一個很重要的特性。
2. QUIC Stream多路復用 — TCP 隊頭阻塞
QUIC支持多路復用辕漂,QUIC的底層UDP不進行排序和確認,只負責傳輸钉嘹,ACK、確認跋涣、排序等都在QUIC進行缨睡。TCP當有多個流同時傳輸時陈辱,如果某個流發(fā)生了丟包奖年,其他流需要等待其重傳排序完成才能繼續(xù)傳輸,因為TCP的滑動窗口只有一個拾并,隊頭阻塞無法避免。而QUIC的多個Stream之間重傳嗅义、排序等互不干擾,有stream級別的流控窗口隐砸,真正從傳輸層解決隊頭阻塞的問題。
從客戶端和CDN服務器的統(tǒng)計上看到季希,同一連接多個Stream同時傳輸的場景非常頻繁幽纷。而去除了隊頭阻塞的QUIC延時比TCP低就很明顯了,并且在弱網丟包率高場景下友浸,表現更好。
3. Up to 256 NACK ranges偏窝,支持256分段ACK
TCP每次只會ACK一個數據包收恢,當滑動窗口用盡之后祭往,只能等待新的ACK到來才能繼續(xù)發(fā)送新數據包伦意。就算開啟了SACK硼补,最多也只能ACK三個包。
QUIC最大支持ACK 256個數據包已骇,支持分段ACK离钝。就算某個ACK包丟失褪储,后續(xù)其他包的ACK也能帶上之前ACK過的包號。
4. Loss Detection丟包探測
QUIC在TCP的經驗之上重新構建了一種新的丟包探測機制:FR快重傳乱豆、ER早期重傳吊趾、RTO宛裕、TLP论泛、F-RTO揩尸,和TCP相比很大不同屁奏。表現為更為激進的丟包發(fā)現和重傳策略:比如如果收到最大的ACK包號大于3就判斷丟包進行FR重傳岩榆;而TCP需要收到連續(xù)三個重復的ACK才判斷丟包進行快重傳坟瓢。
7勇边、總結
本文介紹當前主流的基于chromium的QUIC Client SDK的架構折联,以及其在騰訊MCP++框架上的工程落地。詳細介紹了MCP++框架上的 DCC+QUIC 多線程lib方案诚镰,和QUIC回退到TCP的保障機制奕坟,另外簡單介紹了下全面?0-RTT 在 Front->zone 上實現存在的問題和解決方案。通過在線上使用QUIC與TCP的實際對比效果月杉,以及QUIC多路復用、256NACK等優(yōu)秀特性苛萎,國內各大廠商紛紛在研究和應用QUIC協(xié)議,證明QUIC是一個潛力很大的技術首懈。IETF已經將HTTP over QUIC重命名為HTTP3,隨著下一代HTTP3協(xié)議的普及究履,QUIC將應用的更加廣泛滤否。