為什麼 TCP 協議有效能問題

karspb發表於2021-09-09

為什麼這麼設計(Why’s THE Design)是一系列關於計算機領域中程式設計決策的文章,我們在這個系列的每一篇文章中都會提出一個具體的問題並從不同的角度討論這種設計的優缺點、對具體實現造成的影響。如果你有想要了解的問題,可以在文章下面留言。

TCP 協議可以說是今天網際網路的基石,作為可靠的傳輸協議,在今天幾乎所有的資料都會透過 TCP 協議傳輸,然而 TCP 在設計之初沒有考慮到現今複雜的網路環境,當你在地鐵上或者火車上被斷斷續續的網路折磨時,你可能都不知道這一切可能都是 TCP 協議造成的。本文會分析 TCP 協議為什麼在弱網環境下有嚴重的效能問題

注:本文的分析基於  中定義的 TCP 協議,從 RFC 793 釋出至今已經過了將近 40 年,期間多個狀態為 Proposed Standard 的非強制性 RFC 都對 TCP 協議進行了修訂,嘗試最佳化 TCP 協議的效能,例如:選擇性 ACK(Selective ACK, SACK)、虛假超時剖析(Forward RTO, F-RTO) 和 TCP 快開啟(TCP Fast Open, TFO),最新版本的 Linux 中已經包含了這些 RFC 的實現。

底層的資料傳輸協議在設計時必須要對頻寬的利用率和通訊延遲進行權衡和取捨,所以想要解決實際生產中的全部問題是不可能的,TCP 選擇了充分利用頻寬,為流量而設計,期望在儘可能短的時間內傳輸更多的資料

在網路通訊中,從傳送方發出資料開始到收到來自接收方的確認的時間被叫做往返時延(Round-Trip Time,RTT)。

弱網環境是丟包率較高的特殊場景,TCP 在類似場景中的表現很差,當 RTT 為 30ms 時,一旦丟包率達到了 2%,TCP 的吞吐量就會下降 89.9%,從下面的表中我們可以看出丟包對 TCP 的吞吐量極其顯著的影響:

RTT TCP 吞吐量 TCP 吞吐量(2% 丟包率)
0 ms 93.5 Mbps 3.72 Mbps
30 ms 16.2 Mbps 1.63 Mbps
60 ms 8.7 Mbps 1.33 Mbps
90 ms 5.32 Mbps 0.85 Mbps

本文將分析在弱網環境下(丟包率高)影響 TCP 效能的三個原因:

  • TCP 的擁塞控制演算法會在丟包時主動降低吞吐量;

  • TCP 的三次握手增加了資料傳輸的延遲和額外開銷;

  • TCP 的累計應答機制導致了資料段的傳輸;

在上述的三個原因中,擁塞控制演算法是導致 TCP 在弱網環境下有著較差表現的首要原因,三次握手和累計應答兩者的影響依次遞減,但是也加劇了 TCP 的效能問題。

擁塞控制

TCP 擁塞控制演算法是網際網路上主要的擁塞控制措施,它使用一套基於線増積減(Additive increase/multiplicative decrease,AIMD)的網路擁塞控制方法來控制擁塞,也是造成 TCP 效能問題的主要原因。

第一次發現的網際網路擁塞崩潰是在 1986 年,NSFnet 階段一的骨幹網的處理能力從 32,000bit/s 降到了 40bit/s,該骨幹網的處理能力直到 1987 和 1988 年,TCP 協議實現了擁塞控制之後才得到解決。正是因為發生過網路阻塞造成的崩潰,所以 TCP 的擁塞控制演算法就認為只要發生了丟包當前網路就發生了擁堵,從這一假設出發,TCP 最初的實現 Tahoe 和 Reno 就使用了慢啟動和擁塞避免兩個機制實現擁塞控制,本節中對擁塞控制的分析就是基於這個版本的實現。


圖 1 - TCP 擁塞控制

每一個 TCP 連線都會維護一個擁塞控制視窗(Congestion Window),它決定了傳送方同時能向接收方傳送多少資料,其作用主要有兩個:

  1. 防止傳送方向接收方傳送了太多資料,導致接收方無法處理;

  2. 防止 TCP 連線的任意一方向網路中傳送大量資料,導致網路擁塞崩潰;

除了擁塞視窗大小(cwnd)之外,TCP 連線的雙方都有接收視窗大小(rwnd),在 TCP 連線建立之初,傳送方和接收方都不清楚對方的接收視窗大小,所以通訊雙方需要一套動態的估算機制改變資料傳輸的速度,在 TCP 三次握手期間,通訊雙方會透過 ACK 訊息通知對方自己的接收視窗大小,接收視窗大小一般是頻寬延遲乘積(Bandwidth-delay product, BDP)決定的,不過在這裡我們就不展開介紹了。

客戶端能夠同時傳輸的最大資料段的數量是接收視窗大小和擁塞視窗大小的最小值,即 min(rwnd, cwnd)。TCP 連線的初始擁塞視窗大小是一個比較小的值,在 Linux 中是由 TCP_INIT_CWND 定義的

/* TCP initial congestion window as per rfc6928 */ #define TCP_INIT_CWND 10

C

初始擁塞控制視窗的大小從出現之後被多次修改,幾個名為 Increasing TCP’s Initial Window 的 RFC 文件:RFC2414、RFC3390 和 RFC6928 分別增加了 initcwnd 的值以適應不斷提高的網路傳輸速度和頻寬。

TCP 協議使用慢啟動閾值(Slow start threshold, ssthresh)來決定使用慢啟動或者擁塞避免演算法:

  • 當擁塞視窗大小小於慢啟動閾值時,使用慢啟動;

  • 當擁塞視窗大小大於慢啟動閾值時,使用擁塞避免演算法;

  • 當擁塞視窗大小等於慢啟動閾值時,使用慢啟動或者擁塞避免演算法;


圖 2 - TCP 的慢啟動

如上圖所示,使用 TCP 慢啟動時,傳送方每收到一個響應方的 ACK 訊息,擁塞視窗大小就會加一。當擁塞視窗大小大於慢啟動閾值時,就會使用擁塞避免演算法:

  1. 線性增長:每收到一個 ACK,擁塞視窗大小會加一;

  2. 積式減少:當傳送方傳送的資料包丟包時,慢啟動閾值會設定為擁塞視窗大小的一半;

TCP 的早期實現 Tahoe 和 Reno 在遇到丟包時會將擁塞控制大小重置為初始值,由於擁塞視窗大小小於慢啟動閾值,所以重新進入慢啟動階段。

如果 TCP 連線剛剛建立,由於 Linux 系統的預設設定,客戶端能夠同時傳送 10 個資料段,假設我們網路的頻寬是 10M,RTT 是 40ms,每個資料段的大小是 1460 位元組,那麼使用 BDP 計算的通訊雙方視窗大小上限應該是 35,這樣才能充分利用網路的頻寬:


rwndmax=10×106bit/s⋅40×10−3s1460∗8bit=35rwndmax=10×106bit/s⋅40×10−3s1460∗8bit=35


然而擁塞控制視窗的大小從 10 漲到 35 需要 2RTT 的時間,具體的過程如下:

  1. 傳送方向接收方傳送 initcwnd = 10 個資料段(消耗 0.5RTT);

  2. 接收方接收到 10 個資料段後向傳送方傳送 ACK(消耗 0.5RTT);

  3. 傳送方接收到傳送方的 ACK,擁塞控制視窗大小由於 10 個資料段的成功傳送 +10,當前擁塞控制視窗大小達到 20;

  4. 傳送方向接收方傳送 20 個資料段(消耗 0.5RTT);

  5. 接收方接收到 20 個資料段後向傳送方傳送 ACK(消耗 0.5RTT);

  6. 傳送方接收到傳送方的 ACK,擁塞控制視窗大小由於 20 個資料段的成功傳送 +20,當前擁塞控制視窗大小達到 40;

從 TCP 三次握手建立連線到擁塞控制視窗大小達到假定網路狀況的最大值 35 需要 3.5RTT 的時間,即 140ms,這是一個比較長的時間了。

早期網際網路的大多數計算裝置都透過有線網路連線,出現網路不穩定的可能性也比較低,所以 TCP 協議的設計者認為丟包意味著網路出現擁塞,一旦發生丟包,客戶端瘋狂重試就可能導致網際網路的擁塞崩潰,所以發明了擁塞控制演算法來解決該問題。

但是如今的網路環境更加複雜,無線網路的引入導致部分場景下的網路不穩定成了常態,所以丟包並不一定意味著網路擁堵,如果使用更加激進的策略傳輸資料,在一些場景下會得到更好的效果


作者公眾號:一起寫程式

原文連結:






來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2001/viewspace-2797872/,如需轉載,請註明出處,否則將追究法律責任。

相關文章