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 --> ABDP:視窗該開多大#
擁塞控制的本質是回答「在途資料量該維持多少」。理論上限由**頻寬時延乘積(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 場景對比#
| 維度 | Reno | CUBIC | BBR |
|---|---|---|---|
| 擁塞訊號 | 丟包 | 丟包 | 頻寬 + 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 -pBBR 通常需搭配 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 |