TCP之擁塞視窗原理

高效能架構探索發表於2021-10-29

學過網路相關課程的,都知道TCP中,有兩個視窗:

  • 滑動視窗(在我們的上一篇文章中有講),接收方通過通告傳送方自己的可以接受緩衝區大小(這個欄位越大說明網路吞吐量越高),從而控制傳送方的傳送速度。
  • 擁塞視窗,也就是本文要講的。

概念

一個連線的TCP雙端只是網路最邊緣的兩臺主機,他們不知道整個網路是如何工作的,因此他們不知道彼此之間的有效吞吐量。因此,他們必須找到一種方法來確定它。我們稱之為擁塞視窗 (CWND)。這是在我們必須停止並等待確認之前可以傳送的位元組數。

擁塞視窗是決定任何時候可以發出的位元組數的因素之一。擁塞視窗由傳送方維護,是阻止傳送方和接收方之間的鏈路因流量過多而過載的一種手段。這不應與傳送方維護的滑動視窗相混淆,滑動視窗的存在是為了防止接收方過載。擁塞視窗是通過估計鏈路上有多少擁塞來計算的。

擁塞視窗對於裝置來說是本地的,並且永遠不會在連線上共享,這與在每個段中傳送的接收器視窗不同。在任何給定時間,裝置最多可以傳送由接收器視窗和擁塞視窗之間的最小值指定的位元組數,如下面的公式所示:

transmittable bytes = min(cwnd, rwnd)

這意味著如果擁塞視窗小於接收視窗,則裝置可以在等待確認之前傳輸多達擁塞視窗中定義的位元組數。相反,如果接收視窗小於擁塞視窗,則裝置可以在等待確認之前最多傳輸接收器視窗中定義的位元組數。

擁塞視窗根據網路擁塞動態變化。每次未確認段時,都假定是由於網路擁塞。擁塞視窗隨時間演變的方式被定義為一個演算法,這取決於實現。我們現在將介紹最常見的一種。該演算法遵循以下規則:

  • 擁塞視窗從一個段的大小開始(大約 1KB)
  • 定義了一個擁塞視窗閾值(ssthresh)
  • 如果收到確認,並且當前擁塞視窗大小小於 ssthresh,則擁塞視窗加倍
  • 如果收到確認,但擁塞視窗大於或等於 sshthresh,則擁塞視窗增加其初始值(例如 1KB)
  • 如果一個段沒有被確認從而觸發重傳,擁塞視窗就會減半並且 ssthresh 被放置在這個值
  • 擁塞視窗不能大於接收器視窗

該規則中包括我們經常聽過的幾種演算法:

  • 慢啟動(slow-start)
  • 擁塞避免(congestion avoidance)
  • 快速重傳(fast retransmit)
  • 快速恢復(fast recovery)

演算法

通過下面的圖片,可以方便大家更加快速的理解擁塞視窗的演算法機制。

從上圖中可以看出,每個裝置都會跟蹤自己的擁塞視窗(CWND,綠色)和對端的接收器視窗 (RWND)。

慢啟動

當主機開始傳送資料時,如果立即所大量資料位元組注入到網路,那麼就有可能引起網路擁塞,因為現在並不清楚網路的負荷情況。因此,較好的方法是先探測一下,即由小到大逐漸增大傳送視窗,也就是說,由小到大逐漸增大擁塞視窗數值。通常在剛剛開始傳送報文段時,先把擁塞視窗 cwnd 設定為一個最大報文段MSS的數值。而在每收到一個對新的報文段的確認後,把擁塞視窗增加至多一個MSS的數值。用這樣的方法逐步增大傳送方的擁塞視窗 cwnd ,可以使分組注入到網路的速率更加合理。

每經過一個傳輸輪次,擁塞視窗 cwnd 就加倍。一個傳輸輪次所經歷的時間其實就是往返時間RTT。不過“傳輸輪次”更加強調:把擁塞視窗cwnd所允許傳送的報文段都連續傳送出去,並收到了對已傳送的最後一個位元組的確認。

另,慢開始的“慢”並不是指cwnd的增長速率慢,而是指在TCP開始傳送報文段時先設定cwnd=1,使得傳送方在開始時只傳送一個報文段(目的是試探一下網路的擁塞情況),然後再逐漸增大cwnd。

下面,我們從示例圖著手,來分析慢啟動的過程。

  • 在協商連線時,兩個裝置交換它們的接收器視窗(在這種情況下它們都有 32KB)
  • 雙端都以 1KB 的擁塞視窗開始,但由於在該示例中客戶端將是唯一傳送資料的客戶端,因此它將是唯一一個顯著使用此值的客戶端。
  • 在第 2 行,客戶端收到一個 ACK並將其 CWND 加倍(現在是 2k)
  • 伺服器在第 3 行收到一個ACK時也做同樣的事情
  • 客戶端傳送兩段 1k 的資料,它們稍後在第 6 行和第 7 行確認,其中客戶端上的擁塞視窗加倍(4k,然後是 8k)
  • 然後,客戶端再次傳送 1k 資料並立即得到確認,有效地再次將擁塞視窗加倍(現在第 9 行為 16k)。
  • 這在第 10-11 行重複,其中 CWND 達到 32k。
  • 此時,除非接收端視窗也增長,否則擁塞視窗不能再增長。

慢啟動的整個過程如下:

  • 初始化 cwnd = 1
  • 經過1個RTT,即收到一個ACK,cwnd = 2^(1) = 2
  • 經過2個RTT, cwnd = 2^(2) = 4
  • 經過3個 RTT, cwnd = 2^(3) = 8

擁塞避免

讓擁塞視窗cwnd緩慢地增大,即每經過一個往返時間RTT就把傳送方的擁塞視窗cwnd加1,而不是加倍。這樣擁塞視窗cwnd按線性規律緩慢增長,比慢開始演算法的擁塞視窗增長速率緩慢得多。

無論在慢啟動階段還是在擁塞避免階段,只要傳送方判斷網路出現擁塞(其根據就是沒有收到確認),就要把慢啟動ssthresh設定為出現擁塞時的傳送方視窗值的一半(但不能小於2)。然後把擁塞視窗cwnd重新設定為1,執行慢開始演算法。這樣做的目的就是要迅速減少主機傳送到網路中的分組數,使得發生 擁塞的路由器有足夠時間把佇列中積壓的分組處理完畢。

“擁塞避免”並非指完全能夠避免了擁塞。利用以上的措施要完全避免網路擁塞還是不可能的。“擁塞避免”是說在擁塞避免階段將擁塞視窗控制為按線性規律增長,使網路比較不容易出現擁塞

  • cwnd = i
  • 經過 1 RTT, cwnd = i+1
  • 2 RTT, cwnd = i+2
  • 3 RTT, cwnd = i+3

快速重傳

TCP 有一個快速傳輸特性——在它的計時器到期之前重新傳輸丟失的段。 為了允許快速傳輸,我們需要為傳送方和接收方設定一些規則。

  • 作為接收者,它應該始終傳送它期望接收的序列號。 例如,當接收方接收到第 1 段時,它以 ACK2 響應,
  • 作為傳送方,它應該忽略定時器並在收到 3 個重複的ACK 後立即開始重傳丟失的段。

用一句話概況,就是傳送端在收到3個重複無序的ACK時候,它假定資料包丟失並重傳該資料包,而無需等待重傳計時器到期。

而在此時,擁塞視窗的變化過程如下:

  • ssthresh設定為擁塞視窗的1/2
  • 擁塞視窗大小設定為ssthresh
  • 重新進入擁塞避免階段

快速恢復

  • 當收到第3個重複的ACK時,將ssthresh設定為當前擁塞視窗cwnd的一半。重傳丟失的報文段。設定cwnd為ssthresh加上3倍的報文段大小。
  • 每次收到另一個重複的ACK時,cwnd增加1個報文段大小併傳送1個分組(如果新的cwnd允許傳送)。
  • 當下一個確認新資料的ACK到達時,設定cwnd為ssthresh(在第1步中設定的值)。這個ACK應該是在進行重傳後的一個往返時間內對步驟1中重傳的確認。另外,這個ACK也應該是對丟失的分組和收到的第1個重複的ACK之間的所有中間報文段的確認。這一步採用的是擁塞避免,因為當分組丟失時我們將當前的速率減半。

演算法

快速重傳和快速恢復的目的是:快速恢復丟失的資料包。 如果沒有快速重傳和快速恢復這倆演算法,那麼tcp可能

Tahoe

Tahoe演算法是TCP的早期版本。除了具備TCP的基本架構和功能外,引入了慢啟動、擁塞避免以及快速重傳機制。 在該演算法中,快速重傳機制策略如下:

  • ssthresh設定為擁塞視窗的1/2
  • 擁塞視窗大小設定為1
  • 重新進入慢啟動階段

Reno

Reno與Tahoe相比,增加了快速恢復階段,也就是說,完成快速重傳後,進入了擁塞避免階段而不是慢 啟動階段。 Reno 在快速重傳階段,重新傳送資料之後:

  • ssthresh設定為擁塞視窗的1/2
  • 擁塞視窗設定為之前的1/2
  • 進入快速恢復階段
  • 在快速恢復階段,每收到重複的ACK,則cwnd加1;收到非重複ACK時,置cwnd = ssthresh,轉入擁塞避免階段;
  • 如果發生超時重傳,則置ssthresh為當前cwnd的一半,cwnd = 1,重新進入慢啟動階段。

Reno快速恢復階段退出條件:收到非重複ACK。

NewReno

在Reno版本中,若同時有多個資料包丟失,則大部分必須等到TimeOut之後,才進行重傳。這是因為在Reno中,同時有多個資料包丟失時,只要收到部分丟失資料的ACK,便退出快速恢復。而之所以能收到部分丟失資料的ACK,這是因為在快速重傳階段,只重新傳送了部分丟失的資料。而在Reno結束快速恢復,進入擁塞避免階段之後,對於其他未重新傳送的資料包來說,常常沒有足夠的重複ACK來觸發快速重傳機制。只好等等TimeOut,而TimeOut對於TCP效能有非常大的影響,在等待TimeOut這段時間,無法傳送新的資料包,而在TimeOut之後,CWND被重新設定為1。

基於上述原因,NewReno優化了該機制,NewReno在收到部分丟失資料的ACK後,並不會退出快速恢復階段,而是等待所有丟失的包都重新傳送之後,才退出快速恢復階段。這就使得NewReno在遇到多個資料包同時丟失時,不需要等待TimeOut,便可重新傳送所有丟失的資料包,進而減小TimeOut對效能的影響。

SACK

除了NewReno的方法之外,要解決大量資料包丟失的問題,還有一個解決方案,就是讓傳送端知道,哪些資料包已經送達,哪些資料包已經丟失。 SACK修改在接收端傳送重複的ACK時,同時在ACK中攜帶連續的已經收到的資料序列號範圍,而連續資料序號範圍與連續資料序號範圍之間的間隔就是已經丟失的資料。

滑動視窗與擁塞視窗

共同點:提高網路效能。

不同點:

  • 流量控制:在TCP連線上實現對傳送流量的控制,考慮點對點之間對通訊量的控制,端到端,即:控制傳送端的資料傳送速率,使接收端可以來得及接收,保證網路高效穩定執行。
  • 擁塞控制:處理網路擁塞現象,考慮網路能夠承受現有的網路負荷,全域性性變數,涉及所有的路由器、主機以及與降低網路傳輸效能有關的因素。防止過多的資料注入到網路,使網路中的路由器或鏈路不致過載,確保通訊子網可以有效為主機傳遞分組。

Q&A

1、在一個視窗內重複丟包會造成影響嗎? 會。如果只丟一個包,那麼收到非重複ACK時,就能確認完本視窗內所有的包。然後進入擁塞 避免階段。這就是Reno想達到的。 而如果丟失多個包,那麼收到非重複ACK時,不能確認完本視窗內所有的包。但是,也會退出快速恢復, 進入擁塞避免階段。

這個時候可能會發生兩種情況:

  • 多次進行快速重傳和快速恢復。又發現丟包,再次進入快速重傳和快速恢復。注意,每次進入快速重傳和快速恢復時,ssthresh和CWND都要減半。多次丟包最終會導致ssthresh指數減小。通過畫cwnd(t)圖可以發現,不僅這段時間吞吐量非常低,而且導致恢復完後擁塞避免的起點非常低,從而導致之後的吞吐量也很低。

  • 經過多次快速重傳和快速恢復,接著發生傳輸超時。

2、為什麼發生擁塞時,還增加cwnd?

在檢測到丟包時,視窗為CWND。這時候網路中最多有cwnd個包(傳輸中 < CWND)。每當收到一個重複的ACK,則說明有資料包離開網路,達到接收端了。那麼,此時網路中還可以再容納1個包。由於傳送端滑動視窗不能移動了,所以如果想保持繼續保持固定個數的傳輸資料包,可以使CWND + 1。這樣一來,可以提高吞吐量。而實際上,在fast recovery期間傳送的新資料包比起發生丟包的CWND來說,已經是大大減少了。

- END -

相關文章