理解TCP/IP傳輸層擁塞控制算法
通過本文你將了解到以下內容:
- 擁塞控制概念以及其背景
- 流量控制和擁塞控制的區(qū)別與聯(lián)系
- 擁塞控制主要過程詳解
伙伴們認真學習一下,讓offer來得更猛烈些吧!

0x01.TCP/IP協(xié)議棧簡要回顧
來看下維基百科對TCP/IP的一些介紹,筆者做了少量的修改來確保語句通順:
互聯(lián)網協(xié)議套件是一個網絡通信模型以及整個網絡傳輸協(xié)議家族,由于該協(xié)議簇包含兩個核心協(xié)議:TCP(傳輸控制協(xié)議)和IP(網際協(xié)議),因此常被通稱為 TCP/IP協(xié)議族 。
TCP/IP協(xié)議對于數據應該如何封裝、定址、傳輸、路由以及在目的地如何接收等基本過程都加以標準化。它將通信過程 抽象化為四個層次 ,并采取協(xié)議堆棧的方式分別實現出不同通信協(xié)議,實際使用的四層結構是七層OSI模型的簡化。
我們可以看到TCP/IP協(xié)議棧是一個簡化的分層模型,是互聯(lián)網世界連接一切的基石,一起來看一張 七層模型vs四層模型 的簡圖:

TCP/IP協(xié)議棧過于龐大,篇幅所限本文不再做更多細節(jié)的描述。
0x02.流量控制和擁塞控制
TCP是一種面向連接的、可靠的、全雙工傳輸協(xié)議,前輩們寫了很多復雜的算法為其保駕護航,其中有一組像 海爾兄弟一樣 的算法: 流量控制和擁塞控制 ,這也是我們今天的主角。

2.1 流量控制簡介
流量控制和擁塞控制從漢語字面上并不能很好的區(qū)分,本質上這一對算法既有區(qū)別也有聯(lián)系。
維基百科對于 流量控制Flow Control 的說明:
In data communications, flow control is the process of managing the rate of data transmission between two nodes to prevent a fast sender from overwhelming a slow receiver.
It provides a mechanism for the receiver to control the transmission speed, so that the receiving node is not overwhelmed with data from transmitting node.
翻譯一下:
在數據通信中,流量控制是管理兩個節(jié)點之間數據傳輸速率的過程,以防止快速發(fā)送方壓倒慢速接收方。
它為接收機提供了一種控制傳輸速度的機制,這樣接收節(jié)點就不會被來自發(fā)送節(jié)點的數據淹沒。
可以看到流量控制是通信雙方之間約定數據量的一種機制,具體來說是借助于TCP協(xié)議的確認ACK機制和窗口協(xié)議來完成的。
窗口分為 固定窗口和可變窗口 ,可變窗口也就是 滑動窗口 ,簡單來說就是通信雙方根據接收方的接收情況動態(tài)告訴發(fā)送端可以發(fā)送的數據量,從而實現發(fā)送方和接收方的數據 收發(fā)能力匹配 。
這個過程非常容易捕捉,使用 wireshark在電腦上抓或者tcpdump在服務器 上抓都可以看到,大白在自己電腦上用wireshark抓了一條:

我們以兩個主機交互來簡單理解流量控制過程:

接收方回復報文頭部解釋:

圖中RcvBuffer是接收區(qū)總大小,buffered data是當前已經占用的數據,而free buffer space是當前剩余的空間,rwnd的就是free buffer space區(qū)域的字節(jié)數。
HostB把當前的rwnd值放入報文頭部的接收窗口receive window字段中,以此通知HostA自己還有多少可用空間, 而HostA則將未確認的數據量控制在rwnd值的范圍內,從而避免HostB的接收緩存溢出。
可見 流量控制是端到端微觀層面的數據策略 ,雙方在數據通信的過程中并不關心鏈路帶寬情況,只關心通信雙方的接收發(fā)送緩沖區(qū)的空間大小,可以說是個 速率流量匹配策略 。
流量控制就像現實生活中物流領域中A和B兩個倉庫,A往B運送貨物時只關心倉庫B的剩余空間來調整自己的發(fā)貨量,而不關心高速是否擁堵。
2.2 擁塞控制的必要性
前面我們提到了微觀層面點到點的流量控制,但是我們不由地思考一個問題: 只有流量控制夠嗎?答案是否定的。
我們還需要一個宏觀層面的控去避免網絡鏈路的擁堵,否則再好的端到端流量控制算法也面臨 丟包、亂序、重傳 問題,只能造成惡性循環(huán)。

我們從一個更高的角度去看 大量TCP連接復用網絡鏈路 的通信過程:

所以擁塞控制和每一條端到端的連接關系非常大,這就是流量控制和擁塞控制的深層次聯(lián)系,所謂每一條連接都順暢那么整個復雜的網絡鏈路也很大程度是通暢的。

在展開擁塞控制之前我們先考慮幾個問題:
- 如何感知擁塞
TCP連接的發(fā)送方在向對端發(fā)送數據的過程中,需要根據當前的網絡狀況來調整發(fā)送速率,所以感知能力很關鍵。
在TCP連接的發(fā)送方一般是 基于丟包 來判斷當前網絡是否發(fā)生擁塞,丟包可以由 重傳超時RTO和重復確認 來做判斷。

- 如何利用帶寬
誠然擁塞影響很大,但是一直低速發(fā)包對帶寬利用率很低也是很不明智的做法,因此要充分利用帶寬就不能過低過高發(fā)送數據,而是保持在一個動態(tài)穩(wěn)定的速率來提高帶寬利用率,這個還是比較難的, 就像茫茫黑夜去躲避障礙物 。
- 擁塞時如何調整
擁塞發(fā)生時我們需要有一套應對措施來防止擁塞惡化并且恢復連接流量,這也是擁塞控制算法的精要所在。
0x03.理解擁塞控制
前面我們提了擁塞控制的必要性以及重要問題,接下來一起看下前輩們是如何設計實現精彩的擁塞控制策略的吧!
3.1 擁塞窗口cwnd
從流量控制可以知道接收方在header中給出了rwnd接收窗口大小,發(fā)送方不能自顧自地按照接收方的rwnd限制來發(fā)送數據,因為網絡鏈路是復用的,需要考慮當前鏈路情況來確定數據量,這也是我們要提的另外一個變量cwnd,筆者找了一個關于rwnd和cwnd的英文解釋:
Congestion Window (cwnd) is a TCP state variable that limits the amount of data the TCP can send into the network before receiving an ACK.
The Receiver Window (rwnd) is a variable that advertises the amount of data that the destination side can receive.
Together, the two variables are used to regulate data flow in TCP connections, minimize congestion, and improve network performance.
筆者在rfc5681文檔中也看到cwnd的定義:

這個解釋指出了cwnd是在發(fā)送方維護的,cwnd和rwnd并不沖突,發(fā)送方需要結合rwnd和cwnd兩個變量來發(fā)送數據,如圖所示:

cwnd的大小和MSS最大數據段有直接關系,MSS是TCP報文段中的數據字段的最大長度,即MSS=TCP報文段長度-TCP首部長度。
3.2 擁塞控制基本策略
擁塞控制是一個動態(tài)的過程 ,它既要提高帶寬利用率發(fā)送盡量多的數據又要避免網絡擁堵丟包RTT增大等問題,基于這種高要求并不是單一策略可以搞定的,因此TCP的擁塞控制策略實際上是 分階段分策略的綜合過程 :

注: 有的版本的TCP算法不一定沒有快速恢復階段
如圖為典型的包含4個策略的擁塞控制:

如圖為發(fā)生超時重傳RTO時的過程:

3.3 TCP算法常見版本
實際上TCP算法有很多版本,每個版本存在一些差異,在這里簡單看一下維基百科的介紹:
- 算法命名規(guī)則
TCP+算法名的命名方式最早出現在Kevin Fall和Sally Floyd1996年發(fā)布的論文中。
- TCP Tahoe 和TCP Reno
這兩個算法代號取自太浩湖Lake Tahoe和里諾市,兩者算法大致一致,對于丟包事件判斷都是以重傳超時retransmission timeout和重復確認為條件,但是對于重復確認的處理兩者有所不同,對于 超時重傳RTO情況兩個算法都是將擁塞窗口降為1個MSS ,然后進入慢啟動階段。
TCP Tahoe算法 :如果收到三次重復確認即第四次收到相同確認號的分段確認,并且分段對應包無負載分段和無改變接收窗口的話,Tahoe算法則進入快速重傳,將慢啟動閾值改為當前擁塞窗口的一半,將擁塞窗口降為1個MSS,并重新進入慢啟動階段。
TCP Reno算法 :如果收到三次重復確認,Reno算法則進入快速重傳只將擁塞窗口減半來跳過慢啟動階段,將慢啟動閾值設為當前新的擁塞窗口值,進入一個稱為快速恢復的新設計階段。
- TCP New Reno
TCP New Reno是對TCP Reno中快速恢復階段的重傳進行改善的一種改進算法,New Reno在低錯誤率時運行效率和選擇確認SACK相當,在高錯誤率仍優(yōu)于Reno。
- TCP BIC 和TCP CUBIC
TCP BIC旨在優(yōu)化高速高延遲網絡的擁塞控制,其擁塞窗口算法使用二分搜索算法嘗試找到能長時間保持擁塞窗口最大值, Linux內核在2.6.8至2.6.18使用該算法作為默認TCP擁塞算法 。
CUBIC則是比BIC更溫和和系統(tǒng)化的分支版本,其使用三次函數代替二分算法作為其擁塞窗口算法,并且使用函數拐點作為擁塞窗口的設置值, Linux內核在2.6.19后使用該算法作為默認TCP擁塞算法 。
- TCP PRR
TCP PRR是旨在恢復期間提高發(fā)送數據的準確性,該算法確?;謴秃蟮膿砣翱诖笮”M可能接近慢啟動閾值。在Google進行的測試中,能將平均延遲降低3~10%恢復超時減少5%, PRR算法后作為Linux內核3.2版本默認擁塞算法 。
- TCP BBR
TCP BBR是由Google設計于2016年發(fā)布的擁塞算法 ,該算法認為隨著網絡接口控制器逐漸進入千兆速度時,分組丟失不應該被認為是識別擁塞的主要決定因素,所以基于模型的擁塞控制算法能有更高的吞吐量和更低的延遲,可以用BBR來替代其他流行的擁塞算法。
Google在YouTube上應用該算法,將全球平均的YouTube網絡吞吐量提高了4%, BBR 之后移植入Linux內核4.9版本 。
3.4 擁塞控制過程詳解
我們以典型 慢啟動、擁塞避免、快速重傳、快速恢復 四個過程進行闡述。
- 慢啟動
慢啟動就是對于剛啟動的網絡連接,發(fā)送速度不是一步到位而是試探性增長,具體來說:連接最初建立時發(fā)送方初始化擁塞窗口cwnd為m,之后發(fā)送方在一個RTT內 每收到一個ACK數據包時cwnd線性自增1 ,發(fā)送方 每經過一個RTT時間,cwnd=cwnd*2 指數增長,經過一段時間增長直到cwnd達到慢啟動閾值ssthresh。
之后cwnd不再呈指數增長從而進入擁塞避免階段(注cwnd增長的單位是MSS),當然如果在慢啟動階段還未到達閾值ssthresh而出現丟包時進入快速重傳等階段,需要注意的是 如果網絡狀況良好RTT時間很短,那么慢啟動階段將很快到達一個比較高的發(fā)送速率 ,所以將 慢啟動理解為試探啟動 更形象。

- 擁塞避免
當慢啟動階段cwnd的值到達ssthresh時就不再瘋狂增長,進入更加理性的線性階段直至發(fā)送丟包,本次的閾值ssthresh是上一次發(fā)生丟包時cwnd的1/2,因此這是一個承上啟下的過程。
本次發(fā)送丟包時仍然會調整ssthresh的值,具體擁塞避免增長過程: 發(fā)送方每收到一個ACK數據包時將cwnd=cwnd+1/cwnd,每經過一個RTT將cwnd自增1 。
- 超時重傳和快速重傳
TCP作為一個可靠的協(xié)議面臨的很大的問題就是丟包,丟包就要重傳因此發(fā)送方需要根據接收方回復的ACK來確認是否丟包了,并且發(fā)送方在發(fā)送數據之后啟動定時器,如圖所示:

RTO是隨著復雜網絡環(huán)境而動態(tài)變化的,在擁塞控制中發(fā)生超時重傳將會極大拉低cwnd,如果網絡狀況并沒有那么多糟糕, 偶爾出現網絡抖動造成丟包或者阻塞也非常常見 ,因此觸發(fā)的慢啟動將降低通信性能,故出現了快速重傳機制。
所謂 快速重傳時相比超時重傳而言的 ,重發(fā)等待時間會降低并且后續(xù)盡量避免慢啟動,來保證性能損失在最小的程度,如圖所示:

快速重傳和超時重傳的區(qū)別在于cwnd在發(fā)生擁塞時的取值, 超時重傳會將cwnd修改為最初的值,也就是慢啟動的值,快速重傳將cwnd減半,二者都將ssthresh設置為cwnd的一半 。
從二者的區(qū)別可以看到,快速重傳更加主動,有利于保證鏈路的傳輸性能,但是有研究表明3個ACK的機制同樣存在問題,本文就不做深入闡述了,感興趣的讀者可以自主查閱。
快速重傳是基于對網絡狀況沒有那么糟糕的假設,因此在實際網絡確實還算好的時候,快速重傳還是很有用的,在很差的網絡環(huán)境很多算法都很難保證效率的。
- 快速恢復
在快速重傳之后就會進入快速恢復階段,此時的cwnd為上次發(fā)生擁塞時的cwnd的1/2,之后cwnd再線性增加重復之前的過程。