誕生背景
- HTTP/1:每次請求都建立一個TCP連接
- HTTP/1.1:支持長連接,同一個IP對應一個TCP連接
-
HTTP/2:TCP多路復用,同一個TCP並發 多個HTTP請求
- 並發數量在瀏覽器實現上有限制,以Chrome為例為6,普遍為6~8(可能為滑動窗口大小限制,或者因為更多的並發數量若發生頭部擁塞使得總體傳輸速率下降)
使用HTTP/2所提供的多路復用功能在鏈路出現丟包時,TCP的按序確認機制使得丟失的數據包需要等待重新發送和確認,滑動窗口停滯,其后的所有數據包都被阻塞,這樣一來HTTP/2在這種情形下的表現反而不如HTTP/1。
此外,HTTP/2在建立TCP連接的時,需要和服務器進行三次握手來確認連接成功,會消耗1.5個RTT,如果使用HTTPS的話,還需要使用TLS協議進行加密,而TLS也根據版本需要1~2個RTT(TLS1.2需要1RTT),也就是說,使用HTTP/2在信息得到傳輸前就需要消耗3~4個RTT(至少2.5RTT)的時間。
TCP的短板問題
- TCP +TSL握手占用時間(至少2.5RTT)
-
TCP巨大的頭部浪費帶寬(20~60字節)
- TCP 頭部擁塞
TCP的按序確認除了導致頭部擁塞外,還導致了另一個重傳包數量問題:TCP接收方可以將未按序到達的數據包37、38、40先行緩存(並且引入快速重傳機制,回送缺失數據包34的ack不斷提醒發送方,如果發送方連續收到3次相同的ack,就會重傳,防止超時引發窗口縮小),但是由於ack序列號只能確認連續的數據包,所以無法通知發送方37、38、40已經先行到達,只能發送數據包34的ack,而發送方在接收到重傳請求后不確定從35~41這些已經發送的數據包要不要同樣重傳,因為后續的包可能被接收,也可能丟失。如果全部重傳,那么會浪費帶寬,如果不重傳,那么如果這些包丟失,就會浪費時間。(后續又引入了SACK機制:https://caoziye.top/2019/10/TCP-Options/,但是SACK又繼續增大了TCP的頭部)
- TCP連接無法遷移(源IP + 源Port + 目標IP + 目標Port + 傳輸層協議)
除了傳輸層協議是TCP不變以外,剩下的四元組其中任一發生變化,TCP連接就會斷開,需要重新和新的ip:port重新握手建立連接。比如移動設備wifi和5g網絡的切換,或者是行車過程中導致的移動網絡節點的切換都會讓TCP的連接斷開。
傳輸層協議帶來的問題無法在應用層協議上得到解決,並且TCP因為已經存在了40多年,基於TCP協議的更新非常難以推進(因為被大量內置於操作系統內核、中間件固件以及硬件實現中),因此Google基於UDP協議推出了QUIC協議。
UDP協議相較於TCP,擁有更小的頭部,簡單而高效,但是不保證可靠交付,因此使用UDP協議同時為了確保數據傳輸的可靠性,需要自己維護丟包檢測、數據確認、擁塞控制、重傳等等一系列基礎設施。
QUIC主要特性
多路復用
HTTP/2.0使得一個TCP連接能夠順序傳輸多個文件,再通過SPDY協議實現請求的並發以及優先級控制,但是終歸會受到頭部擁塞的限制。
而QUIC是基於UDP的,在傳輸層層面並沒有固定的連接,可以根據需要開辟任意邏輯鏈路。QUIC一次建立一個Connection,一個Connection下包含多個Stream流(每個stream獨自維護一個邏輯連接,因為UDP層面上是無連接的),每個流對應一個文件傳輸,並將不同的Stream中的數據交付給不同的上層應用。QUIC的一個Connection對應多個Stream,Stream之間相互獨立,因此任意一條鏈路斷開都不會導致其他數據阻塞。
協議頭部
QUIC是基於UDP的,所以最外層是UDP頭部(單位為Bit)
內部是QUIC Connection頭部和每個Stream的Frame頭部(單位為Bit)
具體每個頭部字段含義和標志位過於機械和繁雜,有興趣可以直接查看原文https://datatracker.ietf.org/doc/html/rfc9000#section-12
- Flags: 用於表示 Connection ID 長度、Packet Number 長度等信息;
- Connection ID:客戶端選擇的無符號64位統計隨機數,該數字是連接的標識符。由於 QUIC 的連接被設計為,即使客戶端漫游,連接依然保持建立狀態,因而 IP 4元組(源IP,源端口,目標IP,目標端口)可能不足以標識連接。對每個傳輸方向,當4元組足以標識連接時,連接ID可以省略。
- QUIC Version:QUIC 協議的版本號,32 位的可選字段。
- Diversification nonce:這是服務端用於生成會話密鑰的字段,僅存於服務端->客戶端的請求中。一旦前向保密連接得到建立,后續就不會再包含這個字段了,簡單理解就是只在服務端->客戶端的握手請求中才會使用。(因此QUIC工作組也推進TLS的后續標准將這個字段整合進TLS1.3的頭部中,而不存在於QUIC中)
- Packet Number:長度取決於 Public Flag 中 Bit4 及 Bit5 兩位的值,最大長度 6 字節。發送端在每個普通報文中設置 Packet Number。發送端發送的第一個包的序列號是 1,隨后的數據包中的序列號的都大於前一個包中的序列號;
- Stream ID:用於標識當前數據流屬於哪個資源請求;
- Offset:標識當前數據包在當前 Stream ID 中的偏移量。
數據流控制
QUIC提供了兩種層面上的數據流控制方案:
- Stream 流量控制,通過限制在任何 stream 上可以發送的最大絕對字節偏移量,防止單個 stream 消耗連接(connection)的全部接收緩沖。
- Connection流量控制,通過限制所有
STREAM
幀的數據總字節數,防止發送方超過接收方的連接緩沖容量。
Stream控制
- QUIC 的Stream流基於Stream ID+Offset進行包確認,流量控制需要保證所發送的所有包offset小於最大絕對字節偏移量 ( maximum absolute byte offset ) , 該值是基於當前已經提交的字節偏移量(offset of data consumed) 而進行確定的,QUIC會把連續的已確認的offset數據向上層應用提交。QUIC支持亂序確認,但本身也是按序(offset順序)發送數據包。
- QUIC利用ack frame來進行數據包的確認,來保證可靠傳輸。一個ack frame只包含多個確認信息,沒有正文。
- 如果數據包N超時,發送端將超時數據包N重新設置編號M(即下一個順序的數據包編號) 后發送給接收端。
- 在一個數據包發生超時后,其余的已經發送的數據包依舊可以基於Offset得到確認,避免了TCP利用SACK才能解決的重傳問題。
💡 其實QUIC的亂序確認設計思想並不新鮮,大量網絡視頻流就是通過類似的基於UDP的RUDP、RTP、UDT等協議來實現快速可靠傳輸的。他們同樣支持亂序確認,所以就會導致這樣的觀看體驗:明明進度條顯示還有一段緩存,但是畫面就是卡着不動了,如果跳過的話視頻又能夠播放了。
- 如圖所示,當前緩沖區大小為8,QUIC按序(offset順序)發送29-36的數據包:
- 31、32、34數據包先到達,基於offset被優先亂序確認,但30數據包沒有確認,所以當前已提交的字節偏移量不變,緩存區不變。
- 30到達並確認,緩存區收縮到閾值,接收方發送MAX_STREAM_DATA frame(協商緩存大小的特定幀)給發送方,請求增長最大絕對字節偏移量。
- 協商完畢后最大絕對字節偏移量右移,緩存區變大,同時發送方發現數據包33超時
- 發送方將超時數據包重新編號為42繼續發送
以上就是最基本的數據包發送-接收過程,控制數據發送的唯一限制就是最大絕對字節偏移量,該值是接收方基於當前已經提交的偏移量(連續已確認並向上層應用提交的數據包offset)和發送方協商得出。
Connection控制
除了Stream層面的數據流控制之外,QUIC還提供了Connection層面的總體緩存大小控制,Connection具有總體的緩沖區大小限制,並且可以為其中的各個stream動態分配緩沖區大小,在總體緩沖區大小不變的情況下優先向速度更快的stream傾斜(並不是平均分配)。
如圖所示,Connection具有傳輸字節上限,即Stream1、2、3的Maximum Offset之和不得超過該上限,QUIC會根據網絡情況為各個Stream分配不同的偏移量,並且隨着傳輸的進行,接收方會發送MAX_DATA frame通知發送方提高Connection總體傳輸字節分配上限,並在Stream連接中通過MAX_STREAM_DATA frame為各個Stream分配更多的緩存。
快速握手與加密傳輸
QUIC在握手過程中使用Diffie-Hellman算法協商初始密鑰,初始情況下服務器存儲的配置參數如下:
- Server Config:一個服務器配置文件,包括服務器端的Diffie-Hellman算法的長期公鑰A以及兩個固定質數g和p
- Certificate Chain:用來對服務器進行認證的信任鏈證書
- Signature of the Server Config:Server Config的簽名並用信任鏈的葉子證書的私鑰加密
- Source-Address Token:一個經過身份驗證的加密塊,包含客戶端可見的IP地址和服務器的時間戳。
這些參數會周期性的更新。
Diffie-Hellman 算法的基本原理
Diffie-Hellman並不是加密算法,而是密鑰的一種交換技術,可以通過該算法在雙方互不知情的情況下建立加密通訊
假設Alice為服務器,Bob為客戶端
- Alice和 Bob 都知道兩個素數(g、p)的存在
- Alice隨機選擇a作為private key,Bob隨機選擇b作為private key
於是,雙方都有了一個共享密鑰 (初始密鑰)K。簡單理解,a、b就相當於密鑰,A、B就相當於公鑰。
隨后再利用這個初始密鑰商定會話密鑰,之后就一直用會話密鑰溝通了。
密鑰交換過程
QUIC 首次連接需要1RTT,具體過程如下:
step1: 客戶端發送Inchoate Client Hello消息(CHLO)請求建立連接。
step2: 服務器根據一組質數p以及其原根g和a(長期私鑰)算出A(長期公鑰),將Apg(通過CA證書私鑰加密后)放在serverConfig里面,發到Rejection消息(REJ)到客戶端;
服務器一開始不直接使用隨機生成的短期密鑰的原因就是因為客戶端可以緩存下服務端的長期公鑰,這樣在下一次連接的時候客戶端就可以直接使用這個長期公鑰實現0-RTT握手並直接發送加密數據
setp3&4: 客戶端在接收到REJ消息后,會隨機選擇一個數b(短期密鑰),並用CA證書獲取的公鑰解密出serverConfig里面的p、A和b就可以算出初始密鑰K,並將B(Complete client hello消息)和用初始密鑰K加密的Data數據發到服務器。
step5: 服務器收到客戶端發來的公開數B,再利用p、g計算得到同樣的初始秘鑰K,來解密客戶端發來的數據。這時會利用其他加密算法隨機生成此次會話密鑰K' ,再通過初始密鑰K加密K'發送給客戶端(SHLO)(每次會話都是用隨機密鑰,並且服務器會定期更新a和A,實際上這就是為了保證前向安全性)
在密碼學中,前向保密(Forward Secrecy)是密碼學中通訊協議的安全屬性,指的是當前使用的主密鑰泄漏不會導致過去的會話密鑰泄漏。
step6: 客戶端收到SHLO后利用初始密鑰K解出會話密鑰K',二者后續的會話都使用K'加密。
連接遷移
TCP 的連接標識是通過 “源IP + 源Port + 目標IP + 目標Port + 傳輸層協議(TCP)” 組成的唯一五元組,一旦其中一個參數發生變化,則需要重新創建新的 TCP 連接。
- 比如wifi和5g網絡切換
- 服務數據節點切換
都會造成TCP斷線,需要客戶端上層應用重新發送請求建立連接(又一次進行握手)
QUIC 連接不再以 IP 及端口四元組標識,而是以一個服務端產生的 64 位的隨機數作為 ID 來標識,這樣就算 IP 或者端口發生變化時,只要 ID 不變,這條連接依然維持着,上層業務邏輯感知不到變化,不會中斷,也就不需要重連。(當然如果UDP和IP協議所包含的源IP + 源Port + 目標IP + 目標Port四元組已經能夠標識鏈接的唯一性的話,connection頭部是可忽略的)
連接遷移的簡化流程(實際情況更為復雜):
- 連接遷移之前,客戶端的IP 1,使用非探測包(Non-probing Packet)和服務端進行通信。
- 客戶端的IP變成 2,它繼續發送非探測包維持通信,將連接遷移到新的地址。
-
服務端收到包后在新路徑啟動路徑驗證[1],驗證新路徑的可達性,以及客戶端對其新IP地址的所有權。
- 服務端發送包含
PATH_CHALLENGE
幀的探測包(Probing Packet),PATH_CHALLENGE
幀里面包含一個不可預測的隨機值。 - 客戶端在
PATH_RESPONSE
幀里面包含前一步PATH_CHALLENGE
接收到的隨機值,響應探測包(Probing Packet)。 - 服務端接收到客戶端發送的的
PATH_RESPONSE
,驗證 payload 里面的值是否正確。
- 隨后客戶端也會對服務端進行路徑驗證保證雙向通信。
丟包檢測
TCP 傳輸的數據只包括校驗碼,並沒有增加糾錯碼等冗余數據,如果出現部分數據丟失或損壞,只能重新發送該數據包。
QUIC 引入了前向冗余糾錯碼(FEC: Fowrard Error Correcting),如果接收端出現少量(不超過FEC的糾錯能力)的丟包或錯包,可以借助冗余糾錯碼恢復丟失或損壞的數據包,這就不需要再重傳該數據包了,降低了丟包重傳概率,自然就減少了擁塞控制機制的觸發次數,可以維持較高的網絡利用效率。因此需要根據當前網絡狀況設置一定比率的冗余數據,就可以帶來網絡利用率的提升。
此外由於QUIC 采用單向遞增的Packet Number 來標識數據包,所以不像TCP會因為超時重傳的同樣序列的數據包而和原數據包重疊,造成RTT測量的不准確,進而導致RTO(Retransmission Time Out:重傳超時時間)的不准確。
TCP的RTT計算
TCP對於此問題也是非常頭疼,於是也不斷進行改進,比如
- 忽略重傳,不把重傳的 RTT 做采樣, 但是當網絡波動產生大延時,所有的包都需要重傳而此時RTO又不會被更新,導致數據包超時時間估算不准確。
-
通過各種參數修正的計算方法:
QUIC的RTT計算
- 首先計算平滑平滑RTT(Smooth RTT)
- 計算平滑RTT和真實的差距(加權移動平均)
- 再經過各種修正最終得出RTO:
- 在Linux下,α = 0.125,β = 0.25, μ = 1,∂ = 4 —— nobody knows why, it just works…
QUIC的包號不會重復,重傳的包采用了新的Packet Number,因此不會產生RTT歧義問題
因此QUIC對於RTT的計算更為准確,預估的超時時間能夠有效防止更多的重傳請求被錯誤地發送回發送端。同時也給予了QUIC網絡更為快速的反應時間,及時通知發送方重傳數據包。
自定義擁塞控制
QUIC 的傳輸控制不再依賴內核的擁塞控制算法,而是實現在應用層上,這意味着我們根據不同的業務場景,實現和配置不同的擁塞控制算法以及參數。比如BRR或者Cubic,如果有興趣可以自行查閱相關算法資料。
在HTTP/3上的應用
- wifi和移動網絡無縫切換
- 更強的網絡安全性(前向安全+全載荷加密)
- 在慢網情況下更高的傳輸速率
QUIC離我們並不遙遠
QUIC早在2012年就已經開始試驗性部署,關於其詳細草案在2015年向IETF提出,終於在2021年五月被接受並於RFC9000中標准化。
chrome://flags/#enable-quic
在chrome瀏覽器中可以選擇是否開啟QUIC實驗性功能,如果服務端支持QUIC協議,就會啟用該協議(大部分都是Google的服務器)。
推薦一個插件可以查看當前網頁支持的連接類型:HTTP/2 and SPDY indicator:
https://chrome.google.com/webstore/detail/http2-and-spdy-indicator/mpbpobfflnpcgagjijhmgnchggcjblin
性能參考(數據來源:騰訊PCG研發部)
https://mp.weixin.qq.com/s/DHvvp6EUR5tDffJqzVir0A
60kb主頁面資源加載速度(單位:毫秒)
弱網環境下的表現
不同丟包率下的下載耗時
從總體上來看,QUIC在網絡環境良好的情況下對於當前HTTP2的提升有限,尤其是首次1-RTT握手的總體時間消耗提升只有15%左右,但是在后續有緩存的情況下建立連接的速度就會快很多,首次響應時間將會大大縮小。
此外在弱網環境下,尤其是丟包率高的情況下QUIC對於性能提升十分驚人,良好的RTO估算機制使得超時重發的估算變得更為精確。同時多個邏輯連接使得文件與文件之間的傳輸互不干擾阻塞,加上更加輕量的頭部和簡單高效的握手方式,因此能夠在弱網環境下取得更為強大的表現。
總結
隨着網絡基礎設施的提升,UDP的傳輸准確率也得到了很大的提升,而TCP卻因為20~60字節的頭部以及可能的頭部擁塞導致一定的效率降低,但是TCP協議已經被大量內置於操作系統內核中,因此只能利用UDP進行定制化。雖然QUIC可能會在小頁面的性能不如TCP,但隨着前端日益復雜化,資源量不斷增大的情況下,使用QUIC替換TCP將能夠顯著提升傳輸速率。
放棄TCP而使用基於UDP的QUIC,有點類似早期x86cpu內置的tss硬件切換不好用,linux系統內核直接使用軟件控制進程上下文切換。
參考文獻
【HTTP/2與HTTP/3 的新特性】https://blog.csdn.net/howgod/article/details/102597450
【QUIC 協議原理淺解】https://www.163.com/dy/article/G5D1ETVH0518R7MO.html
【QUIC加密握手中共享密鑰算法】https://blog.csdn.net/chuanglan/article/details/85106706
【QUIC流量控制】:https://zhuanlan.zhihu.com/p/337175711
【QUIC加密傳輸和握手】https://zhuanlan.zhihu.com/p/301505712
【TCP亂序緩存和重傳的改進方式】https://blog.csdn.net/cws1214/article/details/52430554
【科普:QUIC協議原理分析】https://zhuanlan.zhihu.com/p/32553477
【rfc9000】https://datatracker.ietf.org/doc/html/rfc9000
參考資料
[1]路徑驗證: https://zhuanlan.zhihu.com/p/290694322
轉自https://mp.weixin.qq.com/s/Iw5p9XIKfxb_bZfp_N6n8g