Google's BBR TCP擁塞控制算法的四個變速引擎

1.Linux TCP迄今爲止的擁塞控制機制

我並不瞭解其它平臺的TCP擁塞控制算法實現,可是我瞭解Linux的,迄今爲止,在bbr剛剛被引入以後,Linux的擁塞控制算法分爲兩類:算法

保守模式

bbr以前以Reno爲基礎,包括Reno,NewReno,...原理幾乎都不變,這些算法有兩個特色:
1).反饋性差
以Reno爲例,TCP發送端在擁塞避免階段收到ACK後,無條件地將cwnd增長1/cwnd,在慢啓動階段收到ACK後cwnd增長1,這是毫無根據的,然而Reno並無什麼更好的作法,只能瞎猜!後來的Westwood,Vegas,BIC等算法,相對Reno/NewReno更加智能了一步,但仍是傻瓜!再日後,CUBIC搞了一個高大上的以三次方程凸凹曲線來抉擇的增窗機制,看似十分地「博士」,而且十分地「經理」,然而仍是沒法高效利用互聯網的空閒帶寬,相反在碰到異常現象,好比丟包,擁塞的時候,反應太過保守,在保守的路線上趨於激烈,即激烈地保守下降擁塞窗口,更加可悲的是,這個窗口降低的過程並不受這些算法所控制。
2).擁塞算法被接管
在TCP擁塞控制機制發現丟包時(即RTO或者N次重複的ACK等),TCP會徹底接管擁塞控制算法,本身控制擁塞窗口。然而問題是,這種所謂的丟包可能並非真的丟包,這只是TCP認爲丟包而已,這是30年前的丟包判斷機制了...真的丟包了嗎?不必定啊!然而只要TCP認爲丟包,就會接管擁塞控制算法(起碼在Linux上是這樣...)。這使我不得開心顏!我曾經修改過Linux TCP的PRR邏輯,只求降窗過程不那麼猛而已...Linux TCP爲了這個降窗也是費盡心思,前後經歷了多種方案,好比Halving RatePRR等,唉,幹嗎不直接都交給擁塞控制算法呢??數組

        總的來說,bbr以前的擁塞控制邏輯在執行過程當中會分爲兩種階段,即正常階段和異常階段。在正常階段中,TCP模塊化的擁塞控制算法主導窗口的調整,在異常階段中,TCP核心的擁塞控制狀態機從擁塞控制算法那裏接管窗口的計算,在Linux的實現中,這是由如下邏輯表示的:安全

static void tcp_cong_control(struct sock *sk, u32 ack, u32 acked_sacked,  
                 int flag)  
{  
    if (tcp_in_cwnd_reduction(sk)) { // 異常模式  
        /* Reduce cwnd if state mandates */  
        // 在進入窗口降低邏輯以前,還須要tcp_fastretrans_alert來蒐集異常信息並處理異常過程。  
        tcp_cwnd_reduction(sk, acked_sacked, flag);  
    } else if (tcp_may_raise_cwnd(sk, flag)) { // 正常模式或者安全的異常模式!  
        /* Advance cwnd if state allows */  
        tcp_cong_avoid(sk, ack, acked_sacked);  
    }  
    tcp_update_pacing_rate(sk);  
}

 

是否進入tcp_cwnd_reduction的異常模式,是由下面的邏輯來判斷的:網絡

if (tcp_ack_is_dubious(sk, flag)) {  
    is_dupack = !(flag & (FLAG_SND_UNA_ADVANCED | FLAG_NOT_DUP));  
    tcp_fastretrans_alert(sk, acked, is_dupack, &flag, &rexmit);  
}

 

這個會讓TCP擁塞模塊怎麼想?!除了拋出一個ssthresh以外對異常的處理無能爲力,事實上這根本與它無關!app

全速模式

bbr以後以bbr爲基礎的算法,其核心並非bbr自己,而是bbr算法爲了運行對Linux TCP框架的修改!bbr並非最終的算法,更不是神話,它只是個開始,它把TCP的tcp_ack徹底改了,改了以後,若是你有什麼好的想法,即可以自由發揮了!
        都改了什麼呢?咱們看一下核心函數tcp_cong_control就知道了(我這裏不談2.6內核以及3.x內核中沒有抽離cong_control的版本):框架

static void tcp_cong_control(struct sock *sk, u32 ack, u32 acked_sacked,  
                 int flag, const struct rate_sample *rs)  
{  
    const struct inet_connection_sock *icsk = inet_csk(sk);  
    // 這裏是新邏輯,若是回調中宣稱本身有能力解決任何擁塞問題,那麼交給它!  
    if (icsk->icsk_ca_ops->cong_control) {  
        icsk->icsk_ca_ops->cong_control(sk, rs);  
        // 直接return!TCP核心再也不過問。  
        return;  
    }  
    // 這是老的邏輯。  
    if (tcp_in_cwnd_reduction(sk)) {  
        /* Reduce cwnd if state mandates */  
        // 若是不是Open狀態...記住,tcp_cwnd_reduction並不受擁塞控制算法控制!!  
        tcp_cwnd_reduction(sk, acked_sacked, flag);  
    } else if (tcp_may_raise_cwnd(sk, flag)) {  
        /* Advance cwnd if state allows */  
        tcp_cong_avoid(sk, ack, acked_sacked);  
    }  
    tcp_update_pacing_rate(sk);  
}

 

bbr算法實現了cong_control回調函數,而你可能實現一個別的,寫在cong_control中。
        理解了現狀,固然就知道問題之所在了,bbr解決了問題。固然,Appex也解決了問題,可是那幫人是傻逼!這不符合耶穌的邏輯,Appex會受到詛咒。異步

2.bbr並不基於預測

不基於數據反饋的預測都是假的,不真實的,騙人的。這就是爲何基於大數據人工智能比基於算法的人工智能更厲害的緣由,算法再猛都是扯淡,只有數據才能訓練出聰明的模型。在TCP擁塞控制算法領域,CUBIC的三次凸凹函數看起來是那麼的晦澀卻顯得高大上,大多數人無力深究這個算法的細節,然而bbr算法倒是一個隨便什麼人都看得懂的....tcp

        bbr不斷採集鏈接內時間窗口內的最大帶寬max-bw和最小RTT min-rtt(見下面的win_minmax),並以此計算髮送速率和擁塞窗口,依據反饋的實際帶寬bw和max-rtt調節增益係數。其背後的思想是,一旦在一個時間窗口內採集到更大的帶寬和更小的rtt,bbr就認爲客觀上它們的乘積,即BDP是能夠填充的客觀管道容量,並按此做爲基準,萬一沒有達到,bbr會認爲這是發生了擁塞,調小增益係數便可,但在時間窗口範圍內並不改變基準(時間局部性使然),因爲增益係數是根據反饋調節的且基準BDP不變,一旦擁塞緩解,bbr能夠第一時間發現並增大增益係數!模塊化

        bbr能作到以上這些並工做地很好,全在於tcp擁塞狀態機控制邏輯不會打擾它的行爲,而這些在以前的擁塞控制算法中幾乎是作不到的,好比CUBIC算法,一旦丟包,CUBIC便會被接管,直到Linux按照硬性標準判斷其TCP擁塞狀態已經恢復到了Open狀態。
        此外,更加劇要的,bbr算法採用真實的值,它的帶寬bw是真實測量出來的(測量方法見《來自Google的TCP BBR擁塞控制算法解析》),而RTT則包含了原始數據包的RTT以及SACK數據包的RTT等全部能夠測量的RTT,然而咱們沒法預測一個TCP鏈接的生命週期到底有多久,爲了讓採樣結果更加平滑,幾乎全部的算法都會採用「移動指數平均」的方案,RTO爲了展現波動的影響,加入了一個srtt的相似方差的值,可是這些都是不真實的。brr算法採用了真實的值!
        咱們知道移動指數平均是不真實的,咱們一樣知道間隔久遠的兩個採樣值之間幾乎沒有關聯的,如何來解決長時間的波動性呢?這就是將要談到的win_minmax機制。函數

3.win_minmax原理

在bbr中,全部提到的關於最大值最小值的採集都是採用win_minmax機制獲得的,在個人文章中,在我提到最大帶寬和最小RTT的時候,指的並非TCP鏈接的生命週期內的最大帶寬和最小RTT,而是指的基於win_minmax的最大值和最小值。
        win_minmax的原理很是簡單,簡單來說,它能夠記錄一個時間窗口內的一個變量的最大值或者最小值,並無使用任何平均值,一切都是真實的原始值。之因此說一個時間窗口內的最大值或者最小值是可信的,背後的思想是時間空間局部性原理,可計算的馬爾科夫到達過程能夠說明時間局部性,而互聯網的任何角落都不會忽然產生或者吞噬大量的流量則表徵了空間局部性,利用局部性原理,咱們就能夠說「3秒或者5秒內的最大帶寬或者最小RTT是能夠達到的。」
        win_minmax下的最大值和最小值計算也是很是簡單的,若是沒有時間窗口限制,那麼若是要求得帶寬的最大值,就要在TCP鏈接生命週期內持續冒泡,直到鏈接結束才能得出結果,因爲大多數時候不得不採用移動指數平均,那麼偶爾一次的噪點可能會影響後續結果的真實性,win_minmax則不一樣,它沒必要在鏈接內持續冒泡,只要在時間窗口內的3個值中作比較便可。這是bbr所謂的事不過三的第一例。
        win_minmax背後的思想是,好久以前(好比時間局部性以外的5分鐘以前)的最大值或者最小值對於一個控制系統(好比TCP)來說是沒有意義的,可是又不想讓這種值影響到我(無疑使用移動指數平均算法是確定會影響的)。
        win_minmax的代碼不到100行,很是簡單,請自行查閱lib/win_minmax.c文件。
        值得注意的是,bbr的實現中,最小RTT並無採用win_minmax的算法,咱們從其結構體的註釋中能夠看到:

/* BBR congestion control block */  
struct bbr {  
    u32    min_rtt_us;            /* min RTT in min_rtt_win_sec window */  
    u32    min_rtt_stamp;            /* timestamp of min_rtt_us */  
    u32    probe_rtt_done_stamp;   /* end time for BBR_PROBE_RTT mode */  
    struct minmax bw;    /* Max recent delivery rate in pkts/uS << 24 */  
    ...  
};

 

咱們注意表示最小RTT和最大帶寬的min_rtt_us和bw字段,後者就是一個win_minmax變量,而前者只是一個u32類型變量。可是看min_rtt_us的註釋,就會發現它確實不是鏈接內的最小值,而只是在時間窗口min_rtt_win_sec內的最小值,然而全文搜索窗口min_rtt_win_sec,並無找到其定義!這樣的歧義性固然不能讓人滿意,因而找一下更新這個RTT的代碼:

/* Track min RTT seen in the min_rtt_win_sec filter window: */  
filter_expired = after(tcp_time_stamp,  
               bbr->min_rtt_stamp + bbr_min_rtt_win_sec * HZ);  
if (rs->rtt_us >= 0 &&  
    (rs->rtt_us <= bbr->min_rtt_us || filter_expired)) {  
    bbr->min_rtt_us = rs->rtt_us;  
    bbr->min_rtt_stamp = tcp_time_stamp;  
}

 

咱們看到,bbr_min_rtt_win_sec就是bbr結構體裏註釋說的min_rtt_win_sec,默認是10秒。以上的這段代碼表示:
1).在時間窗口min_rtt_win_sec內,冒泡取最小值更新min_rtt_us;
2).一旦超過了一個min_rtt_win_sec時間窗口週期,無條件用新值更新min_rtt_us,開啓新一輪冒泡。

這就是所說的」min RTT in min_rtt_win_sec window「。
        至於rs->rtt_us的採集,爲何不直接取系統的srtt呢?並不屬於本節win_minmax的原理的範疇,我單獨整理了附錄,見本節附錄。
        到這裏,你的疑點多是,爲何不用win_minmax來實現」min RTT in min_rtt_win_sec window「的採集機制呢?這個問題的答案最終能夠揭開win_minmax的終極面紗!
        要理解這個,首先你要知道,爲何」最大帶寬「的值就能夠用win_minmax來計算,而後再考慮一下最小RTT是用來幹什麼的,基本就知道答案了!前文提到win_minmax的基本原理,它的做用是」保存「一個時間窗口內的」最有意義的最大值或者最小值「,在時間流逝的過程當中,時間窗口不斷向後滑動,基於win_minmax,在須要時間窗口內極值的時候,你只要取保存的3個值中的第一個值就行了。win_minmax算法自己保證了時間窗口隨着時間的滑動已經窗口內極值的冒泡更新,以下圖所示:

 

 

對於最大帶寬而言,bbr只是」記錄並使用「,相似LRU。

        而對於最小RTT則徹底不一樣,最小RTT的做用除了計算窗口以外,還有一個做用就是觸發PROBE_RTT引擎(參見第6節)的運做,所以,對於」min RTT in min_rtt_win_sec window「中的min_rtt_win_sec而言,在採集到新的最小RTT前,窗口是不會向前滑動的,窗口的不自動滑動最終會觸發一個」過時「事件,而這個過時事件正是切換到PROBE_RTT引擎的契機!好了,咱們如今能夠總結一下最大帶寬的採集和最小RTT採集之間的不一樣了:

最大帶寬採集:使用窗口隨着時間自動滑動的win_minmax算法進行更新。
最小RTT採集:使用不會隨時間自動滑動的窗口機制,僅在最小RTT更新時滑動,在時間觸及窗口邊緣時觸發超時事件。

因此說,最小RTT的採集並不使用win_minmax!
 

附:關於rs->rtt_us的採集

一個最大的疑問就是,爲何不直接使用系統的srtt來更新bbr的min_rtt_us?
        答案很簡單,由於srtt是通過移動指數平均的,雖然已通過濾了噪點,但仍是會受到噪點的影響,這裏的噪點問題主要是BufferBloat的影響,所以這個srtt並不受bbr信任!此外,系統的srtt計算時並不會針對被SACK的數據包採集RTT樣本,而bbr並不在意ACK和SACK,不在意丟包和亂序,因此bbr必須本身維護一套採集真實RTT(起碼相對真實)的方法。
        咱們看到,bbr的min_rtt_us來自rate_sample結構體rtt_us的賦值,咱們也能夠看到,rs->rtt_us的值是在TCP主邏輯收到ACK的時候更新的,位於tcp_clean_rtx_queue函數中被更新:

/* 若是這是被正常順序ACK的數據包,那麼ca_rtt_us就取當前時間和最後被ACK的數據包發送時間之差。*/  
if (likely(first_ackt.v64) && !(flag & FLAG_RETRANS_DATA_ACKED)) {  
    seq_rtt_us = skb_mstamp_us_delta(now, &first_ackt);  
    ca_rtt_us = skb_mstamp_us_delta(now, &last_ackt);  
}  
/* 若是數據是被SACK的,那麼一樣,ca_rtt_us也是取當前時間與最後被SACK的時間之差 */  
if (sack->first_sackt.v64) {  
    sack_rtt_us = skb_mstamp_us_delta(now, &sack->first_sackt);  
    ca_rtt_us = skb_mstamp_us_delta(now, &sack->last_sackt);  
}  
sack->rate->rtt_us = ca_rtt_us; /* RTT of last (S)ACKed packet, or -1 */

以上的代碼很好理解,註釋已經很清楚了。

 

4.bbr的4個核心引擎

理解了基本思想,就能夠展現bbr核心的引擎了!在展現以前,要明白的是,在TCP鏈接開始引擎發動以後,就不會有人去打擾它了!它會一直運行到TCP鏈接的結束。計算遇到丟包,就算遇到亂序,bbr也是全速發送數據。請看下面的註解:
「就算已經出現了丟包,但bbr並不在意,這並不屬於它管轄,bbr僅僅要求有數據可發就好,換句話說,它相信tcp的擁塞狀態機控制邏輯能夠把數據準備好,無論是新數據仍是標記爲Lost的重傳數據」
標記Lost並不歸bbr管轄,但若是標記Lost很高效的話,無疑是一種好事,由於它會給bbr提供要發送的數據包(無論是新數據仍是重傳數據)!而RACK幫bbr完成了這一切!但要記住,這並非必須的。
        bbr的4個引擎就是4個狀態機,這些原本是屬於一個總體的,若是畫在一塊兒一張圖,那簡直就是一個8缸引擎了,可是那樣有點太複雜,因此我把它們拆開了了,用筆連在一塊兒的話,就是所有。咱們從STARTUP引擎開始。
注意:
1).陰影的方框裏表示的是全局字段,屬於一個TCP鏈接的生命週期;
2).全部的MAX/MIN均採用win_minmax算法(但請注意最小RTT在實現上與最大帶寬的不一樣);
3).每一個引擎中,相似門電路,從上到下的空間順序表示了計算髮生的時間順序,好比越在上面的邏輯越早發生。

4.1.STARTUP引擎細節

這個引擎以下圖所示:

 

 

這是一個快加速階段,再也不像傳統TCP慢啓動那般盲目。

4.2.DRAIN引擎細節

這個狀態比較有意思,DRAIN是爲了排空什麼呢?先看下邏輯框圖:

 

 

之因此要DRAIN,是由於在進入DRAIN狀態以前多發了一些數據形成了擁塞,吃了纔會拉...那麼多發了哪些呢?仔細看STARTUP圖示邊上的註釋也許你就明白了。

4.3.PROBE_BW引擎細節

圖示以下:

 

 

這是一個穩定狀態,該狀態很是重要,至關於你在告訴公路上跑得最歡的時候那種狀態。注意圖中的那個順序循環的8個增益係數,在代碼中表現爲一個增益係數數組:

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... */  
};

 

這個數組中,6個增益係數都是1,也就是說,6種狀況下都是徹底基於反饋回來的在10輪bbr週期內最大帶寬和最小RTT來設置速率和cwnd(在bbr的當前實現中,處在PROBE_BW狀態時,計算cwnd使用的增益係數並非bbr_pacing_gain數組定義的,而是固定的2*BBR_UNIT,可是我我的感受仍是使用另外一個數組比較好...)的。另外兩個增益係數,一個是5/4,這個值大於1,如註釋所說,這是爲了探測更多的帶寬,即時利用其它TCP清空的帶寬,另一個小於1的增益係數則是爲了收斂,這是否是跟高速公路上開車同樣呢?雖然油門和剎車就在腳下,但也只是將腳輕觸其上,時不時看車少了,加下速,偶爾看到前面車子愈來愈近了,也會稍微減速,可是除非嚴重擁堵,速率幾乎是勻速的。
        好了,看到這裏,一種TCP加速方案在中國好司機的內心便生成了,即將增益係數改的大一些,好比5/4改爲6/4,將後面6個1改幾個爲大於1的...

4.4.PROBE_RTT引擎細節

請參見第6節!

 

將以上4個引擎鏈接起來,bbr實現的狀態機就徹底有了。

5.bbr全速模式的含義

通常而言,汽車在告訴公路上飛馳的感受和在城市幹道蠕動的感受是徹底不一樣的。以手動檔車子爲例,在城市幹道開車,會很是累,由於要常常等紅燈,常常經歷擁堵,...換擋,剎車,油門,離合...搞很差就出事故了...然而在高速公路上,倒是很是輕鬆,基本上在過了收費站以後,一邊腳踩油門,一邊眼觀即時流量,加速到比較適合的速度後,而後剎一下車,此後基本不用再加速減速了,除非偶爾發現前車太慢,剎一下車,或者看到前面沒車,踩一腳油門...bbr就是這麼個情形,徹底根據上次的反饋來調節此次的行爲。
        bbr算法消除了沒必要要的鋸齒。這種鋸齒在bbr以前簡直就是TCP的動力源,各類算法盲目地增窗,一旦TCP認爲丟包發生(雖然可能並非真的丟包。因此纔有了各類愈來愈複雜的機制,好比DSACK之類的...),在留下一個幾乎拍腦殼拍出來的ssthresh以後,全部邏輯均被接管,而這裏就是鋸齒的齒尖之所在。事實上,鋸齒是因爲TCP擁塞狀態機控制邏輯和TCP擁塞控制算法之間在擁塞事件發生時「工做交接」而造成的,bbr算法中取消了這種沒必要要的交接,所以鋸齒也天然變鈍甚至磨平了。
        不是Vegas,CUBIC等沒法發現擁塞,是TCP並不將權力全權交給它們從而致使的Vegas,CUBIC等如此眼瞎如此盲目。這事實上多是最初的TCP實現中的作法,好比ssthresh這個概念,事實上不少算法中並不須要這個東西,只是爲了迎合「大師的標準」罷了。bbr沒有使用ssthresh(ssthresh體現了擁塞算法與TCP擁塞狀態機之間的耦合,bbr沒有這種耦合,因此不須要ssthresh)。
 

6.關於bbr週期和PROBE_RTT狀態

最後,PROBE_RTT這個狀態比較特殊!之因此這麼說是由於其它任何狀態都是能夠進入PROBE_RTT狀態的,進入這個狀態的條件徹底是異步的,即:
連續設定好的時間窗口內(即min_rtt_win_sec,默認10秒)沒有采集到新的最小RTT,就會進入PROBE_RTT狀態。這個時間段是能夠配置的,默認是10秒鐘...這貌似有點過久了...
這句話的意思是說,連續一段時間(min_rtt_win_sec窗口內)採集到的RTT均比系統已保存的最小RTT(上個min_rtt_win_sec窗口內的最小RTT)更大,這說明了什麼?這說明十有八九發生了擁塞,既然最小RTT曾經達到過,那麼它就是可達到的,現在沒有達到,那必定是發生了什麼事情阻止系統RTT達到最小RTT。這個事件必定是擁塞!要知道,噪聲丟包是不會延長RTT的!
        請注意如下這個任何引擎中都存在的局部圖放大後的樣子:
 

這個圖裏實際上存在着不少東西,只是我實在不知道該怎麼畫出來它。也就是說,任何引擎在運行過程當中均可能會切換到PROBE_RTT,這就好像隨時剎車減速同樣。

....
在PROBE_RTT狀態中,cwnd的值會保持一個低值,目標是避免丟包(其實能夠說已經檢測到了擁塞!),那麼何時擺脫這個PROBE_RTT狀態呢?bbr在這裏採用了保守的作法,即採用了事先設置好的「時間段」,在超過這個時間段後再進行一次判斷,若是TCP鏈接當前已經佔滿了網絡管道,那麼就會再次進入PROBE_BW,即準勻速狀態,若是沒有達到網絡的滿載狀態,那麼就會進入STARTUP狀態:

static void bbr_reset_mode(struct sock *sk)  
{  
    if (!bbr_full_bw_reached(sk))  
        bbr_reset_startup_mode(sk);  
    else  
        bbr_reset_probe_bw_mode(sk);  
}

 

到底會進入PROBE_RTT狀態多久呢?最短也要是bbr_probe_rtt_mode_ms(200ms by default)。
        在這裏再次重申一下,所謂網絡管道是否滿載的判斷是當前時間窗口內的最大帶寬值來計算的,與當前狀態無關。爲了不文字進一步拖沓,我只能將代碼列以下:

static bool bbr_full_bw_reached(const struct sock *sk)  
{  
    const struct bbr *bbr = inet_csk_ca(sk);  
  
    return bbr->full_bw_cnt >= bbr_full_bw_cnt; // 其實能夠將bbr_full_bw_cnt當作是3!  
}  
static void bbr_check_full_bw_reached(struct sock *sk,  
                      const struct rate_sample *rs)  
{  
    struct bbr *bbr = inet_csk_ca(sk);  
    u32 bw_thresh;  
  
    // 注意round_start的計算!  
    if (bbr_full_bw_reached(sk) || !bbr->round_start || rs->is_app_limited)  
        return;  
  
    // 這裏是一個簡單的冒泡法,只要不是連續的帶寬增加小於25%,那麼就將計數「不增加閾值」加1,事不過三,超過三次,切換到DRAIN。  
    bw_thresh = (u64)bbr->full_bw * bbr_full_bw_thresh >> BBR_SCALE;  
    if (bbr_max_bw(sk) >= bw_thresh) {  
        bbr->full_bw = bbr_max_bw(sk);  
        bbr->full_bw_cnt = 0;  
        return;  
    }  
    ++bbr->full_bw_cnt;  
}

 

上述代碼描述了「如何判斷帶寬已滿載」。代碼比較簡單,只是那個round_start賦值比較模糊(也許你不以爲模糊,但我在剛看bbr的時候以爲這裏比較鬆散...)。關於bbr週期的一些機制,我在這裏簡述一二。咱們先看bbr週期的概念:
「可測量」的一個「發送/接收」週期。
注意,「可測量」三個字特別關鍵!有人會認爲一個週期就是一個SRTT,可是RTT是可測量的嗎?No!SRTT是猜的,其結果大部分都是騙人的!所以RTT並非可測量的(即使是啓用時間戳的鏈接,也不能控制接收端Delay ACK...)那麼什麼是可測量的呢?
        bbr採用可一種實實在在的方式,咱們來看一個週期開始和截止時的斷定代碼:

/* See if we've reached the next RTT */  
if (!before(rs->prior_delivered, bbr->next_rtt_delivered)) {  
    bbr->next_rtt_delivered = tp->delivered;  
    bbr->rtt_cnt++;  
    bbr->round_start = 1;  
    bbr->packet_conservation = 0;  
}

以上代碼若是if語句成立,就意味着進入了一個新的週期,其關鍵點在於三個變量:

tp->delivered:當前被ACK或者SACK的最大值(與序列號無關,只是標量計數)。它會被賦值給錨點以及緊接着被髮送的數據包的scb,賦值給scb的delivered字段。
bbr->next_rtt_delivered:中間變量錨點。保存一個週期開始時被ACK或者SACK的最大值。
rs->prior_delivered:當前被ACK的數據包在發送的時候,被ACK或者SACK的數據最大計數值。當前被ACK或者SACK的數據包的scb中保存有delivered值,賦值給rs->prior_delivered。
基於以上解釋,咱們很容易明白一個「bbr週期」的概念,即「當前發送的包開始的時間到此包被ACK或者SACK的時間之間的差」!很符合RTT的概念,不是嗎?
        有了這個「bbr週期」的概念,就能夠很好的理解以上「事不過三」的意思了,一次探測須要一個週期,這個週期內給接收端時間增加接收窗口。

7.全速bbr的依託

話說bbr能夠運行在全速模式下,一組變速引擎提供源源不斷的強勁動力,然而單單靠承諾的動力還不行,這種承諾的標稱功率以外,能源纔是最重要的!TCP的能源就是數據包!不斷的數據包觸發接收端回覆ACK,不斷的ACK反饋到發送端,做爲時鐘驅動更多的數據包發送...         TCP內部還有一個動力源,那就是滑動窗口,若是窗口再也不滑動,那麼動力就會消失,數據將沒法發送。引起窗口再也不滑動的緣由就是不連續丟包形成的空洞,這些空洞被補上促使接收端按序接收以前,窗口會一直呈卡死狀態!         問題是,如何填補空洞?         OK,固然是快速重傳(而不是超時重傳或者TLP之類...),但是,快速重傳只會將數據包重傳一次!若是重傳的數據包又丟了怎麼辦?!OK,還有LOST Retransmit機制(重傳過被斷定丟失的數據包再重傳一遍!),但是要想使用LOST Retransmit機制須要知足的條件比較苛刻,好比,只要檢測到亂序,就不會進入這個邏輯(TCP的保守性格)...總之,你能夠認爲,快速重傳只會將數據包重傳一遍,若是再丟了,就只能等超時了。         有沒有什麼辦法能夠快速檢測到重傳數據的丟失呢?若是有這樣的機制的話,能夠將這些數據包進行LOST標記,而後就能夠爲bbr引擎喂入能源了。這種機制固然有,那就是RACK機制!

相關文章
相關標籤/搜索