TCP 擁塞控制的演進#

傳輸層已介紹擁塞控制的基本階段(慢啟動、擁塞避免、快速恢復)。本節聚焦「為什麼這些機制會逐步被改良」:傳統演算法用丟包當作擁塞訊號,在高頻寬、高延遲的現代網路中暴露出嚴重的滯後問題,因此催生了以頻寬與延遲為核心的新一代演算法 BBR。理解這條演進線,才能在不同場景選對擁塞控制策略。

經典三階段回顧#

傳統 TCP(以 Reno 為代表)的擁塞視窗(cwnd, congestion window)變化分三階段:

flowchart TD
    A["慢啟動<br/>cwnd 指數增長"] --> B{"達到慢啟動閾值<br/>ssthresh?"}
    B -->|否| A
    B -->|是| C["擁塞避免<br/>cwnd 線性增長"]
    C --> D{"偵測到丟包"}
    D -->|"3 個重複 ACK"| E["快速重傳 + 快速恢復<br/>cwnd 減半"]
    D -->|"逾時 RTO"| F["回到慢啟動<br/>cwnd 重置為 1"]
    E --> C
    F --> A
  • 慢啟動(Slow Start):從很小的視窗開始,每收到一個 ACK 視窗翻倍,呈指數成長,快速探測可用頻寬。
  • 擁塞避免(Congestion Avoidance):達到閾值後改為每個 RTT 增加一個 MSS,呈線性成長,謹慎逼近上限。
  • 快速重傳/快速恢復:收到 3 個重複 ACK 即判定個別封包遺失,立即重傳並把視窗減半,而非粗暴地退回慢啟動。

傳統演算法的滯後問題#

傳統演算法(Reno、CUBIC)有一個共同前提:把「丟包」當成「網路擁塞」的訊號。這在過去的低速網路成立,但在現代網路中產生兩個問題:

  • 訊號來得太晚:路由器佇列要先被塞滿、開始丟包,發送方才會降速。等到丟包發生時,佇列早已堆積,造成「緩衝區膨脹(Bufferbloat)」,延遲飆升。
  • 錯把隨機丟包當擁塞:無線網路、跨國長鏈路本就有隨機丟包,傳統演算法會誤判為擁塞而過度降速,導致明明有頻寬卻跑不滿。
flowchart LR
    A["持續加速發送"] --> B["路由器佇列堆積<br/>延遲開始上升"]
    B --> C["佇列滿、開始丟包"]
    C --> D["發送方才察覺<br/>視窗減半"]
    D -. "反應總是慢一拍" .-> A

傳統演算法的工作點是「填滿緩衝區直到丟包」,而理想工作點其實是「剛好填滿管道、佇列為空」的那一刻。兩者之間的差距,就是延遲與重傳的來源。

BBR:以頻寬與 RTT 為核心#

BBR(Bottleneck Bandwidth and Round-trip propagation time)換了一個思路:不等丟包,而是主動量測瓶頸頻寬與最小 RTT,據此估算管道容量,把發送速率控制在「剛好填滿管道」的工作點。

  • 持續探測瓶頸頻寬(單位時間能送出的最大資料量)與最小往返時延(無佇列堆積時的 RTT)。
  • 用兩者乘積估算管道的理想在途資料量,主動配速(pacing),避免把佇列塞滿。
  • 不把丟包當唯一訊號,因此在有隨機丟包的長肥管道上仍能跑滿頻寬。
flowchart TD
    A["量測瓶頸頻寬 BtlBw"] --> C["估算 BDP = BtlBw × RTprop"]
    B["量測最小往返時延 RTprop"] --> C
    C --> D["以 BDP 為目標配速發送<br/>佇列趨近於空"]
    D --> A

BDP:視窗該開多大#

擁塞控制的本質是回答「在途資料量該維持多少」。理論上限由**頻寬時延乘積(BDP, Bandwidth-Delay Product)**決定:

$$BDP = \text{Bandwidth} \times RTT$$

BDP 代表「管道」在任一瞬間能容納的資料量。若視窗(在途資料量)小於 BDP,頻寬就跑不滿;若大於 BDP,多出來的資料只能堆在路由器佇列裡,徒增延遲與丟包風險。理想的擁塞視窗應趨近於 BDP:

$$cwnd_{\text{ideal}} \approx BDP = \text{Bandwidth} \times RTT$$

舉例:頻寬 $1\ \text{Gbps}$、RTT $100\ \text{ms}$ 的跨國鏈路,BDP 約為 $1 \times 10^9 \times 0.1 / 8 \approx 12.5\ \text{MB}$。這意味著要跑滿這條鏈路,發送方必須維持約 12.5 MB 的在途資料——這也是為何長肥管道(LFN)上需要視窗縮放(Window Scaling)選項,否則 16 位元的視窗欄位根本不夠用。

「長肥管道(Long Fat Network)」指頻寬大、延遲也高的鏈路(如跨洲專線)。它的 BDP 很大,對隨機丟包又特別敏感,正是 BBR 相較 CUBIC 優勢最明顯的場景。

CUBIC vs Reno vs BBR 場景對比#

維度RenoCUBICBBR
擁塞訊號丟包丟包頻寬 + RTT 量測
視窗增長線性三次函數(更積極)依估算的 BDP 配速
對隨機丟包敏感、易誤判降速敏感(略優於 Reno)不敏感,仍能跑滿頻寬
延遲表現易造成緩衝區膨脹易造成緩衝區膨脹主動控制佇列,延遲較低
典型場景教學、老舊系統Linux 預設、一般網際網路長肥管道、跨國傳輸、影音串流
公平性較好較好與丟包型演算法共存時可能偏激進

BBR 並非萬靈丹。在與傳統丟包型演算法共享同一瓶頸時,BBR 可能搶佔較多頻寬而影響公平性;其早期版本在淺緩衝區下也可能造成額外丟包。是否啟用須依實際鏈路與共存流量評估。

Linux 切換擁塞控制演算法#

Linux 透過 tcp_congestion_control 參數選擇演算法,可隨時切換:

# 查看當前使用的擁塞控制演算法(多數發行版預設為 cubic)
sysctl net.ipv4.tcp_congestion_control

# 查看核心可用的演算法
sysctl net.ipv4.tcp_available_congestion_control

# 載入 BBR 模組(若未內建)
modprobe tcp_bbr

# 切換為 BBR(重開機後失效)
sysctl -w net.ipv4.tcp_congestion_control=bbr

# 永久生效:寫入設定檔
echo "net.ipv4.tcp_congestion_control = bbr" >> /etc/sysctl.conf
sysctl -p
BBR 通常需搭配 fq 排隊規則

BBR 依賴精準的封包配速(pacing),建議搭配 fq(Fair Queue)排隊規則以獲得最佳效果:

sysctl -w net.core.default_qdisc=fq

把這行與 tcp_congestion_control = bbr 一起寫入 /etc/sysctl.conf 即可在開機時自動套用。

本節小結#

概念關鍵點
經典三階段慢啟動指數、擁塞避免線性、快速恢復視窗減半
丟包型滯後等佇列塞滿才降速,造成緩衝區膨脹與誤判
BBR量測瓶頸頻寬與最小 RTT,配速到理想工作點
BDP頻寬 × RTT,決定理想在途資料量與視窗大小
演算法選擇一般網路用 CUBIC,長肥管道考慮 BBR