斯坦福 CS144 計算機網(wǎng)絡(luò)播客:TCP 擁塞控制
在計算機網(wǎng)絡(luò)的世界里,數(shù)據(jù)包就像高速公路上飛馳的汽車。當車流量恰到好處時,一切井然有序;但如果車輛瞬間涌入,遠超道路承載能力,就會發(fā)生嚴重的堵車。網(wǎng)絡(luò)中的“堵車”就是我們今天要討論的核心話題—— 擁塞(Congestion) 。
本文將以 Stanford CS 144 課程的核心知識為基礎(chǔ),帶你深入理解 TCP 擁塞控制的來龍去脈,從它是什么、為什么重要,到核心算法 AIMD,再到 TCP 各個版本的演進。
什么是擁塞控制?
首先,我們需要區(qū)分一個非常相似但本質(zhì)不同的概念: 流量控制 (Flow Control) 。流量控制是點對點的問題,它確保發(fā)送方不會因為發(fā)送速度過快而淹沒接收方。這好比兩個人對話,一方說得太快,另一方需要讓他“慢一點”。
而 擁塞控制 (Congestion Control) 則是一個全局性的問題。它關(guān)注的是整個網(wǎng)絡(luò)的承載能力,確保數(shù)據(jù)發(fā)送方不會向網(wǎng)絡(luò)中注入過多的數(shù)據(jù),導(dǎo)致路由器等網(wǎng)絡(luò)設(shè)備不堪重負。它管理者是“所有發(fā)送者”與“網(wǎng)絡(luò)”之間的關(guān)系。
簡單來說,擁塞控制是一種資源管理機制,它的目標是:
- 防止網(wǎng)絡(luò)過載 :避免路由器緩沖區(qū)溢出和大規(guī)模的數(shù)據(jù)包丟失。
- 保證資源公平分配 :讓多個數(shù)據(jù)流(flow)可以相對公平地共享有限的網(wǎng)絡(luò)帶寬。
有趣的是,IP 協(xié)議本身并不提供擁塞反饋,因此,擁塞控制的重任落在了傳輸層,主要由 終端主機 (end-hosts) 上的 TCP 協(xié)議來完成。這正是著名的“端到端原則”的一個經(jīng)典體現(xiàn)。但這又引申出另一個問題:端是不知道網(wǎng)絡(luò)中各節(jié)點的狀態(tài)的,因此想在端到端控制網(wǎng)絡(luò)擁塞是一個充滿挑戰(zhàn)的任務(wù)。
擁塞的危害:從性能下降到網(wǎng)絡(luò)崩潰
當發(fā)送方注入網(wǎng)絡(luò)的數(shù)據(jù)量超過了某個瓶頸路由器的處理能力時,數(shù)據(jù)包就會開始在路由器的緩沖區(qū)中排隊。適度的排隊是正常的,它表明網(wǎng)絡(luò)鏈路得到了充分利用。
然而,當數(shù)據(jù)注入速度持續(xù)高于處理速度時,路由器的緩沖區(qū)最終會被填滿,后續(xù)到達的數(shù)據(jù)包將被無情地丟棄。這就是 丟包 (Packet Loss) 。
丟包會觸發(fā)發(fā)送方的重傳機制,但這會導(dǎo)致一個惡性循環(huán):
- 網(wǎng)絡(luò)已經(jīng)擁塞,發(fā)生丟包。
- 發(fā)送方檢測到丟包,認為數(shù)據(jù)未送達,于是重傳數(shù)據(jù)。
- 重傳的數(shù)據(jù)包進一步加劇了網(wǎng)絡(luò)的擁塞。
在極端情況下,這可能導(dǎo)致 擁塞崩潰 (Congestion Collapse) ——網(wǎng)絡(luò)中充斥著大量重傳的數(shù)據(jù)包,但幾乎沒有任何有效的數(shù)據(jù)能夠成功傳輸?shù)侥康牡兀W(wǎng)絡(luò)吞吐量急劇下降,甚至趨近于零。
理想模型:帶寬時延積 (BDP)
那么,一個理想的發(fā)送方應(yīng)該以多快的速度發(fā)送數(shù)據(jù)呢?TCP 引入了一個名為 擁塞窗口 (Congestion Window) ,通常寫作 cwnd
的變量來控制。它表示在收到確認(ACK)之前,可以發(fā)送到網(wǎng)絡(luò)中的最大數(shù)據(jù)量。
注意:實際的發(fā)送窗口大小是
cwnd
和接收方通告的接收窗口rwnd
中的較小值,即EffectiveWindow = min(cwnd, rwnd)
。
在理想狀態(tài)下,我們希望 cwnd
的大小恰好能“填滿”從發(fā)送方到接收方的網(wǎng)絡(luò)路徑。這個理想值就是 帶寬時延積 (Bandwidth-Delay Product, BDP) 。
BDP 的計算公式為:
它代表了在網(wǎng)絡(luò)路徑中可以容納的最大數(shù)據(jù)量。我們可以用一個簡單的圖來理解:
Sender Receiver
| |
|------------------ The "Pipe" of the Network ---------------|
| |
v v
<------------------ RTT (Round-Trip Time) ------------------->
Data in flight to fill the pipe = Bandwidth * RTT
例如,如果一條鏈路的帶寬是 5 Mbit/s,往返時延(RTT)是 100 ms,那么:
BDP = 5,000,000 bits/s * 0.1 s = 500,000 bits = 62,500 bytes = 62.5 KB
這意味著,為了讓鏈路時刻保持繁忙而又不產(chǎn)生排隊,發(fā)送方需要維持一個 62.5 KB 大小的擁塞窗口。
然而,現(xiàn)實是殘酷的。TCP 發(fā)送方在運行時 無法預(yù)知 瓶頸鏈路的帶寬、網(wǎng)絡(luò)中是否存在其他競爭流量,以及沒有排隊時的最小 RTT。因此,TCP 需要一個動態(tài)的算法來探測并逼近這個理想的 cwnd
值。
AIMD:TCP 擁塞控制的基石
為了動態(tài)調(diào)整 cwnd
,TCP 采用了一種被稱為 加性增窗、乘性減窗 (Additive Increase, Multiplicative Decrease, AIMD) 的經(jīng)典算法。
這個算法的邏輯非常優(yōu)雅,可以概括為“謹慎探測,快速避讓”。
加性增窗 (Additive Increase, AI)
當網(wǎng)絡(luò)狀態(tài)良好時(即數(shù)據(jù)包被成功確認),TCP 會認為網(wǎng)絡(luò)仍有余力,于是緩慢地增加其擁塞窗口,以探測更多的可用帶寬。
具體來說,在擁塞避免階段,每經(jīng)過一個 RTT,如果所有數(shù)據(jù)包都被成功確認,cwnd
就會增加一個 最大段大小 (Maximum Segment Size, MSS) 。
實現(xiàn)細節(jié):為了在每個 RTT 內(nèi)增加 1 個 MSS,TCP 的策略是每收到一個 ACK,cwnd
就增加 MSS / cwnd
。這樣,在一個窗口的數(shù)據(jù)被全部確認后,cwnd
大小正好增加了約 1 個 MSS。詳細解釋一下:
- 目標: 在擁塞避免階段,每經(jīng)過一個 RTT,
cwnd
增加 1 個 MSS。 - 挑戰(zhàn): 發(fā)送方并不知道 RTT 的精確時長。它只能通過“收到一個窗口數(shù)據(jù)的 ACK”來近似一個 RTT 周期。
- 推導(dǎo):
假設(shè)當前 cwnd
是 10 個 MSS。這意味著在一個 RTT 內(nèi),發(fā)送方會發(fā)出 10 個數(shù)據(jù)段,并期望收到 10 個對應(yīng)的 ACK。
我們的目標是在收到這 10 個 ACK 后,cwnd
的總增量為 1 個 MSS。
那么,將這個總增量平均分配到每個到來的 ACK 上,就是最自然的方法。
每個 ACK 應(yīng)該帶來的增量 = 總增量 / ACK 數(shù)量
= 1 MSS / 10
。
這里的 10
正好是當前 cwnd
能容納的段數(shù),即 cwnd / MSS
。
所以,每個 ACK 帶來的增量 = 1 MSS / (cwnd / MSS)
= MSS * MSS / cwnd
(如果 cwnd
以字節(jié)為單位),或者更通俗地寫成 MSS / cwnd
。
- 結(jié)論: 這個公式
cwnd += MSS / cwnd
是一種平滑的、在 RTT 周期內(nèi)實現(xiàn)+1 MSS
目標的工程實現(xiàn)。
乘性減窗 (Multiplicative Decrease, MD)
當網(wǎng)絡(luò)發(fā)生擁塞(即檢測到丟包)時,TCP 必須迅速降低其發(fā)送速率,以緩解網(wǎng)絡(luò)壓力。這個反應(yīng)必須是劇烈的。
TCP 的策略是將 cwnd
直接 減半 (cwnd = cwnd / 2
)。這種大幅度的削減能夠快速為網(wǎng)絡(luò)“降溫”,為恢復(fù)穩(wěn)定創(chuàng)造條件。
“鋸齒”形的 cwnd
變化
AIMD 算法導(dǎo)致 cwnd
的變化呈現(xiàn)出一種非常典型的“鋸齒狀”模式。
這種不斷探測(增加)和快速后退(減半)的機制,使得 TCP 能夠動態(tài)地適應(yīng)網(wǎng)絡(luò)負載的變化,并在多個 TCP 流之間實現(xiàn)相對的公平。
然而,AIMD 也有一個固有的問題:它的吞吐量與 1/√p
(p 是丟包率)成正比,與 1/RTT
成正比。這意味著,RTT 越長的連接(例如跨國連接),其吞吐量會越低,因為它增加 cwnd
的速度更慢。這在一定程度上懲罰了距離較遠的連接。
- 為什么與
1/RTT
成正比? 這個比較好理解。吞吐量的基本定義是窗口大小 / 時間
。在這里,就是cwnd / RTT
。在一個 RTT 時間內(nèi),我們最多可以發(fā)送cwnd
大小的數(shù)據(jù)。因此,RTT 越長,單位時間能發(fā)送的輪次就越少,吞吐量自然就越低。 - 為什么與
1/√p
成正比? (p 是丟包率)
想象一下 AIMD 的“鋸齒”圖。cwnd
從 W/2
增長到 W
時發(fā)生一次丟包。
在一個丟包周期內(nèi),發(fā)送的數(shù)據(jù)包總數(shù)約等于這個鋸齒下方的面積。這個面積大致和 W2
成正比。
丟包率 p
的含義是“平均每發(fā)送 1/p
個包,就會發(fā)生一次丟包”。
因此,一個丟包周期內(nèi)發(fā)送的數(shù)據(jù)包總數(shù)也約等于 1/p
。
所以我們得出 W2
∝ 1/p
,進而 W
∝ 1/√p
。
因為吞吐量正比于平均窗口大小,而平均窗口大小又正比于最大窗口 W
,所以 吞吐量 ∝ 1/√p
。
- 綜合來看: 這個公式揭示了標準 TCP 的一個內(nèi)在特性:在有損網(wǎng)絡(luò)中,丟包率對吞吐量的影響是平方根級別的;而在高延遲網(wǎng)絡(luò)中,RTT 對吞吐量的影響是線性的。這也解釋了為什么長距離(高 RTT)連接在有少量丟包時性能下降如此明顯。
慢啟動:快速啟動,指數(shù)增長
如果一個新的 TCP 連接從一個很小的 cwnd
(例如 1-3 個 MSS) 開始,使用 AIMD 的加性增長,那么它需要很多個 RTT 才能達到一個合理的窗口大小,這個過程太慢了。
為了解決這個問題,TCP 引入了 慢啟動 (Slow-Start) 機制。雖然名字里有“慢”,但它的增長方式實際上是 指數(shù)級 (exponential) 的,非常快。
為什么叫慢啟動呢? 這是一個歷史遺留的命名問題,關(guān)鍵在于“和誰比”。在“慢啟動”算法被發(fā)明之前,TCP 連接一旦建立,發(fā)送方會立即向網(wǎng)絡(luò)中注入接收方窗口 (rwnd
) 允許的全部數(shù)據(jù)。如果網(wǎng)絡(luò)中間某個路由器處理不了這么大的瞬時流量,就會立即導(dǎo)致?lián)砣痛罅縼G包。
相比于那種“瞬間洪水”式的野蠻行為,從 cwnd=1
開始,然后以指數(shù)級增長的方式“慢慢地”將數(shù)據(jù)注入網(wǎng)絡(luò),已經(jīng)是一種非常“溫柔”和“緩慢”的啟動方式了。所以,“慢啟動”的“慢”是相對于過去那種簡單粗暴的實現(xiàn)而言的。
- 初始階段 :連接建立后,
cwnd
被初始化為一個較小的值,例如IW
(Initial Window),通常為 2-4 個 MSS,在現(xiàn)代系統(tǒng)中甚至可以達到 10 個 MSS。 - 指數(shù)增長 :在慢啟動階段,每收到一個 ACK,
cwnd
就會增加 1 個 MSS。這意味著,每經(jīng)過一個 RTT,cwnd
就會 翻倍 (具體可以參考下面的例子)。 - 退出條件 :慢啟動階段會在以下兩種情況之一發(fā)生時結(jié)束:
當 cwnd
的值超過一個預(yù)設(shè)的 慢啟動閾值 (slow-start threshold, ssthresh
) 時,TCP 會從慢啟動階段切換到擁塞避免階段(即 AIMD 的加性增長)。
當檢測到丟包時。
慢啟動的目的是在連接初期快速探測網(wǎng)絡(luò)的可用容量,盡快將 cwnd
提升到一個合理的量級,然后再轉(zhuǎn)入更為謹慎的 AIMD 階段。
MSS (Maximum Segment Size) 的確定
MSS
并不是一個固定的常量,而是在 TCP 連接建立的“三次握手”階段,由通信雙方 協(xié)商 確定的。它的核心目標是盡可能地利用網(wǎng)絡(luò)鏈路的承載能力,同時避免 IP 層的數(shù)據(jù)包分片。
MSS
的大小受到鏈路層最大傳輸單元 MTU (Maximum Transmission Unit)
的限制。其理想計算公式為:
MSS = MTU - IP Header Size - TCP Header Size
協(xié)商過程:
- 在三次握手時,客戶端在第一個
SYN
包中,通過 TCP 選項字段宣告自己的MSS
值。 - 服務(wù)器在返回的
SYN-ACK
包中,也會宣告自己的MSS
值。 - 最終,雙方會選擇兩者中 較小 的
MSS
值作為本次連接的最終MSS
。這確保了任何一方發(fā)送的數(shù)據(jù)段都不會超過對方的處理能力。
生產(chǎn)中的典型值:
- 在最常見的以太網(wǎng)(Ethernet)環(huán)境中,
MTU
通常是 1500 字節(jié)。 - 標準的 IP 頭部大小為 20 字節(jié),TCP 頭部大小也為 20 字節(jié)(不含選項)。
- 因此,最常見的
MSS
值為:1500 - 20 - 20 = 1460 字節(jié)
。 - 如果你在使用撥號上網(wǎng)(PPPoE),它會額外占用 8 字節(jié),導(dǎo)致
MTU
為 1492 字節(jié),MSS
則為 1452 字節(jié)。VPN 或其他隧道技術(shù)也可能進一步減小MTU
,從而影響MSS
的取值。
現(xiàn)代操作系統(tǒng)通常會自動處理這個協(xié)商過程。此外,還有一種更高級的機制叫 路徑 MTU 發(fā)現(xiàn) (Path MTU Discovery, PMTUD) ,它能動態(tài)地發(fā)現(xiàn)從源到目的地的整個路徑上最小的 MTU
,從而更精確地設(shè)置 MSS
,但這已超出了基礎(chǔ)討論的范疇。
ssthresh (Slow Start Threshold) 的確定
與 MSS
不同,ssthresh
是 TCP 發(fā)送方 內(nèi)部維護 的一個狀態(tài)變量,它不對外宣告,也無需協(xié)商。它的作用是作為慢啟動和擁塞避免兩種模式的切換“警戒線”。
初始值: 在一個 TCP 連接剛剛建立時,發(fā)送方對網(wǎng)絡(luò)路徑的狀況一無所知。因此,ssthresh
通常會被設(shè)置為一個 非常大 的值(實際上是系統(tǒng)允許的最大接收窗口大小)。這樣做是為了讓“慢啟動”階段可以盡可能地發(fā)揮作用,不受限制地進行指數(shù)增長,直到第一次擁塞事件發(fā)生。
動態(tài)更新:ssthresh
的值在連接的整個生命周期中是動態(tài)變化的,其更新遵循一個核心原則: 當擁塞發(fā)生時,將 ssthresh
設(shè)置為當前擁塞窗口 cwnd
的一半 。
- 更精確地說,是
ssthresh = max(FlightSize / 2, 2 * MSS)
。FlightSize
指的是已發(fā)送但未被確認的數(shù)據(jù)量,在大多數(shù)情況下約等于cwnd
。 - 與
2 * MSS
取較大值的目的是為了防止ssthresh
變得過小,導(dǎo)致網(wǎng)絡(luò)性能低下。
ssthresh
的本質(zhì)是 TCP 的一種“記憶”: 它記錄了上一次網(wǎng)絡(luò)擁塞發(fā)生時窗口大小的一半,作為下一次探測網(wǎng)絡(luò)容量時的“安全”起點 。
TCP 擁塞控制實戰(zhàn):一個完整例子的剖析
理論總是有些枯燥,讓我們通過一個生動的例子,從頭到尾觀察一個 TCP 連接的 cwnd
是如何在各種算法的支配下動態(tài)變化的。
場景設(shè)定:
- 客戶端 A 向服務(wù)器 B 發(fā)送一個大文件。
- 我們假設(shè) MSS = 1 KB,方便計算。
- 連接剛建立,初始擁塞窗口
cwnd
= 2 MSS (即 2 KB)。 - 初始慢啟動閾值
ssthresh
非常大 (例如 64 KB),在開始階段不會限制cwnd
的增長。
第 1 階段:慢啟動 (Slow-Start)
連接開始,TCP 首要任務(wù)是快速探明網(wǎng)絡(luò)容量。
- RTT 0:
cwnd = 2 KB
。A 發(fā)送 2 個數(shù)據(jù)包。 - RTT 1: A 收到 2 個 ACK。根據(jù)慢啟動規(guī)則(每收到一個 ACK,
cwnd
增加 1 MSS),cwnd
變?yōu)?nbsp;2 KB + 2 * 1 KB = 4 KB
。A 現(xiàn)在可以發(fā)送 4 個數(shù)據(jù)包。 - RTT 2: A 收到 4 個 ACK。
cwnd
變?yōu)?nbsp;4 KB + 4 * 1 KB = 8 KB
。 - RTT 3: A 收到 8 個 ACK。
cwnd
變?yōu)?nbsp;8 KB + 8 * 1 KB = 16 KB
。 - RTT 4: A 收到 16 個 ACK。
cwnd
變?yōu)?nbsp;16 KB + 16 * 1 KB = 32 KB
。
cwnd
的增長軌跡是 2 -> 4 -> 8 -> 16 -> 32
,這是典型的指數(shù)增長。假設(shè)我們的 ssthresh
初始值就是 32 KB。現(xiàn)在 cwnd
達到了 ssthresh
,慢啟動階段結(jié)束。
第 2 階段:擁塞避免 (Congestion Avoidance)
cwnd
已經(jīng)到達了一個相對合理的水平,不能再野蠻增長了,需要進入謹慎的線性探測階段。
- RTT 5:
cwnd = 32 KB
。A 發(fā)送 32 個包并全部收到 ACK。根據(jù)擁塞避免規(guī)則(每輪 RTT 增加 1 MSS),cwnd
變?yōu)?nbsp;32 KB + 1 KB = 33 KB
。 - RTT 6:
cwnd = 33 KB
。一切順利。cwnd
變?yōu)?nbsp;33 KB + 1 KB = 34 KB
。 - ... 這個過程持續(xù)進行,
cwnd
緩慢增加。 - RTT n: 假設(shè)當
cwnd
增長到40 KB
時,網(wǎng)絡(luò)開始出現(xiàn)擁塞,A 發(fā)送的 40 個包中有一個不幸丟失了。
第 3 階段:擁塞事件 - 三次重復(fù)確認 (Reno 模式)
接收方 B 沒有收到丟失的包,但收到了它后面的一些包。于是 B 不斷地對最后一個收到的、有序的包發(fā)送重復(fù) ACK。很快,A 收到了三次重復(fù)的 ACK。
- 擁塞信號: 三次重復(fù)確認。這被認為是輕度擁塞的信號。
- 觸發(fā)機制: 快速重傳 (Fast Retransmit) 與快速恢復(fù) (Fast Recovery)。
- 變量變化:
- A 立即重傳丟失的那個包,不等待超時。
ssthresh
被更新為當前cwnd
的一半:ssthresh = 40 KB / 2 = 20 KB
。這是新的“慢啟動警戒線”。cwnd
不再降為 1,而是直接設(shè)置為新的ssthresh
值:cwnd = 20 KB
。- TCP 直接進入 擁塞避免 階段。
第 4 階段:新一輪的擁塞避免
A 從 cwnd = 20 KB
開始,繼續(xù)進行線性的加性增窗。
- RTT n+1:
cwnd = 20 KB
。發(fā)送 20 個包,一切順利。cwnd
變?yōu)?nbsp;20 KB + 1 KB = 21 KB
。 - RTT n+2:
cwnd = 21 KB
。cwnd
變?yōu)?nbsp;21 KB + 1 KB = 22 KB
。 - ...
cwnd
再次緩慢爬升。
第 5 階段:嚴重擁塞事件 - 超時 (Timeout)
假設(shè)當 cwnd
增長到 26 KB 時,網(wǎng)絡(luò)發(fā)生了嚴重的擁塞,A 發(fā)送的一個數(shù)據(jù)包及其重傳包都丟失了,導(dǎo)致 A 在規(guī)定時間內(nèi)沒有收到任何 ACK,觸發(fā)了 超時 (Timeout) 。
- 擁塞信號: 超時。這被認為是嚴重擁塞的信號。
- 變量變化:
ssthresh
更新為當前cwnd
的一半:ssthresh = 26 KB / 2 = 13 KB
。cwnd
被無情地重置為初始值:cwnd = 2 KB
。- TCP 重新進入 慢啟動 階段。
TCP Congestion Control 經(jīng)典圖例:
解惑:ACK 時鐘:TCP 如何感知“一輪” RTT?
TCP 沒有 一個精確的“RTT 時間窗”來框定“這一輪”的 ACK。它依賴于一個更為優(yōu)雅和強大的機制—— ACK 時鐘 (ACK Clocking) 。
核心思想:事件驅(qū)動,而非時間驅(qū)動
首先要明確,TCP 的擁塞控制算法不是基于一個固定的計時器去判斷“一輪 RTT 是否結(jié)束”。它是一個 事件驅(qū)動 的系統(tǒng),這個核心事件就是 收到 ACK 。
這個機制被稱為“自時鐘 (Self-clocking)”。可以這樣理解:
- 發(fā)送方將一個窗口的數(shù)據(jù)包(例如
cwnd
個)發(fā)送到網(wǎng)絡(luò)中。 - 這些數(shù)據(jù)包在網(wǎng)絡(luò)中傳輸,經(jīng)過路由、排隊,最終到達接收方。
- 接收方在收到數(shù)據(jù)后,會返回 ACK。
- 這些 ACK 同樣需要經(jīng)過網(wǎng)絡(luò)傳輸才能回到發(fā)送方。
- 關(guān)鍵在于: 發(fā)送方只有在收到一個“舊”數(shù)據(jù)包的 ACK 后,才被允許發(fā)送一個“新”的數(shù)據(jù)包。
ACK 返回的速率,從根本上決定了發(fā)送方發(fā)送新數(shù)據(jù)的速率。如果網(wǎng)絡(luò)通暢,ACK 回來得快,發(fā)送方的“時鐘”就走得快,數(shù)據(jù)發(fā)送也快。如果網(wǎng)絡(luò)擁塞,ACK 回來得慢,發(fā)送方的“時鐘”自然就慢下來了,數(shù)據(jù)發(fā)送也隨之減速。
如何實現(xiàn)“每輪 RTT”的規(guī)則?
既然沒有精確的 RTT 時間窗,那“每輪 RTT 增加 1 MSS”這種規(guī)則是如何實現(xiàn)的呢?
TCP 將“ 成功接收到一個 cwnd
大小數(shù)據(jù)的 ACK ”近似地看作“ 經(jīng)過了一輪 RTT ”。
讓我們回到擁塞避免的例子:
- 假設(shè)
cwnd = 32 KB
,MSS = 1 KB
。這意味著發(fā)送方在一個“RTT 周期”內(nèi),期望收到 32 個數(shù)據(jù)包的 ACK。 - “每輪 RTT 增加 1 MSS”的目標,就被分解到了這 32 個 ACK 事件上。
- 每當收到一個 ACK,
cwnd
就增加一小部分:1 MSS / 32
。 - 當所有 32 個 ACK 都順利返回時,
cwnd
的總增量正好是32 * (1 MSS / 32) = 1 MSS
。 - 這正是
cwnd += MSS * MSS / cwnd
公式(當cwnd
以字節(jié)為單位時)的直觀體現(xiàn)。
結(jié)論就是: TCP 不去“判斷”一輪的 ACK 是否全部收到了,而是通過一個 平滑、累加 的方式,將宏觀的“每輪 RTT”的目標,分解到微觀的“每一次 ACK 到達”的事件上。ACK 的到達自然地驅(qū)動著窗口的增長,構(gòu)成了 TCP 擁塞控制算法的脈搏。
解惑:詳解快速重傳與快速恢復(fù)
這兩個機制是 TCP Reno 相對于 TCP Tahoe 的核心改進,它們緊密協(xié)作,旨在更智能地處理輕度網(wǎng)絡(luò)擁塞。
快速重傳 (Fast Retransmit):擁塞的“哨聲”
快速重傳是一種 丟包檢測 機制。它的核心思想是,如果發(fā)送方連續(xù)收到了 三個或以上 的重復(fù) ACK(例如,都要求確認同一個序列號),那么 TCP 就有充分的理由相信,這個序列號對應(yīng)的數(shù)據(jù)包已經(jīng)在網(wǎng)絡(luò)中丟失了。
為什么是三次? 網(wǎng)絡(luò)中的數(shù)據(jù)包有時會因為路由變化等原因發(fā)生 亂序 。收到一兩個重復(fù)的 ACK 可能是由于亂序到達引起的,但收到三個就說明后續(xù)的數(shù)據(jù)包大概率已經(jīng)到達,而中間的那個包丟失的可能性極高。
一旦觸發(fā)快速重傳,發(fā)送方會 立即重傳 那個被認為丟失的數(shù)據(jù)包,而 不必等待超時計時器 (RTO) 到期 。這就是“快速”的含義。它大大縮短了因丟包而造成的傳輸中斷時間。
快速恢復(fù) (Fast Recovery):擁塞后的“手術(shù)刀”
快速恢復(fù)是一個 擁塞控制 策略。它在快速重傳被觸發(fā)后 立即啟動 。
它做什么?(Reno 的經(jīng)典流程)
- 進入狀態(tài): 當快速重傳觸發(fā)時,TCP 進入快速恢復(fù)狀態(tài)。
- 執(zhí)行操作:
- 將
ssthresh
設(shè)置為當前cwnd
的一半(ssthresh = cwnd / 2
)。這步操作被稱為“乘性減窗 (MD)”。 - 將
cwnd
也直接設(shè)置為新的ssthresh
值(cwnd = ssthresh
)。 - 注意: 與 Tahoe 不同,
cwnd
沒有降為 1,并且跳過了慢啟動。
- 維持與退出: TCP 會一直 保持 在快速恢復(fù)狀態(tài),直到收到一個 新的 ACK (即確認了被重傳數(shù)據(jù)的 ACK)。收到這個新 ACK 后,TCP 就會 退出快速恢復(fù) 狀態(tài),并進入標準的 擁塞避免 階段。
存在退出快速恢復(fù)的說法嗎? “快速恢復(fù)”是 TCP 的一個明確的 狀態(tài) 。TCP 在進入這個狀態(tài)后,會執(zhí)行一系列特殊的操作,直到某個條件滿足后才會 退出 該狀態(tài)。
TCP 擁塞控制的演進史
TCP 擁塞控制不是一蹴而就的,而是經(jīng)歷了幾十年的發(fā)展和迭代。其中最重要的幾個版本是 Tahoe、Reno 和 NewReno。
TCP Tahoe (1988) - 開創(chuàng)者
為了解決 1980 年代末期的“擁塞崩潰”問題,TCP Tahoe 首次引入了完整的擁塞控制機制:
- 核心機制 :同時包含了慢啟動和擁塞避免 (AIMD)。
- 丟包檢測 :通過兩種方式推斷丟包:
超時 (Timeout) :發(fā)送數(shù)據(jù)后在規(guī)定時間內(nèi)未收到 ACK。
三次重復(fù)確認 (Triple Duplicate ACKs) :連續(xù)收到三個相同的 ACK,表明接收方收到了后續(xù)的數(shù)據(jù)包,但中間有一個包丟失了。
- 策略 :無論是由超時還是由三次重復(fù)確認檢測到丟包,Tahoe 的處理方式都非常“激進”:
將 ssthresh
設(shè)置為當前 cwnd
的一半。
將 cwnd
重置為 1 個 MSS。
重新進入 慢啟動 階段。
這種“一刀切”的方式雖然有效,但在網(wǎng)絡(luò)只是輕微擁塞(例如只丟了一個包)時,將 cwnd
降至 1 會導(dǎo)致吞吐量的巨大抖動。
TCP Reno (1990) - 優(yōu)化者
TCP Reno 在 Tahoe 的基礎(chǔ)上進行了重要優(yōu)化,它認為“三次重復(fù)確認”和“超時”是兩種不同程度的擁塞信號。
- 快速重傳 (Fast Retransmit) :當收到三次重復(fù)確認時,Reno 不再等待超時,而是立即重傳丟失的那個數(shù)據(jù)段。
- 快速恢復(fù) (Fast Recovery) :這是 Reno 的精髓。在執(zhí)行快速重傳后,Reno 不會 將
cwnd
降至 1,而是:
將 ssthresh
設(shè)置為當前 cwnd
的一半。
將 cwnd
直接設(shè)置為新的 ssthresh
值(而不是 1)。
直接進入 擁塞避免 階段(加性增長),跳過了慢啟動。
這種機制使得在發(fā)生單個丟包時,TCP 的吞吐量下降得不那么劇烈,恢復(fù)得也更快。只有在發(fā)生更嚴重的“超時”事件時,Reno 才會像 Tahoe 一樣將 cwnd
降至 1 并重新開始慢啟動。
TCP NewReno (1996) - 完善者
TCP Reno 在處理單個窗口內(nèi)發(fā)生 多個丟包 的情況時表現(xiàn)不佳。當?shù)谝粋€丟失的包被重傳并確認后,Reno 就會退出快速恢復(fù)狀態(tài),但此時窗口內(nèi)可能還有其他丟失的包,這會導(dǎo)致吞-吐量的多次劇烈下降。
TCP NewReno 優(yōu)化了快速恢復(fù)算法,使其能夠更好地處理單個窗口內(nèi)的多個丟包事件,從而提高了網(wǎng)絡(luò)傳輸?shù)姆€(wěn)定性和效率。
Reno 的“快速恢復(fù)”有一個致命缺陷:它只能很好地處理單個窗口內(nèi)的 一次 丟包。如果一個窗口內(nèi)有多個包丟失了,Reno 就會“精神錯亂”。
Reno 的問題場景:
- 假設(shè)發(fā)送方在一個窗口內(nèi)發(fā)送了 10 個包(#1 到 #10)。其中 #3 和 #5 都丟了。
- 接收方收到 #1, #2, #4。于是不斷發(fā)送針對 #2 的重復(fù) ACK。
- 發(fā)送方收到三次重復(fù) ACK,觸發(fā)快速重傳,重傳 #3,并進入快速恢復(fù)(
cwnd
減半)。 - 假設(shè)重傳的 #3 成功到達。接收方現(xiàn)在可以確認到 #4 了,于是發(fā)回一個對 #4 的新 ACK。
- 問題來了: Reno 收到這個新的 ACK (被稱為部分 ACK,因為它沒有確認所有已發(fā)送的數(shù)據(jù)) 時,會錯誤地認為網(wǎng)絡(luò)擁塞已經(jīng)解除,于是 立即退出快速恢復(fù) 狀態(tài),并將
cwnd
設(shè)置回ssthresh
。 - 但實際上 #5 仍然是丟失的!很快,發(fā)送方又會收到針對 #4 的重復(fù) ACK,不得不再次觸發(fā)快速重傳和快速恢復(fù),導(dǎo)致
cwnd
再一次減半。這使得吞吐量恢復(fù)得非常慢。
NewReno 的優(yōu)化:
- NewReno 在收到部分 ACK 時, 不會立即退出快速恢復(fù) 。
- 它會判斷出“哦,我重傳的包被確認了,但對方?jīng)]有確認到最新的數(shù)據(jù),這說明窗口里肯定還有別的包也丟了”。
- 于是,NewReno 會立即重傳它推斷出的下一個丟失的包(在這個例子里是 #5),并 保持在快速恢復(fù)狀態(tài) ,直到所有在進入該狀態(tài)時“在途”的數(shù)據(jù)包都被確認為止。
- 這樣,NewReno 用一次快速恢復(fù)過程,解決了單個窗口內(nèi)的多次丟包問題,避免了
cwnd
的多次懲罰性下降。
下面是一個簡單的對比表格:
事件 | TCP Tahoe 的響應(yīng) | TCP Reno / NewReno 的響應(yīng) |
超時 (Timeout) |
, |
, |
三次重復(fù)確認 |
, |
, |
總結(jié)與展望
TCP 擁塞控制是互聯(lián)網(wǎng)能夠穩(wěn)定運行的基石之一,它體現(xiàn)了分布式系統(tǒng)中簡單而強大的設(shè)計哲學(xué)。其核心目標可以總結(jié)為:
- 高吞吐量 (High Throughput) :高效利用網(wǎng)絡(luò)資源。
- 公平性 (Fairness) :確保不同數(shù)據(jù)流能公平共享帶寬。
- 快速響應(yīng) (Responsiveness) :快速適應(yīng)網(wǎng)絡(luò)條件的變化。
- 分布式控制 (Distributed Control) :無需中央?yún)f(xié)調(diào),易于擴展。
從 Tahoe 的誕生,到 Reno 和 NewReno 的不斷完善,TCP 擁塞控制的核心思想——基于丟包信號的 AIMD——在很長一段時間內(nèi)都主導(dǎo)著互聯(lián)網(wǎng)。
然而,時代在進步。在當今高帶寬、高延遲(長肥網(wǎng)絡(luò))和無線網(wǎng)絡(luò)環(huán)境下,僅依賴丟包作為擁塞信號已顯不足。因此,更新的擁塞控制算法,如基于延遲的 TCP Vegas 、Google 開發(fā)的 BBR (Bottleneck Bandwidth and Round-trip propagation time) 以及目前 Linux 系統(tǒng)默認的 CUBIC,正在不斷涌現(xiàn)和部署,以適應(yīng)更加復(fù)雜的網(wǎng)絡(luò)環(huán)境。
但無論技術(shù)如何演進,理解 TCP Tahoe 和 Reno 所奠定的基礎(chǔ),依然是每一位網(wǎng)絡(luò)學(xué)習(xí)者和工程師的必修課。