TCP擁塞控制
擁塞控制的一般原理
在計算機網路中的鏈路容量(頻寬),交換節點中的快取和處理機等,都是網路的資源,在某段時間,若對網路中某一資源的需求超過該資源所能提供的可用部分,網路效能就會變壞,這種情況叫做擁塞(congestion)。
若網路中有許多資源同時呈現供應不足,網路的效能就要明顯的變壞,整個網路的吞吐量將隨著輸入的負荷增大而下降。
網路擁塞往往是由許多因素引起的。例如,當某個節點的快取容量太小時候,到達該節點的分組因無儲存空間而不得不被丟棄。現在設想將該節點快取的容量擴至到非常代表的大,於是凡是到該節點的分組可在節點的快取佇列中排隊,不受任何限制。由於輸出鏈路的容量和處理機的速度並未提高,因此在這佇列中的絕大多數分組的排隊等待時間將會大大增加,結果上層軟體只好把它們進行重傳(因為早就超時了)。由此可見,簡單的擴大快取儲存空間同樣會造成網路資源的嚴重浪費,因而解決不了網路擁塞的問題。
又如,處理機處理的速率太慢可能引起網路的擁塞。簡單地將處理機的速率提高,可能會使上述情況緩解一些,但往往又會將瓶頸轉移到其他地方。問題的實質往往是整個系統的各個部分不匹配。只有所有的部分都平衡了,問題才會得到解決。擁塞常常趨於惡化。如果一個路由器沒有足夠的快取空間,它就會丟棄一些新到的分組。但當分組被丟棄時,傳送這一分組的源點就會重傳這一分組,甚至可能還要重傳多次。這樣會引起更多的分組流入網路和被網路中的路由器丟棄。可見擁塞引起的重傳並不會緩解網路的擁塞,反而會加劇網路的擁塞。
擁塞控制與流量控制的關係密切,它們之間也存在這差別:
-
擁塞控制就是防止過多的資料注入到網路中,這樣可以使得網路中的路由器或者鏈路防止過載,擁塞控制所要做的都有一個前提,就是網路中能夠承受現有的網路負載。擁塞控制是一個全域性性的過程,涉及到所有的主機、所有的路由器,以及與降低網路傳輸效能相關的所有因素。
-
流量控制往往指的是點對點通訊的控制,是個端到端的問題,接收端控制傳送端,流量控制要做的是抑制傳送端傳送資料的速率,以便接收端來得及接受。
在圖 5-23 中的橫座標是提供的負載(offered load),代表單位時間內輸入給網路的分組數
目。因此提供的負載也稱為輸入負載或網路負載。縱座標是吞吐量(throughput),代表單位
時間內從網路輸出的分組數目。具有理想擁塞控制的網路,在吞吐量飽和之前,網路吞吐量
應等於提供的負載,故吞吐量曲線是 45°的斜線。但當提供的負載超過某一限度時,由於網
絡資源受限,吞吐量不再增長而保持為水平線,即吞吐量達到飽和。這就表明提供的負載中
有一部分損失掉了(例如,輸入到網路的某些分組被某個結點丟棄了)。雖然如此,在這種理想的控制作用下,網路的吞吐量仍然維持在其所能到達的最大值。
但是,實際的網路情況就不相同了,從圖5-23中可以看出,隨著提供的負荷增大,網路吞吐量的增長速率逐漸減小。也就是說,在網路吞吐量還未到達飽和的時候,就已經有一部分的輸入分組被丟棄了。當網路的吞吐量明顯的小於理想的吞吐量時,網路就進入了輕度的輕度擁塞的狀態。更值得注意的是。當提供的負載達到某一數值的時候,網路的吞吐量就下降為0,網路已無法工作,這就是所謂的死鎖(deadlock)。
擁塞控制是很難設計的,因為它是一個動態的(而不是靜態的)問題。當前網路正朝著高速化的方向發展,這很容易出現快取不夠大而造成分組的丟失。但分組的丟失是網路發生擁塞的徵兆而不是原因。在許多情況下,甚至正是擁塞控制機制本身成為引起網路效能惡化甚至發生死鎖的原因。這點應特別引起重視。
由於計算機網路是一個很複雜的系統,因此可以從控制理論的角度來看擁塞控制這個問題。這樣,從大的方面看,可以分為開環控制和閉環控制兩種方法。開環控制就是在設計網路時事先將有關發生擁塞的因素考慮周到,力求網路在工作時不產生擁塞。但一旦整個系統執行起來,就不再中途進行改正了。
閉環控制是基於反饋環路的概念,主要有以下幾種措施:
-
監控網路系統一邊檢測到擁塞在何時、何處發生。
-
把擁塞發生的資訊傳送到可採取行動的地方。
-
調整網路系統的執行以解決出現的問題。
有很多的方法可用來檢測網路的擁塞。主要的一些指標是:由於是缺少快取空間而被丟棄的分組的百分數、平均佇列長度、超時重傳的分組數、平均分組時延的標準差,等等。上述這些指標的上升都標誌著擁塞的增長。
一般在檢測到擁塞發生變化的時候,要將擁塞發生的資訊傳送到產生分組的源站。當然,通知擁塞發生的分組同樣會使網路更加擁塞。
另一種方法是在路由器轉發的分組中保留一個位元或者欄位,用該位元或欄位的值表示網路沒有擁塞或者產生了擁塞。也可以由一些主機或路由器週期性的發出探測報文,以詢問擁塞是否發生。
此外,過於頻繁地採取行動以緩和網路的擁塞,會使系統產生不穩定的振盪。但過於遲緩地採取行動又不具有任何實用價值。因此,要採用某種折中的方法。但選擇正確的時間常數是相當困難的。下面就來介紹更加具體的防止網路擁塞的方法。
TCP擁塞控制方法
TCP 進行擁塞控制的演算法有四種,即慢開始(slow-start)、擁塞避(congestionavoidance)、快重傳(fast retransmit)和快恢復(fast recovery)(見 2009 年 9 月公佈的草案標準RFC 5681)。下面就介紹這些演算法的原理。為了集中精力討論擁塞控制,我們假定:
-
資料是單方向傳送的,對方只傳送確認報文。
-
接收方總是有足夠大的快取空間,因而傳送視窗的大小由網路的擁塞程度來決定。
慢開始和擁塞控制
下面討論的擁塞控制也叫作基於視窗的擁塞控制。為此,傳送方維持一個叫做擁塞視窗cwnd(congestion window) 的狀態量。擁塞視窗的大小取決於網路的擁塞程度,並且動態的在變化,傳送方讓自己的傳送視窗等於擁塞視窗。
傳送方控制擁塞視窗的原則是:只要網路上沒有出現擁塞,擁塞視窗就可以再大一些,以便把更多的分組傳送出去,這樣就提高了網路的利用率。但只要出現網路擁塞,就必須減少擁塞視窗的值,以緩解網路出現的擁塞。
傳送方又是如何知道網路出現擁塞了?我們知道:當網路發生擁塞的時候,路由器就會丟棄分組。因此只要傳送方沒有按時接收到應當到達的確認報文(超時),就可以猜測網路中出現了擁塞。現在通訊線路的傳輸質量都比較好,一般出現丟棄分組的概率是很小的(一般遠小於1%)。因此,判斷網路擁塞的依據就是出現了超時。
下面將討論擁塞視窗cwnd的大小是如何變化的。我們從“慢開始演算法”講起。
慢開始演算法思路:當主機開始傳送資料時,由於並不清楚當時網路的負載情況,所以如果立即傳送大量資料,就有可能導致網路出現擁塞甚至癱瘓。需要先驗證一下比較好,最好的辦法就是先探測一下,即由小到大逐大逐漸增大傳送視窗。也就是說,由小到大逐漸增大擁塞視窗數值。
舊的規定是這樣的:在剛剛開始傳送報文段時,先把初始擁塞視窗cwnd設定為1至2個傳送方的最大報文段SMSS(Sender Maximum Segment Size)的數值,但新的RFC5681把初始擁塞視窗cwnd設定為不超過2至4個SMSS的數值。具體的規定如下:
-
若SMSS>2019位元組:則設定初始擁塞視窗cwnd=2xSMSS位元組,且不得超過2個報文段。
-
若SMSS>1095位元組並且SMSS<=2190位元組:設定初始擁塞視窗cwnd=3xSMSS位元組,且不得超過3個報文段。
-
若SMSS<=1095位元組:則設定初始擁塞視窗cwnd=4xSMSS位元組,且不得超過4個報文段。
可見這個規定就是限制初始擁塞視窗的位元組數。
慢開始規定,在每收到一個對新的報文段的確認後,可以把擁塞視窗增加最多一個SMSS的數值,更具體些,就是
\(擁塞視窗cwnd每次的增加量=min(N,SMSSs)\)
這裡N為原先未被確認的、但現在被剛收到的確認報文段所確認的位元組數。不難看出,當N<SMSS時,擁塞視窗每次增加量要小於SMSS。
用這樣的方法逐步增大傳送方的擁塞視窗cwnd,可以使分組注入到網路的速率更加合理。下面用例子說明慢開始演算法的原理。請注意,雖然實際上 TCP 是用位元組數作為視窗大小的單位。但為敘述方便起見,我們用報文段的個數作為視窗大小的單位,這樣可以使用較小的數字來闡明擁塞控制的原理(如圖5-24)。
在一開始傳送方先將cwnd=1,傳送第一個報文M1,接收方收到後確認M1。傳送方收到對M1的確認後,把cwnd從1增加到2,於是傳送方接著傳送M2和M3兩個報文段,接收方收到發回對M2和M3的確認。傳送方每收到一個對新報文段的確認(重傳的不算在內) 就使傳送方的擁塞視窗+1,因此傳送反在接收到兩個確認後,cwnd就從2增加到4,並可以傳送M4~M7共4個報文段(如圖5-24)。因此在使用慢開始演算法後,每經過一個傳輸輪次(transmission round),擁塞視窗cwnd就加倍。
這裡我們使用了一個名詞:傳輸輪次。從圖 5-24 可以看出,一個傳輸輪次所經歷的時間其實就是往返時間 RTT(請注意,RTT 並非是恆定的數值)。使用“傳輸輪次”是更加強調:把擁塞視窗 cwnd 所允許傳送的報文段都連續傳送出去,並收到了對已傳送的最後一個位元組的確認。例如,擁塞視窗cwnd的大小是4個報文段,那麼這時的往返時間RTT就是傳送方連續傳送4個報文段,並收到這4個報文段的確認,總共經歷的時間。
我們還要指出,慢開始的“慢”並不是指cwnd的增長速率慢,而是指在TCP開始傳送報文段時先設定 cwnd = 1,使得傳送方在開始時只傳送一個報文段(目的是試探一下網路的擁塞情況),然後再逐漸增大 cwnd。這當然比設定大的 cwnd 值一下子把許多報文段注入到網路中要“慢得多”。這對防止網路出現擁塞是一個非常好的方法。
順便指出,圖 5-24 只是為了說明慢開始的原理。在 TCP 的實際執行中,傳送方只要收到一個對新報文段的確認,其擁塞視窗 cwnd 就立即加 1,並可以立即傳送新的報文段,而不需要等這個輪次中所有的確認都收到後(如圖 5-24 所示的那樣)再傳送新的報文段。
為了防止擁塞視窗 cwnd 增長過大引起網路擁塞,還需要設定一個慢開始門限 ssthresh狀態變數(如何設定ssthresh,後面還要講)。慢開始門限ssthresh的用法如下:
-
當cwnd<ssthresh時:使用上述的慢開始演算法。
-
當cwnd>ssthresh時:停止使用慢開始演算法而改用擁塞避免演算法。
-
當cwnd=ssthresh時:既可以使用慢開始演算法,也可以使用擁塞避免演算法。
擁塞避免演算法的思路是讓擁塞視窗 cwnd 緩慢地增大,即每經過一個往返時間 RTT 就把傳送方的擁塞視窗cwnd加1®,而不是像慢開始階段那樣加倍增長。因此在擁塞避免階段就有“加法增大”AI(Additive Increase)的特點。這表明在擁塞避免階段,擁塞視窗cwnd按線性規律緩慢增長,比慢開始演算法的擁塞視窗增長速率緩慢得多。
圖 5-25 用具體例子說明了在擁塞控制的過程中,TCP 的擁塞視窗 cwnd 是怎樣變化的。圖中的的數字1至5是特別要注意的幾個點。現假定TCP的傳送視窗等於擁塞視窗。當 TCP 連線進行初始化時,把擁塞視窗 cwnd 置為 1。為了便於理解,圖中的視窗單位不使用位元組而使用報文段的個數。在本例中,慢開始門限的初始值設定為 16 個報文段,即ssthresh = 16。在執行慢開始演算法時,傳送方每收到一個對新報文段的確認ACK,就把擁塞視窗值加 1,然後開始下一輪的傳輸(請注意,圖 5-25 的橫座標是傳輸輪次,不是時間)。因此擁塞視窗 cwnd 隨著傳輸輪次按指數規律增長。當擁塞視窗 cwnd 增長到慢開始門限值ssthresh 時(圖中的點1,此時擁塞視窗 cwnd = 16),就改為執行擁塞避免演算法,擁塞視窗按線性規律增長。但請注意,“擁塞避免”並非完全能夠避免了擁塞。“擁塞避免”是說把擁塞視窗控制為按線性規律增長,使網路比較不容易出現擁塞。
當擁塞視窗 cwnd = 24 時,網路出現了超時(圖中的點2),傳送方判斷為網路擁塞。於是調整門限值 ssthresh = cwnd / 2 = 12,同時設定擁塞視窗 cwnd = 1,進入慢開始階段。按照慢開始演算法,傳送方每收到一個對新報文段的確認 ACK,就把擁塞視窗值加 1。當擁塞視窗cwnd = ssthresh = 12 時(圖中的點1,這是新的ssthresh值),改為執行擁塞避免演算法,擁塞視窗按照線性規律增大。
當擁塞視窗 cwnd = 16 時(圖中的點 O),出現了一個新的情況,就是傳送方一連收到 3
個對同一個報文段的重複確認(圖中記為3-ACK)。關於這個問題要解釋如下。
有時,個別報文段會在網路中丟失,但實際上網路並未發生擁塞。如果傳送方遲遲收不到確認,就會產生超時,就會誤認為網路發生了擁塞。這就導致傳送方錯誤地啟動慢開始,把擁塞視窗 cwnd 又設定為 1,因而降低了傳輸效率。
採用快重傳演算法可以讓傳送方儘早知道發生了個別報文段的丟失。快重傳演算法首先要求接收方不要等待自己傳送資料時才進行捎帶確認,而是要立即傳送確認,即使收到了失序的報文段也要立即發出對已收到的報文段的重複確認。如圖 5-26 所示,接收方收到了 M 和M2後都分別及時發出了確認。現假定接收方沒有收到 M3 但卻收到了 M4。本來接收方可以什麼都不做。但按照快重傳演算法,接收方必須立即傳送對 M2 的重複確認,以便讓傳送方及早知道接收方沒有收到報文段 M3。傳送方接著傳送 M5 和 M6。接收方收到後也仍要再次分別發出對 M2 的重複確認。這樣,傳送方共收到了接收方的 4 個對 M2 的確認,其中後 3 個都是重複確認。快重傳演算法規定,傳送方只要一連收到3個重複確認,就知道接收方確實沒有收到報文段 M3,因而應當立即進行重傳(即“快重傳”),這樣就不會出現超時,傳送方也不就會誤認為出現了網路擁塞。使用快重傳可以使整個網路的吞吐量提高約20%。
因此,在圖 5-25 中的點4,傳送方知道現在只是丟失了個別的報文段。於是不啟動慢開始,而是執行快恢復演算法。這時,傳送方調整門限值 ssthresh = cwnd / 2 = 8,同時設定擁塞視窗cwnd = ssthresh = 8(見圖5-25 中的點≤),並開始執行擁塞避免演算法(\(ssthresh=max(FlightSize/2,2xSMSS)\))。
從圖 5-25 可以看出,在擁塞避免階段,擁塞視窗是按照線性規律增大的,這常稱為加法增大 AI (Additive Increase)。而一旦出現超時或 3 個重複的確認,就要把門限值設定為當前 擁 塞 視窗值的一半,並大大減小擁塞視窗的數值。這常稱為“乘法減小”MD(Multiplicative Decrease)。二者合在一起就是所謂的AIMD演算法。
採用這樣的擁塞控制方法使得TCP的效能有明顯的改進。
根據以上所述,TCP 的擁塞控制可以歸納為圖 5-27 的流程圖。這個流程圖就比圖 5-25所示的特例要更加全面些。例如,圖 5-25 沒有說明在慢開始階段如果出現了超時(即出現了網路擁塞)或出現 3-ACK,傳送方應採取什麼措施。但從圖 5-27 的流程圖就可以很明確地知道傳送方應採取的措施。
在這一節的開始我們就假定了接收方總是有足夠大的快取空間,因而傳送視窗的大小由網路的擁塞程度來決定。但實際上接收方的快取空間總是有限的。接收方根據自己的接收能力設定了接收方視窗 rwnd,並把這個視窗值寫入 TCP 首部中的視窗欄位,傳送給傳送方。因此,接收方視窗又稱為通知視窗(advertised window)。因此,從接收方對傳送方的流量控制的角度考慮,傳送方的傳送視窗一定不能超過對方給出的接收方視窗值 rwnd。
如果把本節所討論的擁塞控制和接收方對傳送方的流量控制一起考慮,那麼很顯然,傳送方的視窗的上限值應當取為接收方視窗 rwnd 和擁塞視窗 cwnd 這兩個變數中較小的個,也就是說:
\(傳送視窗的上限值=min[rwnd,cwnd]\)
當 rwnd < cwnd 時,是接收方的接收能力限制傳送方視窗的最大值。
反之,當 cwnd < rwnd 時,則是網路的擁塞程度限制傳送方視窗的最大值。
也就是說,rwnd 和 cwnd 中數值較小的一個,控制了傳送方傳送資料的速率。
主動佇列管理AQM
上面討論的TCP擁塞控制並沒有和網路層採取的策略聯絡起來。其實,它們之間有著密切的關係。
例如,假如一個路由器對某些分組的處理時間特別的長,那麼就可能導致這些分組中的資料部分(TCP報文段)經過很長的時間段才能到達終點,結果就有可能引起傳送方對這些報文段的重傳,由於重傳會使得傳送端認為網路中出現了擁堵,於是在TCP的傳送端就採取了擁塞控制措施,但實際上網路上沒有發生擁塞。
網路層的決策對TCP擁塞控制影響最大的就是路由器的分組丟棄策略,在最簡單的情況下,路由器的佇列通常是按照先進先出FIFO的規則來處理到來分組,但是佇列長度是有限的,當佇列滿的時候,以後再到達佇列的分組就會被丟棄掉,這也叫尾部丟棄策略(tail-drop policy)。
路由器的尾部丟失往往會導致一連串的分組丟失,這會使得傳送方出現超時重傳,使得TCP進入擁塞控制的慢開始階段,結果使得TCP連線的傳送方突然把資料的傳送速率降低到很低,更嚴重的是,在網路中通常有很多TCP連線(它們有著很多不同的源點和終點),這些連線中的報文段通常是複用網路中的IP資料包傳送。在這種情況下,若發生了路由器尾部丟棄,則可能會同時影響到很多條TCP連線,結果使得許多TCP連線在同一時間突然進入到慢狀態。這在TCP的術語中叫做全域性同步(global synchronization),全域性同步使得全網的通訊量突然下降了很多,而在網路恢復正常後,其通訊量又突然增大了很多。
為了避免網路中的全域性同步現象,在1998年提出了主動佇列管理AQM(Active Queue Management),所謂主動,就是不要等到路由器裡的佇列長度滿了才丟棄後面的分組,這樣太被動了,應當在佇列中佇列的長度到達某一個長度(警惕值)的時候(當網路擁塞出現了某些擁塞的徵兆),就應該主動丟棄到達的分組。這樣就提醒了傳送方應該放慢資料的傳送速率,就可能使得網路中的擁塞情況相應的減少,避免出現網路擁堵情況。AQM有著不同的實現方法,例如:
隨機早期檢測RED(Random early detection),實現RED就需要使路由器維持兩個引數:佇列長度最小門限,最大門限,每當一個分組到達時,RED就按照規定的演算法先計算當前的平均佇列長度:
-
若平均佇列長度小於最小門限,則把先到達的分組放入到佇列中進行排隊。
-
若平均佇列的長度超過最大門限,則把新到達的分組丟棄。
-
若平均佇列長度在最小門限和最大門限中間則按照一定的概率p,把新到達的分組丟棄掉(體現了丟棄分組的隨機性)。
由此可見,RED不是等到已經發生擁塞後才把所有再佇列尾部的分組全部丟棄掉,而是檢測到網路擁塞的早期徵兆(即路由器的平均佇列長度到達一定數值的時),以概率p丟棄個別分組,讓擁塞控制只在個別的TCP連線上進行,因而避免了全域性性的擁塞控制。
在RED中,最難處理的就是丟棄概率p的選擇,p並不是一個常數,對於每一個到達的分組,都必須重新計算丟棄概率p的值,這就比較難確定。因此2015年後標準不再推薦使用RED演算法。但是對路由器進行主動路由管理AQM仍然是有必要的,AQM實際上是對路由器中的分組排隊進行智慧管理,而並不是簡單的把佇列的尾部丟棄掉,現在已經有不同的演算法可以代替RED,但是都還在實驗階段,還沒有成為IETF標準。