TCP核心概念-慢启动,ssthresh,拥塞避免,公平性的真实含义
前言
本文主要闡述TCP擁塞控制中ssthresh的來歷以及為什么擁塞避免探測到丟包的時候,ssthresh會被設置為當前窗口的一半。
進入證實內容之前,不得不再次吐槽!目前在網上搜的,任何資料上看的,甚至RFC上,都沒有講明白到底什么是ssthresh,它的值有什么講究,幾乎所有的資料都是在說,如果窗口大于ssthresh,那么就執行線性增窗的擁塞避免階段,否則執行慢啟動...這讓幾乎所有人記住了這個結論,并且在長期被洗滌之后,很多人對這個不知所以然的事實卻表現的不以為然,其實也包括我自己。因此當我明白了ssthresh到底是怎么一回事的時候,當我知道了丟包后ssthresh的1/2系數與公平性之間的關系的時候,我便迫不及待地想把這些東西分享出來!
?
TCP數據段填滿端節點之間的網絡
假設端系統A和B之間在進行TCP通信,那么只要A和B之間存在空間距離,由于光速的傳播時延,一定意味著A和B之間存在一定的容量,可以容納若干的數據段,你可以將其想做是一般的緩存,另外,為了更具普遍性,我們認為A和B之間的所有路由器,交換機等中間節點的隊列緩存,也包含在A和B的網絡緩存之中。如下圖所示:
?
?
為了達到最高的網絡利用率,我們希望A和B之間的緩存(包括節點隊列以及網絡本身)中完全充盈著TCP的數據段,并且是持續維持。
TCP數據段無間隙地持續流動
如上圖所示,當A,B之間的網絡被填滿時,A和B之間一共有N個數據段,發送端還可以繼續發送數據嗎?事實上是可以的,因為在網絡被填滿之后,發送端每發送一個數據段,接收端同時也會消費掉一個數據段,同時發出一個ACK,直到填滿A,B間網絡的那個數據段的ACK到達發送端為止,依照假定,ACK的速率和數據段的速率一致,我們算一下,發送端一共可以持續發送2*N個數據段,這2*N個數據段發送的開始時間點是第1個數據段發送的時間,結束時間點是第一個數據段的ACK回到發送端的時間,正好是一個RTT,設發送速率為r,那么以下的等式顯而易見:
2*N = r*RTT
過程如下圖所示:
?
?
我們還可以看到,前N個段是為了填滿A,B之間的網絡,后N個段是在“A,B之間已經滿載的情況下”TCP的ACK clock驅動的pacing。緊隨著這2*N個數據段的是一個新的周期,又是一個RTT內2*N個數據段,這就是理想情況下的情景,數據段充盈著網絡,不間斷地源源不斷從發送端發出,ACK亦不間斷地從接收端返回。
兩個區域(safe & dangerous)
我其實不想現在就把謎底揭穿,但是我也不想賣關子,畢竟現在也不早了。
請注意上圖中的t28這個時間點,在t28之前,A和B之間并不總是被充滿,而在t28之后,卻總是滿的。這意味著,t28之前的數據段是可以緩沖的,即網絡中還存在一些空閑的空間可以提供給數據進行緩沖,從而不至于數據被丟棄,然而在t28之后,網絡滿載了,我們看到沒有絲毫的空白區域可供數據包緩沖,這意味一旦發生擁塞,數據包必然丟失!
顯而易見,t28之前是安全的,而t28之后則是危險的,這就是safe area和dangerous area的由來!劃分二者的是什么?正是網絡的容量!在上述圖示的例子中,就是4!當Flight的數據段小于4的時候,意味著可以激進的傳輸,當Flight數據一旦越過4,就必須保守傳輸了!
? ? ? ? 何謂激進?何謂保守?激進就是可以讓窗口最快的速度增加到safe和dangerous的邊界,由于TCP由ACK來驅動,只要收到一個ACK,就意味著通道是暢通的,窗口就可以遞增一個MSS,而所謂的保守就是,必須等待當前窗口的數據全部都被ACK了之后,說明剛才發送的數據是可以到達對端的,此時才能將窗口增加一個MSS?,F在來總結以下兩個區域的增窗方式:
safe區域:
?
?
dangerous區域:
?
?
我們來比對一下窗口在這兩個區域時的特性,如果說安全區域的目標是填滿網絡的話,那么在安全區域我們已知的是網絡并未被填滿,此時只要有ACK到來就可以增窗,直到網絡被填滿,現在我們來看危險區域,此時我們已知的事實是網絡已經滿了,我們希望讓它繼續滿下去,能滿則保持,提高網絡的利用率,我們知道TCP的發送窗口(即可以發送多少數據)是ACK驅動的,ACK就像時鐘一樣,我們希望數據可以持續發送以保持網絡滿載,就要保證ACK源源不斷地到來,因此在這個階段繼續發送數據的目標僅僅是為了消除ACK時鐘的空白期,或者稱作停擺期,因為只有這樣,源源不斷的ACK才能驅動數據源源不斷地被發送。
? ? ? ? 回過頭來再看一下增窗過程圖的t20這個時間點,網絡被4,5,6,7這四個數據包已經充滿,但是在下一個時刻t21,隨著4被接收,由于沒有到達的ACK,網絡被清空了1個MSS,理論上,我們知道最終的窗口肯定就是2*N,此例中,N就是4,在例子中,最終也確實窗口增加到了8,然而在現實中,擁塞隨時都會發生,也就是說在窗口從4增加到8的期間,隨時會發生丟包,這也就意味著窗口可能永遠都增加不到8!那么我們怎么可以知道當前的窗口是合理的,然后嘗試繼續增加窗口呢?答案就是前一個窗口的數據全部被確認!這就是擁塞避免的由來,這個過程很慢,并不是設計的問題,而是它必須這么慢。擁塞避免這個名字非常好,確實在避免!
? ? ? ? 理解了上面的描述,我們可以給出TCP管道的概念了,然后所有的真相就大白了。
?
?
TCP管道的概念
事實上,TCP管道包含兩個部分,按照公式2*N=r*RTT,2*N便是管道的容量,這兩部分的第一部分是網絡被填滿之前的容量,只要知道尚未被填滿,盡情地填,使用慢啟動足矣,第二部分是網絡被填滿之后的容量,完全按照ACK來驅動,采用擁塞避免方式探測窗口,理想情況下,理論上,這兩部分的容量是相等的。因此,我們可以就可以知道事情的真相了。
問題1:N是什么?
答:N就是ssthresh!
問題2:為什么探測到擁塞后,ssthresh要下降為當前窗口的一半?
答:探測到擁塞說明管道的容量為當前窗口C,而C=2*N,因此N=(1/2)*C!
問題3:為什么在擁塞避免階段要加性增?即AI
答:只有當一窗的數據被確認,才可以確保之前的窗口是有效的,畢竟網絡已經塞滿,而ACK有可能不對稱返回,擁塞隨時可能發生。
問題4:為什么乘性減?
答:見問題2.
問題5:慢啟動階段為什么可以指數級增窗?
答:因為此時可以確保窗口小于N,即網絡還沒有塞滿,即便發生擁塞,也還有富余的緩存可用。
問題6:還有問題嗎?
答:如果一切都如上那般美好,當然就沒有問題了,問題是,ssthresh從來就沒有估計準確過。
?
?
?
?
?
?
?
回到現實中的TCP
TCP在設計之處的愿景十分美好,然而現實世界并不是一個友好的世界,不過,TCP本身就是自適應的,它并沒有規定ssthresh的值的大小,甚至都沒有建議,它完全靠在TCP自行發現丟包或者擁塞后,以當前窗口的一半作為ssthresh的當前值,隨著連接的繼續,ssthresh也會動態調整,因為不管現實多么殘酷,理想中的反饋系統總歸是一個萬物收斂的目標,那就是實際的帶寬總是趨向于ssthresh的2倍的大小,令人驚訝的是,ssthresh就是依靠擁塞避免算法計算出來的。當然,隨著TCP的發展,這個C=2*N=r*RTT經典公式也歷經了諸多的變化形成了各種變體,比如cubic就不再以2作為ssthresh的系數來計算管道容量。
?
?
?
?
?
?
?
慢啟動的hystatr優化
我們知道,ssthresh的設置是以丟包作為反饋信號的,現在問題是,連接剛剛建立的時候,沒有丟包作為反饋信號的時候,如何來設置ssthresh?
一般而言,默認的實現都是將其設置為一個巨大的值,然后最快的速度歷經一次丟包,然后設置ssthresh為丟包時窗口的一半,然后像ssthresh的2倍緩慢逼近。但是這會帶來問題,由于沒有ssthresh作為閾值限制,用丟包作為代價,太高昂。因此在慢啟動過程中如果可以探測到ssthresh的值,那就可以隨時退出慢啟動狀態了。那么我們如何來探測呢?
還是C=2*N=r*RTT這個公式,關鍵看看我們怎么使用它。由于我們只是探測網絡被塞滿時的情況,即N的值,因此:
N=r*(RTT/2)
我們看看r是什么?所謂速率其實就是一定的事務量除以做這些事務的時間,如果說我們發出去了N'個數據包,一共用了時間段T,那么:
r=N'/T
代入后得到:
N=(N'/T)*(RTT/2)
理想情況下,在畢竟網絡容量的時候,N=N',那么就可以很簡單得到T等于RTT/2的時候,就說明達到了ssthresh,該退出慢啟動了!
那么如何來實現它呢?由于我們無法單獨探測N個數據段到達接收端并計時,我們可以變相等價使用ACK來計算,以一個窗口的第一個數據段作為計時開始Tstart,每收到一個ACK即更新以下數值:
RTTmin:采樣周期內最小的RTT,以最大限度地表示A和B之間的理想往返時延。
Tcurr:當前時間
如果下列條件成立,則可以退出慢啟動了:
Tcurr - Tstart >= RTTmin/2
非常簡單易懂。然而現實并不是理想的,大多數情況下,以上的算法并沒有帶來比較好的效果,為什么呢?因為整個帶寬不是一個TCP連接獨享的,而是全世界的所有TCP連接甚至包括UDP共享的,因此以上的公式基本上無法表示任何真實的情況,所以實際當中,更傾向于使用RTT來預估網絡已經被塞滿。使用RTT來估算網絡容量ssthresh更加實際一些,因為它充分考慮了擁塞時的排隊延時,因此在該方法下,退出慢啟動的條件便成了:
Tcurr_rtt > RTTmin + fixed_value
以上旨在解決首次慢啟動在還沒有ssthresh值的時候預測ssthresh的方式,其實在此后的任何時候,只要是慢啟動,都可以用以上的算法來預測當前的ssthresh,而不是說必須要用擁塞算法給出的ssthresh或者說僅僅是1/2丟包窗口(雖然你已經看到,這個1/2是多么地合理!)
?
?
?
?
?
?
ssthresh的快速穿越問題
我們知道,慢啟動的時候,增窗速度非???基本就是根據ACK的反饋,將數據段翻倍突突出去的),那么在窗口增加到接近到仍然小于ssthresh的時候,會出現如下圖所示的情況:
?
?
?
?
?
?
?
然而這在實現中是不易發生的,是什么限制住它的發生呢?以下幾點:
1.TCP的延遲確認機制最多只能延遲2個MSS
慢啟動增窗,收到一個ACK遞增1個MSS,即便在使用ABC的時候,也就是說窗口最多只能超越ssthresh 2個MSS,這是由下述代碼保證的:
if (sysctl_tcp_abc > 1 && tp->bytes_acked >= 2*tp->mss_cache)
cnt <<= 1;
?
?
2.即便發生了ACK大量丟失,TCP的默認實現也是數ACK的個數,而不是數被ACK的字節數
?
3.發生大量ACK丟失又啟用ABC時,見方法1.
4.兩段處理方式
Linux的4.x版本內核中默認使用ACK的字節數來計數增窗值(ABC方案),在穿越ssthresh的時候,TCP擁塞控制邏輯會將被ACK的字節數分為兩個部分,ssthresh以下的部分用來計數慢啟動,而ssthresh以上的部分用來計數擁塞避免。綜上所述,下圖總結了ssthresh穿越的情況:
?
AIMD的公平性收斂
為了簡單起見,我們假設有TCP1和TCP2兩個連接共享一個鏈路,現在看它們是怎么“收斂到公平”的,下面的圖示清晰顯示了一切:
?
?
如果你看不懂這個圖,請自行google。我們可以肯定,在公平線的下方,紅色的減窗線的斜率是恒小于公平線(斜45度角)的斜率的,兩個鏈接的每一次降窗,其降窗線的斜率都會越來越接近公平線的斜率,即收斂到公平,最終,它將在綠色粗線上震蕩,永葆公平(雖然利用率不是那么高!)。
? ? ? ? 我們還可以看到,TCP1和TCP2是等比例降窗的,在此例中比例是0.5,它們非得是0.5嗎?非也!只要保持等比例,圖中的注解1就永遠成立,最終的收斂也會永遠成立,不同的僅僅是最終收斂額綠色粗線的長度和范圍!雖然說按照最初的Reno TCP,保持0.5的降窗比例是多么得合理(見上述推論),然而考慮到現實的復雜情況,比例不再是0.5也是合理的。
? ? ? ? ?現在我們來看看如果TCP1和TCP2的降窗比例不同會怎樣。假設TCP2降窗依然為0.5的比例,而TCP2則小于0.5,那么上圖將會變成下面的樣子:
?
?
我們可以看到,競爭者中降窗比率最小的將會最終搶占幾乎所有的帶寬,它會將所有的其它連接的帶寬逐漸往左上角擠兌,最終歸零。這么說來,如果想讓自己的TCP具有侵略性,減少降窗比率是不是就可以了呢?沒這么這簡單!要知道,我上面的兩幅圖有一個共同的前提,那就是競爭者的RTT是相等的!但是現實中,會這樣嗎??非常難!如果RTT相等,比如它們的源頭和目標都在同一個地點,那么它們十有八九是合作關系,而不是競爭!爆炸!
?
? ? ? ? 那么,RTT將會是一個十分重要的角色!確實是這樣,實際的TCP在運行中,RTT的波動非常大,這就幾乎將我上面的論述全部推翻了,顯然很令人心碎!然而,上述的分析作為一個理論模型還是有意義的,它起碼讓你理解了TCP的本質行為。至于說實際情況,RTT的波動是一個有意義的信號,它讓端系統看到了中間路由器交換機的排隊行為,因此會出現RTT所謂的“噪點”,很多人想除掉它們,平滑掉它們,但是這同時也意味著你屏蔽了重要的信號。
? ? ? ? RTT的波動非常具有動感且性感,它用數值表征了整個排隊理論,或者你可以推出馬爾科夫到達過程,或者你只是覺得它們是令人難過的噪點...于是,現實中的TCP幾乎完全改進了Reno的指導,除了Reno幾乎沒有什么擁塞算法在發現丟包時把ssthresh降為當前窗口的一半。這就是TCP的進化,但是這種進化始終圍繞著一個內核,這個內核就是我上面說的這些,簡單,易懂,然而卻令人驚訝的東西。
轉載自?大神:dog250
總結
以上是生活随笔為你收集整理的TCP核心概念-慢启动,ssthresh,拥塞避免,公平性的真实含义的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 后台开发技术(1)--概述
- 下一篇: 磁盘调度算法寻道问题