從TCP擁塞本質看BBR算法及其收斂性(附CUBIC的改進/NCL機制)

本文試圖給出一些與BBR算法相關但倒是其以外的東西。算法

1.TCP擁塞的本質

注意,我並無把題目定義成網絡擁塞的本質,否則又要扯泊松到達和排隊論了。事實上,TCP擁塞的本質要好理解的多!TCP擁塞絕大部分是因爲其」加性增,乘性減「的特性形成的!
        也就是說,是TCP本身形成了擁塞!TCP加性增乘性減的特性引起了丟包,而丟包的擁塞誤判帶來了巨大的代價,這在深隊列+AQM情形下尤爲明顯。
        我儘量快的解釋。爭取用一個簡單的數學推導過程和一張圖搞定。
        除非TCP端節點之間的網絡帶寬是均勻點對點的,不然就必然要存在第二類緩存。TCP並沒有法直接識別這種第二類緩存。正是這第二類緩存的存在致使了擁塞的代價特別嚴重。我依然用經典的圖做爲基準來解釋:數組

 

 

第二類緩存的時間牆特徵致使了排隊的發生,而排隊會致使一個TCP鏈接中數據包的RTT變大。爲了討論方便,咱們假設TCP端節點之間管道最細處(即Bottleneke處)的帶寬爲B,那麼正如上圖所代表的,我把TCP端節點之間的網絡中,凡是帶寬比B大的網絡均包含在第二類緩存中,也就是說,凡是會引發排隊的路徑,均是第二類緩存。

假設TCP端節點之間的BDP爲C,那麼:
C = C1 + C2  (其中C1是網絡自己的管道容量,而C2是節點緩存的容量)
因爲路徑中最小帶寬爲B,那麼整個鏈路的帶寬將由B決定,在排隊未發生時(即沒有發生擁塞時),假設測量RTT爲rtt0,發送速率爲B0=B,則:
C1 = B0*rtt0
C = B0*rtt0 +C2 > B*rtt0

此時,任何事情均爲發生,一切平安無事!繼續着TCP」加性增「的行爲,此時發送端繼續線性增長髮送速率,到達B1,此時:
B0*rtt0 < B1*rtt1
C是客觀的不變量,這會致使C2開始被填充,即開始輕微排隊。排隊會形成RTT的增長。假設C2已經被加性增特性填充到滿載的臨界,此時發送帶寬爲B2,即:
C = B2*rtt2 = B*rtt0 + C2緩存

B2*rtt2是定值,rtt2在增大,B2則必須減少!可是」臨界值已經達到「這件事反饋到發送端,至少要通過1/2個RTT,在忽略延遲ACK和ACK丟失等反饋失靈情形下,最多的反饋時間要1個RTT。問題是,TCP發送端怎麼知道C2已經被填滿了??它不知道!除非再增長一些窗口,多發一個數據包!這行爲是如此的當心翼翼,以致於你會認爲這是多麼正確的作法!在發送端不知情的狀況下,會持續增長或者保持當前的擁塞窗口,可是絕對不會下降,然而此時RTT已經增大,必須降速了!事實上,在丟包事件發生前,TCP是必定會加性增窗的,也就是說,丟包是TCP惟一能夠識別的事件!網絡

        TCP在臨界點的加性增窗行爲,目的只是爲了探測C2是否是已經被填滿。咱們來根據以上的推導計算一下此次探測所要付出的代價。因爲反饋C2已滿的時間是1/2個RTT到1個RTT,取決於C2的位置,那麼將會在1/2個RTT到1個RTT的時間內面臨着丟包!注意,這裏的代價隨着C2的增長而增長,由於C2越大,RTT的最終測量值,即rtt2則越大!這就是深隊列丟包探測的問題。
        然而,在30多年前,正是這個」加性增「行爲,直接導出了」基於丟包的擁塞控制算法「。那時沒有深隊列,問題貌似還不嚴重。但隨着C2的增長,問題就愈來愈嚴重了,RTT的增大使得丟包處理的代價更大!
        記住,對丟包的敏感不是錯誤,基於丟包的擁塞探測的算法就是這樣運做的,錯誤之處在於,丟包的代價太大-窗口猛降,形成管道被清空。這是因爲深隊列的BufferBloat引起的問題,在淺隊列中問題並不嚴重。隨着路由器AQM技術的發展,好的初衷會對基於丟包的擁塞探測產生反而壞的影響。

        如今,咱們明白了,之因此基於丟包的擁塞控制算法的帶寬利用率低,就是因爲其填充第二類緩存所平添排隊延遲形成的虛假且逐漸增大的RTT最終致使了BDP很大的假象,而這一切的目的,卻僅僅是爲了探測丟包,自覺得在丟包前已經100%的利用了帶寬,然而在丟包後,全部的一切都加倍還了回去!是丟包致使了帶寬利用率的降低,而不是增長!!

        總結一句,用第二類緩存來探測BDP是一種透支資源的行爲。
        我一直以爲這不是TCP的錯,但在發現BBR是如此簡單以後,再也不這麼認爲了,事實上,經過探測時間窗口內的最大帶寬和最小RTT,就能夠明確知道是否是已經填滿了第一類緩存,並中止繼續填充第二類緩存,即向最小化排隊的方向收斂!曾經的基於時延的算法,好比Vegas,其實已經在走這條路了,它已經知道RTT的增長意味着排隊了,只是它沒有采用時間窗口過濾掉常規波動,而是採用了RTT增量窗口來過濾波動,最終甚至因爲RTT抖動主動減小窗口,因此會形成競爭性不足。無論怎樣,這是一種君子行爲,它老是無力對抗基於丟包算法的流氓行爲。
        BBR綜合了兩者,對待君子則君子(不會填充第二類緩存,形成排隊,由於一旦排隊,全部鏈接的RTT均會增長,對相似Vegas的不利),對待流氓則流氓(採用滑動時間窗口抗帶寬噪聲,採用固定超時時間窗口抗RTT噪聲,時間窗口內,決不降速),這是一種什麼行爲?我以爲比較相似警察的行爲...

        若是不是很理解,那麼看看那些高速公路上隨意變道或者佔用應急車道的行爲致使的後果吧(大多數沒有什麼後果,緣由在於監管的不力,這就好像CUBIC遇到了Vegas同樣!)。基於此,即使不使用BBR算法,最好也不要使用基於時延的Vegas等算法,可是也許,咱們能夠更好的改進CUBIC,咱們也許已經知道了如何去更改CUBIC了。CUBIC的問題不是其算法自己致使的,而是TCP擁塞控制的框架致使的。見本文」CUBIC更改前奏-實現NCL(非擁塞丟包)「小節。

        本節的最後,咱們來看點關於第二類緩存的特性。
        第二類緩存既然不是用來進行」BDP探測「(事實上,BDP的組成里根本就該有第二類緩存)的,那要它幹什麼??
        我想這裏能夠簡單解釋一下了。第二類緩存的做用是爲了適配統計複用的分組交換網絡上路由器處理不過來這個問題而引入的。若是沒有路由器交換機節點的存在,那麼第二類緩存這裏什麼也沒有:框架

 

 

若是你想最快速度理解上圖中泊松到達這個點的入口行爲和固定速率發出的出口行爲,請考慮丁字路由或十字路口,和路由器同樣,只有在交叉點的位置才須要第二類緩存來平滑多方瞬時速率的不匹配特徵!我以丁字路口爲例:dom

 

 

無論哪裏爲應對瞬時到達率而加入的」緩存「,都是第二類緩存,這類緩存的目的是臨時緩存瞬時到達過快的數據或者車流,這就是統計複用的分組交換網節點緩存的本質!然而一旦這些緩存被誤用了,擁塞就必定會發生!誤用行爲不少,好比UDP毫無節制的發包,好比TCP依靠填滿它而發現擁塞,諷刺的是,很大程度上,擁塞是TCP本身形成的,要想發現擁塞,就必需要先製造擁塞。
本節完!tcp

2.突發特徵與pacing

這裏僅僅提一點,那就是突發最容易形成排隊!這也是能夠從泊松到達的排隊論中推導出來的。爲了避免被人認爲我在這裏裝逼,就不展現過程了,須要的請私下聯繫我。
        解決突發問題的方法有兩種,一種就是邊緣網絡路由器上設置整形規則,這有效避免了匯聚層以及核心層路由器的排隊。另一種更加有效的方法就是直接在端主機作Pacing。Linux在3.12內核之後已經支持了FQ這個sched模塊,它基於TCP鏈接發現的Pacing Rate來發送數據,取代了以前一窗數據突發出去的弊端。
        Pacing背後的思想就是儘可能減小網絡交換節點處隊列的排隊!經過上一節的最後,咱們知道,交換節點出口的速率恆定,而入口可能會面臨突發,雖然在統計意義上,出入口的處理能力匹配便可,然而即使大多數時候到達速率都小於出口速率,只要有一瞬間的突發就可能衝擊隊列到爆滿!事實上隊列緩存存在的理由就是爲了應對這種狀況!
傳統意義上,TCP擁塞控制邏輯僅僅計算一個擁塞窗口,TCP發送按照這個擁塞窗口發送適當大小的數據,但這些數據幾乎是一次性突發出去的,Linux 3.9以後的patch出現了TCP Pacing rate的概念,能夠將一窗數據按照必定的速率平滑發送出去,然而TCP自己並無實現實際的Pacing發送邏輯,Linux 3.12內核實現了FQ這個schedule,TCP能夠依靠這個schedule來實現Pacing了。
        爲何不在TCP層實現這個Pacing,緣由在於TCP層並不能控制嚴格的發送時序,它是屬於軟件層的。Pacing必須在數據包被髮送到鏈路以前進行才比較有效,由於這時的Pacing是真實的!切記,Pacing目前能夠經過TC來配置,要想Pacing起做用,在其以後就不能再有別的隊列,不然,FQ的Pacing Rate就可能會被後面的隊列給沖掉!
...
咱們繼續談BBR算法的收斂特性。性能

3.BBR算法的收斂性

BBR算法的收斂性與以前基於加性增乘性減的算法的收斂性徹底不一樣,比以前的更加優美!欲知如何,我先展現加性增乘性減的收斂圖:測試

 

 

如下是根據上圖總結出來的一幅抽象圖:ui

 

 

 

這個圖以前貼過,這個圖來自於控制論的理論,每一個鏈接是獨立地向最終的收斂點去收斂,你們彼此不交互,只要都奔着平衡收斂點走就行。
        當咱們認識BBR收斂性的時候,咱們要換一種思路。即BBR收斂過程並非獨立的,它們是配合的,BBR算法根本就沒有定義收斂點,只是你們互相配合,知足其帶寬之和不超過第一類緩存的大小,即真正BDP的大小,在這個約束條件下,BBR最終本身找到了一個穩定的平衡點。
        在展現圖解以前,爲了簡單起見,咱們先假設BBR在PROBE_BW狀態,討論在該狀態的收斂過程。咱們先看一下PROBE_BW狀態的增益係數數組:

static const int bbr_pacing_gain[] = {  
    // 佔據帶寬,在帶寬滿以前,一直運行。效率優先,儘量處在這裏久一些...  
    BBR_UNIT * 5 / 4,    /* probe for more available bw */    
    // 出讓帶寬,只要帶寬不滿了,則進入穩定狀態平穩運行。兼顧公平,儘量離開這裏...  
    BBR_UNIT * 3 / 4,    /* drain queue and/or yield bw to other flows */  
    // 一方面平穩運行,一方面等待出讓的帶寬不被本身從新搶佔!  
    BBR_UNIT, BBR_UNIT, BBR_UNIT,    /* cruise at 1.0*bw to utilize pipe, */  
    BBR_UNIT, BBR_UNIT, BBR_UNIT    /* without creating excess queue... */  
};

 

仔細看這個數組,就會發現,bbr_pacing_gain[0],bbr_pacing_gain[1]以及後面的元素安排很是巧妙!bbr_pacing_gain[0]代表,BBR有機會獲取更多的帶寬,而bbr_pacing_gain[1]則代表,在獲取了足夠的帶寬後,在須要的狀況下要出讓部分帶寬,而後在出讓了部分帶寬後,循環6個週期,等待其它鏈接獲取出讓的帶寬。那麼,BBR如何安排以上三類增益係數的使能週期長度呢?
        很顯然,BBR但願鏈接儘量多的使用帶寬,所以bbr_pacing_gain[0]的使能時間儘量久些,其退出條件是:
已經運行超過了一個最小RTT時間而且要麼發生了丟包,要麼本次ACK到來前的inflight的值已經等於窗口值了。
雖然BBR但願一個鏈接儘量佔用帶寬,可是BBR的原則是不能排隊或者起碼減小排隊,當另外一個鏈接發起時,額外的帶寬佔用會讓處在正增益的鏈接inflight發生滿載,所以bbr_pacing_gain[0]會讓位給bbr_pacing_gain[1],進而出讓帶寬給新鏈接,隨後進入長達6個RTT週期的平穩時期,等待出讓的帶寬被利用。總之,總結一點就是:
若是沒有其它鏈接,一個鏈接會一直試圖佔滿全部帶寬,一旦有新鏈接,則老鏈接儘可能一次性或者很短期內出讓部分帶寬,而後在這些帶寬被利用以前,老鏈接再也不搶帶寬,若是超過6個RTT週期以後,老鏈接從新開始新一輪搶佔,出讓,等待被利用的過程,從而和其它的鏈接一塊兒收斂到平衡點。
        所以,和加性增乘性減的獨立收斂方案不一樣,BBR一開始就是考慮到對方存在的收斂方案。咱們看一個簡單的例子,描述一下大體的收斂思想:
初始狀態
鏈接1:10 & 鏈接2:0
1>.鏈接1在一個RTT出讓1/4帶寬,穩定6個RTT,帶寬爲7.5 & 鏈接2以4個RTT爲一個PROBE週期分別的帶寬爲:1.25, 1.55,1.95,2.4
2>.鏈接1在bbr_pacing_gain[0]佔據帶寬失敗,繼續出讓帶寬,穩定在5.6 & 鏈接2以3個RTT爲一個PROBE週期分別的帶寬爲3.0,3.75,4.6
3>.完成收斂。

最後,咱們能夠看一下BBR的收斂圖了:

 

 

 

根據Google的測試,其收斂效果以下圖:

 

 

 

 

經過上圖以及bbr_pacing_gain數組,咱們知道了是什麼保證了收斂。假設鏈接1和鏈接2在網絡中的RTT相同(這種假設是合理的,由於在BDP計算中,能夠將RTT做爲一種權值看待),那麼根據PROBE_BW狀態內部增益在bbr_pacing_gain數組中轉換的規則:
1).BBR_UNIT * 5 / 4的增益要保持多個最小RTT(非滑動的時間內)的時間,直到填滿帶寬(但要避免排隊!因此只要觸及帶寬便可,即上一次的inflight等於本次的窗口估值)
2).BBR_UNIT * 3 / 4的增益保持的時間要短,最多一個最短RTT時間就退出,在不到RTT時間內,若是排空了部分網絡資源也退出。
3).BBR_UNIT的增益緊接在BBR_UNIT * 3 / 4以後,而且持續6個最小RTT的時間。
4).在1)和2)表示的獲取和出讓之間,保持等比例平衡,默認爲當前帶寬1/4的獲取和出讓。

咱們來根據以上規則描述上圖中的收斂:
1).在上圖的右下邊,鏈接1帶寬大於鏈接2,以上的規則使得鏈接1一次出讓的帶寬大於鏈接2一次獲取,所以鏈接2多個RTT週期內維持在BBR_UNIT * 5 / 4增益,平衡點向沿着帶寬線向左上收斂。
2).在上圖的左上方,鏈接2的帶寬大於鏈接1,鏈接1即便再出讓帶寬,即使鏈接2獲取了,那鏈接2回贈的帶寬仍是大於鏈接1的出讓,使得鏈接1比較久維持在BBR_UNIT * 5 / 4增益,向右下收斂。


 

4.BBR的公平收斂性與調優

關於收斂和性能調優的話題,這裏還要多說幾句。
        首先,雖然收斂是必然的,上文已經分析,可是收斂速度如何呢?BBR在Linux實現的目前是第一個版本,其帶寬出讓環節和帶寬獲取環節中,增益係數中有一個定值,即3/4和5/4,這兩個值的算術平均值爲1,這裏的意義在於保持一個比例上的平衡。若是將出讓係數3/4調大,好比調到1/2,那麼獲取帶寬的增益係數就要調整爲3/2,這會讓收斂速度更快些。
        我着手要作的就是參數化這個bbr_pacing_gain數組:

static const int bbr_pacing_gain[] = {  
    BBR_UNIT * a,  // 1<a<2      
    BBR_UNIT * (2-a),      
    BBR_UNIT, BBR_UNIT, BBR_UNIT,      
    BBR_UNIT, BBR_UNIT, BBR_UNIT,  
    BBR_UNIT, BBR_UNIT, BBR_UNIT  // 新增2個週期  
};

這裏列舉幾個典型的a值:6/5,5/4,4/3,3/2。所須要的就是將a參數化。

        其次,因爲以上的討論都是基於PROBE_BW穩定狀態這種理想化場景的,然而現實中你沒法忽略STARTUP,DRAIN等狀態,爲了在測試中找到狀態瓶頸,包括帶寬利用最大化,收斂公平性,咱們把BBR的狀態機看做是一個馬爾科夫鏈:

 

 

之因此能夠創建這樣的模型,是由於BBR全權接管了全部計算窗口和帶寬的邏輯,所以這個轉換圖是閉合且不受外部干擾的。在獲得各類轉換機率後,咱們基本就能夠看出網絡的行爲了。
        好比,調優的目標是儘量讓BBR系統運行在PROBE_BW狀態,且在PROBE_BW內部,也有一個狀態機,咱們但願儘量的穩定在係數爲1的增益上運行。
        獲取了各類機率數據後,BBR的參數化調優方案就有了基調了。
        在上一節,咱們描述了BBR算法在穩定的PROBE_BW狀態的收斂性。如今,咱們來看一個異常的可是有廣泛意義的場景,那就是發生擁塞的時候,BBR如何表現,如何收斂。因爲BBR自己的宗旨就是消除隊列,咱們假設某個或者某些鏈接剛啓動時,在STARTUP狀態填充了隊列的一部分,此時在其進入DRAIN狀態以前,隊列一直是存在的,因爲隊列已經開始被填充,那麼已有的鏈接會在至關長的時間內沒法採集到更小的RTT,最終,它們幾乎會同時進入PROBE_RTT狀態!看BBR的PROBE_RTT實現,就知道在這個狀態中,cwnd會被瞬間縮減到4個MSS的大小!這會致使大量的網絡資源被騰出,而這些騰出的資源會被新鏈接共享,這就是STARTUP和PROBE_RTT狀態的公平性!
        可是,若是和CUBIC共享資源怎麼辦?!
        很遺憾,BBR沒法識別CUBIC的存在!當BBR將cwnd縮減的時候,CUBIC會繼續填充第二類緩存,直到透支掉最後的那一個字節。隨後,也許你會認爲CUBIC會執行乘性減來縮減cwnd,是的,確實如此,然而即便這樣,也不能期望它們會騰出帶寬,由於CUBIC的行爲是各自獨立的,你沒法假設它們會同時進入乘性減窗,所以幾乎能夠確定,共享鏈路上的緩存老是趨向與被填滿的狀態,這都是CUBIC的所爲。然而怎能怪它呢,畢竟它的基礎就是填滿全部兩類緩存爲止,決不降速(不一樣於BBR的發現排隊以前毫不減速的特性)。所以,BBR和CUBIC共存的時候,頗有可能會出現全盤皆輸的局面。
        怎麼緩解?!
        事實上,BBR不必對CUBIC過度謙讓。只要知足本身不排隊便可(由於排隊於人於己均無好處!)。所以大可沒必要將窗口降到4個MSS,直接降到一半便可,這也是沿襲了傳統乘性減的規則!此外,在PROBE_RTT階段,也不要在這個狀態運行太久,時間減半意思意思就好!爲此,很容易更改代碼:
0).定義一個新的fast_probe模塊參數
1).bbr_set_cwnd的修改:

if (bbr->mode == BBR_PROBE_RTT) { /* drain queue, refresh min_rtt */  
    if (fast_probe)  
        tp->snd_cwnd = max(tp->snd_cwnd >> 1, bbr_cwnd_min_target); // 取cwnd/2!  
    else  
        tp->snd_cwnd = min(tp->snd_cwnd, bbr_cwnd_min_target);  
}

 

2).bbr_update_min_rtt的修改:

if (!bbr->probe_rtt_done_stamp &&  
    tcp_packets_in_flight(tp) <= bbr_cwnd_min_target) {  
    bbr->probe_rtt_done_stamp = tcp_time_stamp +  
        msecs_to_jiffies(fast_probe?(bbr_probe_rtt_mode_ms>>1):bbr_probe_rtt_mode_ms);  
...  
}

 

這樣會再也不過度對CUBIC低頭示弱。經過上一節描述的BROBE_BW狀態的收斂過程,這種強勢的行爲並不影響多個同時運行BBR算法的TCP流之間公平性,它們之間的公平收斂,留到PROBE_BW狀態慢慢玩吧。至於和CUBIC之間的競爭,你不仁,我便不義了!

最後,咱們看一下STARTUP和DRAIN的增益係數,它們互爲倒數,怎麼填充就怎麼清空,完美回退。
/* We use a high_gain value of 2/ln(2) because it's the smallest pacing gain
 * that will allow a smoothly increasing pacing rate that will double each RTT
 * and send the same number of packets per RTT that an un-paced, slow-starting
 * Reno or CUBIC flow would:
 */
static const int bbr_high_gain  = BBR_UNIT * 2885 / 1000 + 1;
/* The pacing gain of 1/high_gain in BBR_DRAIN is calculated to typically drain
 * the queue created in BBR_STARTUP in a single round:
 */
static const int bbr_drain_gain = BBR_UNIT * 1000 / 2885;

咱們來嘗試調整這參數,能夠是不對稱的緩慢Drain,這是基於在降速排空隊列的過程當中,可能已經有別的鏈接出讓了帶寬!
 

5.CUBIC更改前奏-實現NCL(非擁塞丟包 Non-congestion Loss)

CUBIC還算是迄今比較偉大的算法,它不會輕易被BBR取代,可是它須要被改進。
        首先,在沒有AQM時,加性增乘性減自己並無錯,通常的丟包都是尾部擁塞丟包,這對於TCP擁塞控制而言,基於丟包的擁塞探測太容易作了,可是尾部丟包會帶來一系列的問題,爲了解決這些問題,出現了AQM,好比RED之類的丟包算法,這樣一來就沒法區別RED丟包,尾部丟包,線路噪聲丟包,亂序未丟包這幾類現象了。問題的嚴重性是由擁塞算法對丟包的敏感性形成的,只要有丟包,或者說僅僅是按照本身的邏輯檢測到了可能的丟包,就好像出了大事通常,窗口會大幅度降低!!然而,噪聲丟包和亂序並非擁塞,因此若是能過濾掉這兩類,CUBIC的效率必定會有大的提升!
        事實上,CUBIC算法沒有任何問題,問題出在Linux的TCP實現(其它系統估計也好不到哪去,你們都是仿照BSD實現的)的問題,形成不少時候,CUBIC愛莫能助,好不容易探測到一個合適的擁塞窗口,被dubious的ACK調用tcp_fastretrans_alert的PRR算法一會兒拉下去了,或者直接被定時器超時事件給拉下去...BBR之因此優秀,並非說其算法優秀,其獨到之處在於一切都是它本身來決定的,沒有誰能拉低BBR算出來的窗口和發送帶寬,除了它本身!因此說,BBR的優秀更多的指的是其框架的優秀。

        我不對CUBIC自己進行任何的改造,我只是解放它。首先要作的就是排除假擁塞丟包,要確保進入PRR邏輯的丟包都是因爲致使第二類緩存被填滿的擁塞避免引發的。至於說非擁塞丟包,繼續進入CUBIC邏輯,讓CUBIC獲取更多的權力。
        這裏介紹的一種方法是NCL機制。詳細文檔請參見《TCP-NCL: A Unified Solution for TCP Packet Reordering and Random Loss》。其基本思想就是,將重傳與擁塞控制Alert(即主動的擁塞控制邏輯調用,在Linux中表現爲tcp_fastretrans_alert以及tcp_enter_loss)邏輯分離,在確認真的擁塞以前不進入擁塞控制Alert邏輯。NCL是怎麼作到的呢?很簡單:

 

 

 

其各個例程的示意圖以下:

 

 

NCL的設計巧妙之處就是至關於爲丟包的擁塞探測設置了一個時間窗口,在標準的Linux TCP擁塞控制實現中,只要程序邏輯斷定發生了丟包,就會驚恐地呻吟着進入擁塞控制Alert階段,退出Open狀態,進入Recovery狀態PRR降窗,或者進入LOSS狀態窗口掉底。而NCL分離了丟包的性質判斷和擁塞控制Alert的調用,在以往,重傳定時器超時就意味着擁塞已經發生,然而NCL中卻能夠有一次過濾機會,即第一個RD定時器僅僅處理重傳邏輯,重傳後啓動的CD定時器超時後纔會判斷爲擁塞。其巧妙之處還在於RD,CD兩個定時器的超時時間選擇,其思想都在圖示的註釋裏。

相關文章
相關標籤/搜索