TCP 和 QUIC

title

當前互聯網幾乎全部的 HTTP 通訊都由 TCP/IP 來承載,但 TCP 爲了可靠性而犧牲性能被不少人所詬病。再到 QUIC 及 HTTP over QUIC 的發佈,通過改進的 UDP 彷彿正在爲將來替代 TCP 作準備。雖然 HTTP/3 尚未大面積普及,但愈來愈多的公司及我的也在不斷嘗試在傳輸層提高網絡鏈接的效率。這篇文章主要討論 TCP 及其問題和 QUIC 協議相關的內容。算法

關於 TCP 及其性能評估

傳輸層 TCP 協議爲 HTTP 提供了一條可靠的比特傳輸管道。當 HTTP 傳輸一條報文時,會以流的形式將報文數據的內容經過一條打開的 TCP 鏈接按序輸送。TCP 收到數據流後,會將數據流砍成數據塊(段),並將其封裝在 IP 分組中,數據格式以下圖:服務器

DATA

傳輸協議在設計時須要對各類條件和場景進行策略的權衡取捨,TCP 爲了可靠性設計了一系列的規則好比三次握手,四次揮手等等。下面咱們來展開討論下 TCP 鏈接細節以及性能問題。markdown

創建鏈接

TCP 慢啓動擁塞控制

爲了防止 TCP 鏈接一開始向對方發送大量數據而致使網絡擁塞崩潰,TCP 鏈接通常會隨着時間進行自我調諧,起初限制鏈接的最大速度,若是數據傳輸成功,則會提升傳輸速度,這種自我調諧被稱爲 TCP 慢啓動,用於防止因特網的忽然過載和擁塞。網絡

TCP 鏈接控制速度大小則經過擁塞控制窗口,在創建之初雙方會協商傳輸的數據大小,而後發送方的擁塞控制窗口大小會跟隨接收方的響應而變化,若是發送成功則線性增加,若是丟包則減半。併發

三次握手

爲何要三次握手

3-way

創建 TCP 鏈接時,須要通訊雙方首先要對 Socket,序列號以及窗口大小信息達成共識,Socket 是由互聯網地址標識符和端口組成,窗口大小用來作流控制,序列號則是用來追蹤通訊發起方發送的數據包序號,接收方能夠經過序號來向發送方確認某個數據包的成功接收。負載均衡

歷史鏈接判斷socket

咱們知道一個客戶端是能夠重複的與同一臺服務器創建鏈接進行通訊的,RFC 793 中就指出三次握手的首要緣由就是阻止歷史重複鏈接致使的混亂問題。tcp

The principle reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion.ide

假如 TCP 鏈接爲兩次握手,那服務器方將只能選擇接受或拒絕發送方屢次的請求,它並不清楚哪個是過時的鏈接。高併發

爲解決這個問題,TCP 選擇了三次握手並在鏈接引入了 RST 這一控制消息,發送方在收到接收方的 ACK 確認號後,會判斷是否爲歷史鏈接,若是是會發送 RST 控制消息停止此次鏈接。若是不是則會繼續完成握手流程。

序列號控制

因爲網絡具備很大的不肯定性,可能會致使下面的問題:

  • 數據包被髮送方屢次發送形成的數據重複
  • 數據包在傳輸過程當中被路由或者其餘節點丟失
  • 數據包到達接收方可能沒法按照發送順序

因此 TCP 在數據包中加入了 SEQ 字段,有了數據包的序列號則能夠:

  • 接收方能夠經過序列號對重複的數據包去重
  • 發送方會在對應數據包未被 ACK 時重複發送
  • 接收方能夠根據數據包的序列號對它們進行重排

避免數據丟失形成的循環

咱們再假設另一個場景,發送方發送了一個鏈接請求分組,對方收到後併發送確認應答分組,若是這時候認定鏈接創建,接收方會開始給發送方傳輸數據分組,但在應答分組丟失的狀況下發送方不知道是否創建好鏈接,將會忽略任何數據分組直到應答分組收到爲止。而接收方在發出的分組超時後會重複發送,這也造成了某種意義的死循環。

性能損耗

三次握手須要雙方一共發送 3 個 TCP 分組(一般是 40 ~ 60)個字節,那麼三次則是 120 字節到 180 字節,但大多數 HTTP 請求都不會攜帶大量的數據,小的 HTTP 事務極可能會在 TCP 創建上花費 50% 或者更多時間,同時也會增長不少字節的額外開銷。

延遲確認與重傳機制

因爲咱們沒法確保發送的數據分組必定能被對方收到,因此 TCP 實現了本身的確認機制來確保數據的成功傳輸。

每一個 TCP 段都有一個序列號和數據完整性校驗和 (checksum),接收者收到無缺的段時都會向發送者回送小的確認分組。若是發送者沒有在指定窗口時間(一般有 100 ~ 200 ms)內收到確認信息時,發送者就認爲分組已被破壞或損毀,並重發數據。

ACK 的方式很容易保證消息的順序性,但在某些狀況下會致使發送方重傳已經接收的數據:好比發送方發送四個數據段,後三個傳輸成功但第一個失敗了,因爲 ACK 語義是當前數據段前的所有數據段都已經被接收和處理,因此接收方沒法發送 ACK 消息,發送方看沒有 ACK,全部數據段對應的計時器就會超時並從新傳輸數據。在丟包嚴重的網絡下,這種重傳機制會形成大量的帶寬浪費。

斷開鏈接

四次揮手

4-way

如圖所示,TCP 鏈接的拆除須要通過 four-way handshake,客戶端或服務器都可主動發起揮手動做。

爲何創建鏈接是三次握手而斷開時是四次呢?

創建鏈接時,服務器開始處於 LISTEN 狀態,收到 SYN 報文後能夠把 ACK 和 SYN 放在一個報文回送給客戶端。 但關閉鏈接時,服務器收到對方的 FIN 報文時,僅僅表示對方再也不發數據了但還能接收數據,本身未必所有數據都已經發送給對方,因此此時能夠即刻關閉,也能夠發送一些數據後再發送 FIN 報文給對方關閉鏈接,因此通常 ACK 和 FIN 會分開發送。

TIME_WAIT

當 TCP 端點關閉 TCP 鏈接時,會在內存中維護一個小的控制塊,用來記錄最近所關閉鏈接的 IP 地址和端口號。這類信息只會維持一小段時間,一般是所估計的最大分段使用期的兩倍 2MSL, 以確保這段時間內不會建立相同地址和端口號的新鏈接。另外 TIME_WAIT 的存在也是爲了保證 TCP 雙工可靠的終止,雖然四次揮手發送和協調完畢,但咱們必須假設網絡是不可靠的,沒法保證最後發送的 ACK 報文必定會被對方收到,所以對方處於 LAST_ACK 狀態下的 socket 可能會由於超時沒收到 ACK 報文而重發 FIN 報文,因此這個 TIME_WAIT 能夠用來重發可能丟失的 ACK 報文。

TIME_WAIT 時間是動態可配的,因此須要咱們本身設定策略來選擇 MSL 時間。

HTTP/2 的優化

HTTP/2 基於 Google 推行的 SPDY,專一於性能,最大的目標是用戶和網站間只須要單個鏈接。因此增長了二進制分幀,多路複用等強大的功能。同時 HTTP/2 協議也是最大限度的兼容 HTTP/1.x 的,request 模型,scheme 並無發生變化,不識別 HTTP/2 的代理服務器也能夠將請求降級爲 HTTP/1.x.

HTTP/2 並無改變 HTTP/1.x 的語義,只是在應用層使用二進制分幀的方式傳輸。所以引入了新的通訊單位:幀,消息,流。HTTP/2 是一個完全的二進制協議,頭信息和數據包都是二進制的,統稱爲「幀「。

http-2

幀的類型包括了 DATA 幀 (用來承載請求或相應的內容,包括 Pad Length, Data, Padding 等字段),HEADERS 幀 (用來承載 start line + header 的 HTTP Header 幀), PRIORITY 幀 (stream 流發送方指定了建議優先級),PING 幀(用做心跳檢測及計算 RTT 往返時間), GOAWAY 幀 (用於啓動鏈接關閉或發出嚴重錯誤信號) 等等。

分幀使得服務器單位時間接收到的請求數變多,能夠提升併發數,也爲多路複用提供了底層支持。

多路複用

多路複用就是在一個 TCP 鏈接中能夠存在多個 stream 流。

connection

多個 stream 同時存在時是無序的,因此須要 streamID 來標識,stream ID 使用無符號的 31 位整數標識,客戶端發起的流必須使用奇數編號,服務器發起的則必須使用偶數編號,流標識符零 (0x0) 用於發送控制消息,並不能創建新的 stream .

數據流在發送中的任意時刻,客戶端和服務器均可以發送信號 (RST_STREAM 幀) 來取消這個數據流。

多路複用有效的解決多個連接時慢啓動,重複 TCP 握手耗時的問題使得 TCP 效率更高。

HTTP/2 其餘特性和存在的問題

HTTP/2 使用 HPACK 來開啓頭部壓縮,另外客戶端和服務器同時維護一張頭信息表,全部字段會存入該表,生成索引號,一樣字段再發送時只發送索引號便可。HTTP/1.1 平均響應頭有 500 個字節左右,而 HTTP/2 平均只有 20 多個字節,只有之前的 4% 左右。

HTTP/2 還增長了服務端推送可讓服務端主動把資源文件推送給客戶端。

但 HTTP/2 依舊存在一些問題好比:

  • 建連延時

TCP 鏈接依舊須要三次握手,消耗 1.5 個 RTT,再加上 TLS 握手,時間耗費將會更長。

RTT(Round-Trip Time):往返時延。表示從發送端發送數據開始,到發送端收到來自接收端的確認(接收端收到數據後便當即發送確認),總共經歷的時延。

  • 隊頭阻塞沒有完全解決

HTTP/2 在丟包時,整個 TCP 都要等待重傳,會阻塞該 TCP 鏈接中的全部請求。

  • 多路複用致使的服務器壓力

多路複用沒有限制同時請求數,可能會致使許多請求的短暫爆發,致使 QPS 暴增。

  • Timeout

網絡帶寬和服務器資源有限,多個並行的流會致使資源被稀釋,而後出現超時狀況。

QUIC

QUIC(Quick UDP Internet Connections)協議基於 UDP 協議,它比較出色的解決了隊頭阻塞的問題,HTTP/3 也名 HTTP over QUIC,集成了 QUIC,TLS1.3 等等。

http3

QUIC 優點

主要特色爲:

  1. 改進的擁塞控制,可靠傳輸
  2. 快速握手
  3. 多路複用
  4. 鏈接遷移

改進的擁塞控制,可靠傳輸

  • 應用層面能實現不一樣的擁塞控制算法

一個應用程序的不一樣鏈接能支持配置不一樣的擁塞控制,應用程序不須要停機和升級就能實現擁塞控制的變動,能夠針對不一樣業務,網絡和不一樣的 RTT 來使用不一樣的擁塞控制算法

  • 單調遞增的 Packet Number 代替了 TCP 的 seq

每一個 Packet Number 都嚴格遞增且惟一,而 TCP 重傳存在二義性,從新發送時數據包中標識符都不變。

  • 不容許丟棄確認過的 Packet

QUIC 中的 ACK 包含了與 TCP 中等價的信息,但 QUIC 不容許 ACK 確認過的 Packet 被丟棄。這樣不只能夠簡化發送端與接收端的實現難度,還能夠減小發送端的內存壓力。

  • 前向糾錯能力 FEC

FEC 中,QUIC 數據幀的數據混合原始數據和冗餘數據,來確保不管到達接收端的 n 次傳輸內容是什麼,接收端都可以恢復全部 n 個原始數據包。

  • 更多 ACK 塊和增長 ACK Delay 時間

QUIC 能夠同時提供 256 個 ACK Block,在重排序時相對於 TCP 更有彈性,在丟包率比較高的網絡下能夠提高網絡的恢復速度,減小重傳量。

同時在計算 RTT 時會把接收到包到發送 ACK 的這段時間(ACK Delay)計算進去。

  • 基於 stream 和 connection 級別的流量控制

QUIC 的流量控制相似 HTTP/2,在 Connection 和 Stream 級別提供了兩種流量控制。

快速握手

因爲 QUIC 基於 UDP,因此只需花費 0~1 個 RTT 就能夠創建鏈接。

多路複用

QUIC 是爲多路複用設計的,攜帶個別流的數據包丟失時,一般只會影響該流,QUIC 鏈接上的多個 stream 之間並無依賴,也不會有底層協議限制。

這也很大程度上緩解了隊頭阻塞的影響。

鏈接遷移

TCP 經過 ip, 端口號來肯定鏈接, 而 QUIC 則經過 ConnectionID (64 bit) 來區別不一樣鏈接。主要 Connection ID 不變鏈接就不須要從新創建。

面臨的問題

QUIC 對於弱網環境的優化是明顯的,可是也因爲各類緣由面臨着一些問題:

NAT 設備端口記憶問題

對於基於 TCP 的 HTTP(S) 傳輸,NAT 設備能夠根據 TCP 報文頭的 SYN/FIN 狀態來了解通訊開始結束狀態,對應記憶 NAT 映射的開始和結束,可是 UDP 中不存在 SYN/FIN 狀態位,若是 NAT 設備的記憶短於用戶會話時間則用戶會話會被中斷。

NAT 設備禁用 UDP

在一些網絡環境下(好比校園網),UDP 協議會被路由器等中間網絡設備禁止(包括我國的運營商= =), 這時客戶端會直接降級, 選擇備選通道。

負載均衡

QUIC 客戶端存在網絡制式切換,就算是同一個移動機房,可能第一次業務請求會落到 A 服務器,後續再鏈接則落到 B 服務器,重複走握手流程。

更多 QUIC 的內容能夠參照 chromium/quic 官方文檔


以上爲這篇文章所有內容,部分資料參考自:

也歡迎關注公衆號: RannDev

wechat
相關文章
相關標籤/搜索