TCP擁塞控制基礎

TCP要點有四,一曰有連接,二曰可靠傳輸,三曰數據按照到達,四曰端到端流量控制。注意,TCP被設計時只保證這四點,此時它雖然也有些問題,然而很簡單,然而更大的問題很快呈現出來,使之不得不考慮和IP網絡相關的東西,比如公平性,效率,因此增加了擁塞控制,這樣TCP就成了現在這個樣子。

爲什麼要進行擁塞控制

要回答這個問題,首先必須知道什麼時候TCP會出現擁塞。TCP作爲一個端到端的傳輸層協議,它並不關心連接雙方在物理鏈路上會經過多少路由器交換機以及報文傳輸的路徑和下一條,這是IP層該考慮的事。然而,在現實網絡應用中,TCP連接的兩端可能相隔千山萬水,報文也需要由多個路由器交換機進行轉發。交換設備的性能不是無限的!, 當多個入接口的報文都要從相同的出接口轉發時,如果出接口轉發速率達到極限,報文就會開始在交換設備的入接口緩存隊列堆積。但這個隊列長度也是有限的,當隊列塞滿後,後續輸入的報文就只能被丟棄掉了。對於TCP的發送端來說,看到的就是發送超時丟包了。

在這裏插入圖片描述

網絡資源是各個連接共享的,爲了大家都能完成數據傳輸。所以,TCP需要當它感知到傳輸發生擁塞時,需要降低自己的發送速率,等待擁塞解除。

如何進行擁塞控制

擁塞窗口 cwnd

首先需要明確的是,TCP是在發送端進行擁塞控制的。TCP爲每條連接準備了一個記錄擁塞窗口大小的變量cwnd1,它限制了本端TCP可以發送到網絡中的最大報文數量2。顯然,這個值越大,連接的吞吐量越高,但也更容易導致網絡擁塞。所以,TCP的擁塞控制本質上就是根據丟包情況調整cwnd,使得傳輸的吞吐率儘可能地大!而不同的擁塞控制算法就是調整cwnd的方式不同!

1: 本文中的cwnd以發送端的最大報文段長度SMSS爲單位的
2: 這個數量也受對端通告的窗口大小限制

Linux 用戶可以使用 ss --tcp --info 查看鏈接的cwnd

擁塞控制算法

TCP從誕生至今,已經有了多種的擁塞控制算法,直到現在還有新的在被提出!其中TCP Tahoe(1988)和TCP Reno(1990)是最初的兩個算法。雖然看上去年代很就遠了,但 Reno算法直到現在還在廣泛地使用。

  • Tahoe 提出了1)慢啓動,2)擁塞避免,3)快速重傳
  • RenoTahoe的基礎上增加了4)快速恢復

Tahoe算法的基本思想是

  • 首選設置一個符合情理的初始窗口值
  • 當沒有出現丟包時,慢慢地增加窗口大小,逐漸逼近吞吐量的上界
  • 當出現丟包時,快速地減小窗口大小,等待阻塞消除

Tahoe 擁塞避免 (congestion-avoidance)

當我們在理解擁塞控制算法時,可以假想發送端是一下子將整個擁塞窗口大小的報文發送出去,然後等待迴應。

Tahoe採用的是加性增乘性減(Additive Increase, Multiplicative Decrease, AIMD)方式來完成緩慢增加和快速減小擁塞窗口:

發送端發送整窗的數據:

  • 如果沒有丟包,則 cwnd = cwnd + 1
  • 如果出現丟包了,則 cwnd = cwnd / 2

爲什麼丟包後是除以2呢, 這裏的2實際上是一個折中值!用下面的例子來解釋!
在這裏插入圖片描述

物理傳輸路徑都會有延時,這個延時也讓傳輸鏈路有了傳輸容量(transit capacity)這樣一個概念,同時我們把交換設備的隊列緩存稱爲隊列容量(queue capacity).比如下面這樣一個連接,傳輸容量是M,隊列容量是N.

cwnd小於M時,不會使用R的隊列,此時不會有擁塞發生;當cwnd繼續增大時,開始使用R的隊列,此時實際上已經有阻塞了!但是A感知不到,因爲沒有丟包! 當cwnd繼續增大到M + N時,如果再增大cwnd,就會出現丟包。此時擁塞控制算法需要減小cwnd,那麼減小到多少合適呢? 當然是減少到不使用R的緩存,或者說使得cwnd = M,這樣可以快速解除阻塞。

這是理想的情況! 實際情況是A並不知道MN有多大(或者說有什麼關係),它只知道當cwnd超過M + N時會出丟包!於是我們折中地假定M = N,所以當cwnd = 2N時是丟包的臨界點,爲了解除阻塞,讓cwnd = cwnd / 2 = N就可以解除阻塞3
![圖片描述][3]

所以,cwnd的變化趨勢就像上面這樣,圖中上方的紅色曲線表示出現丟包。這樣的穩定狀態也稱爲擁塞避免階段(congestion-avoidance phase)

現實算法實現中,擁塞避免階段的cwnd是在收到每個ACK時更新的:cwnd += 1/cwnd,如果認真算,會發現這比整窗更新cwnd += 1要稍微少一點!

3:後面會提到,Tahoe在此時會將cwnd先設置爲1,然後再迅速恢復到cwnd / 2

Tahoe 慢啓動 (Slow Start)

Tahoe需要爲選定一個cwnd初始值,但是發送端並不知道多大的cwnd才合適。所以只能從1開始4,如果這個時候就開始加性增,那就太慢了,比如假設一個連接會發送5050MSS大小的報文,按照加性增加,需要100RTT才能傳輸完成(1+2+3+…+100=5050)。因此,TahoeReno使用一種稱爲慢啓動的算法迅速提高cwnd。也就是隻要沒有丟包,每發送一個整窗的數據,cwnd = 2 X cwnd。換句話說,在慢啓動階段(slow-start phase),當發送端每收到一個ACK時,就讓cwnd = cwnd + 1

在這裏插入圖片描述

4 RFC 2581 已經允許cwnd的初始值最大爲2, RFC 3390 已經允許cwnd的初始值最大爲4, RFC 6928已經允許cwnd的初始值最大爲10

那麼,慢啓動階段何時停止?或者說什麼時候進入前面的擁塞避免階段 ? Tahoe算法定義了一個慢啓動閾值(slow-start threshold)變量,在cwnd < ssthresh時,TCP處於慢啓動階段,在cwnd > ssthresh後,TCP處於擁塞避免階段。

ssthreshold的初始值一個非常大的值。連接建立後cwnd以指數增加,直到出現丟包後, 慢啓動閾值將被設置爲 cwnd / 2。同時cwnd被設置爲1,重新開始慢啓動過程。這個過程如下圖所示, 可以看到,慢啓動可是一點也不慢。

在這裏插入圖片描述

Tahoe 快速重傳 (Fast Retransmit)

現實的網絡網絡環境拓撲可能十分複雜,即使是同一個TCP連接的報文,也有可能由於諸如等價路由等因素被路由器轉發到不同的路徑,於是,在接收端就可能出現報文的亂序到達,甚至丟包!舉個例子,發送端發送了數據DATA1DATA2、……、DATA8,但由於某些因素,DATA2在傳輸過程中被丟了,接收端只收到另外7個報文,它會連續回覆多次 ACK1(請求發送端發送DATA2)。這個時候,發送端還需要等待DATA2的回覆超時(2個RTT)嗎?

快速重傳的策略是,不等了!擋發送端收到第3個重複的ACK1時(也就是第4ACK1),它要馬上重傳DATA2,然後進入慢啓動階段,設置ssthresh = cwnd / 2, cwnd = 1.

在這裏插入圖片描述
如上圖所示,其中cwnd的初始值爲8,當發送端收到第3個重複的ACK1時,迅速進入慢啓動階段,之後當再收到ACK1時,由於cwnd = 1只有1,因此並不會發送新的報文

Reno 快速恢復(Fast Recovery)

在快速重傳中,當出現報文亂序丟包後,擁塞窗口cwnd變爲1,由於該限制,在丟失的數據包被應答之前,沒有辦法發送新的數據包。這樣大大降低了網絡的吞吐量。針對這個問題,TCP RenoTCP Tahoe的基礎上增加了快速恢復(Fast Recovery)。

快速恢復的策略是當收到第3個重複的ACK後,快速重傳丟失的包,然後

  • 設置 sshthresh = cwnd / 2
  • 設置 cwnd = cwnd /2

還是以上面的例子爲例
在這裏插入圖片描述
與快速重傳中不同的是,發送端在收到第3個重複的ACK後,cwnd變爲5EFS設置爲7

這裏EFS表示發送端認爲的正在向對端發送的包(Estimated FlightSize),或者說正在鏈路上(in flight)的包。一般情況下,EFS是與cwnd相等的。但在快速恢復的時候,就不同了。假設擁塞避免階段時cwnd = EFS = N,在啓動快速恢復時,收到了3個重複的ACK,注意,這3ACK是不會佔用網絡資源的(因爲它們已經被對端收到了),所以EFS = N - 3,而既然是出發了快速恢復,那麼一定是有一個包沒有到達,所以EFS = N - 4,然後,本端會快速重傳一個報文,EFS = N - 3,這就是上面EFS設置爲7的來源。

其他部分沒什麼好說的,發送端會在EFS < cwnd時發送信的數據,而同時,這又會使得EFS = cwnd

TCP New Reno

根據Reno的描述,TCP發送端會在收到3個重複的ACK時進行快速重傳和快速恢復,但還有有一個問題,重複的ACK背後可能不僅僅是一個包丟了!如果是多個包丟了,即使發送端快速重傳了丟失的第一個包,進入快速恢復,那麼後面也會收到接收端發出的多個請求其他丟失的包的重複ACK!這個時候?發送端需要再累計到3個重複的ACK才能重傳!

問題出在哪裏?發送端不能重收到的重複ACK中獲得更多的丟包信息!它只知道第一個被丟棄的報文,後面還有多少被丟棄了?完全不知道!也許使用SACK(參考RFC6675)這就不是問題,但這需要兩端都支持SACK。對於不支持SACK的場景,TCP需要更靈活!

RFC6582中描述的New Reno算法,在Reno中的基礎上,引入了一個新的變量recover,當進入快速恢復狀態時(收到3個重複的ACK[a]),將recover設置爲已經發送的最後的報文的序號。如果之後收到的新的ACK[b]序號b不超過recover,就說明這還是一個丟包引起的ACK !這種ACK在標準中也稱之爲部分應答(partial acknowledgments), 這時發送端就不等了,立即重傳丟失的報文。

還有後文!

Ref List

Intronetworks
CoolShell
dog250

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章