天天看點

使用TCP時序圖解釋BBR擁塞控制算法的幾個細節0.說明1.延遲ACK以及ACK丢失并不會影響TCP的傳輸速率2.為什麼BBR要把計算出來的BDP乘以2作為擁塞視窗值?3.為什麼在探測最小RTT的時候最少要保持4個資料包4.用時序圖總覽一下BBR的Startup/Drain/ProbeBW階段5.Startup階段擁塞視窗計算的滞後性6.由ACK通告的接收視窗還有意義嗎?7.BBR在計算擁塞視窗時其它的關鍵點8.關于我的Pacing發送引擎結語

周六,由于要趕一個月底的Deadline,是以選擇了在家VPN加班,大半夜就爬起來跑用例,抓資料...自然也就沒有時間寫文章和外出耍了...不過利用周日的午夜時間(不要問我為什麼可以連續24小時不睡覺,因為我覺得吃飯睡覺是負擔),我決定把工作上的事情先放下,還是要把每周至少一文補上,這已經成了習慣。由于上周實在太忙亂,是以自然根本沒有更多的時間去思考一些“與工作無關且深入”的東西,我指的與工作無關并非意味着與IT,與網際網路無關,隻是意味着不是目前我在做的。比如在兩年前,VPN,PKI這些是與工作有關的,而現在就成了與工作無關的,古代希臘羅馬史一直都是與工作無關的,直到我進了羅馬曆史研究相關的領域去領薪資,直白點說,老闆不為之給我支付薪水的,都算是工作無關的東西。玩轉與思考這些東西是比較放得開的,不需要對誰負責,沒有壓力,沒有KPI,沒有Deadline,完全自由的心态對待之,說不定真的很容易獲得真知。

       我認識一個草根鼓手朋友,玩轉爵士鼓的水準遠高于那些所謂的專業鼓手,自然帶有一種俠客之風傳道授業解惑,鼓槌随心所欲地揮舞在他自己的心中,沒有任何負擔和障礙,任何的節奏都可以一氣呵成,從來不打重複鼓點,那叫一個帥!然而他并非專業考級出來的,是拜師出來後自己摸索的。

       要興趣去自然揮灑,而不是迫于壓力去應對。

       我也是鼓手,但我打的不是爵士鼓,我是鼓噪者,技術的鼓噪者。本文與TCP BBR算法相關。

0.說明

BBR熱了一段時間後終于回歸了理性,這顯然要比過熱地炒作要好很多。這顯然也是我所期望的。

       本文的内容主要解釋一些關于BBR的細節問題。這些問題一般人可能不會關注,但是針對這些問題仔細思考的話,會得到很多有用的東西。在解釋這些問題時,我依然傾向于使用圖解的方式,但這一次我不再使用Wireshark的tcptrace圖了,而是使用時序圖的方式,因為這種時序圖既然能夠令人一目了然地解釋TCP三向交握,四次分手,TIME-WAIT等,那它自然也能解釋更複雜的機制,比如說擁塞控制。

1.延遲ACK以及ACK丢失并不會影響TCP的傳輸速率

在大的時間尺度上看,延遲ACK以及ACK丢失并不會對速率造成任何影響,比如一個檔案4個TCP段正好發完,即便前面幾個ACK全部丢失,隻有最後一個到達,那它的傳輸總時間也是不變的。

       但是在細微的時間段内,由于延遲ACK或者ACK丢失帶來的時間偏差卻是不可忽略的。

       首先我們再次看一下BBR是如何測量即時速率的。測量即時速率需要做一個除法,分子是一段時間内成功到達對端的資料包總量,分母就是這段時間。BBR會在每收到一次ACK的時候測量一次即時速率。計算需要的資料分别在資料傳輸和資料被ACK的時候采樣。很顯然,我們可以想當然地拍腦袋得出一個算法:

設資料包x發出的時間為t1,資料包x被應答的時間為t2,則在資料包x被應答時采集的即時速率為:

Rate=(從x被發出到x被應答之間一共ACK以及SACK了多少個資料包)/(t2-t1)

但是這會造成什麼問題呢?這會造成誤差。如下圖所示:

使用TCP時序圖解釋BBR擁塞控制算法的幾個細節0.說明1.延遲ACK以及ACK丢失并不會影響TCP的傳輸速率2.為什麼BBR要把計算出來的BDP乘以2作為擁塞視窗值?3.為什麼在探測最小RTT的時候最少要保持4個資料包4.用時序圖總覽一下BBR的Startup/Drain/ProbeBW階段5.Startup階段擁塞視窗計算的滞後性6.由ACK通告的接收視窗還有意義嗎?7.BBR在計算擁塞視窗時其它的關鍵點8.關于我的Pacing發送引擎結語

BBR如果依賴這種即時的速率測量機制來運作的話,在ACK丢失或者延遲ACK的情況下會造成測量值偏高。舉一個簡單的例子:

使用TCP時序圖解釋BBR擁塞控制算法的幾個細節0.說明1.延遲ACK以及ACK丢失并不會影響TCP的傳輸速率2.為什麼BBR要把計算出來的BDP乘以2作為擁塞視窗值?3.為什麼在探測最小RTT的時候最少要保持4個資料包4.用時序圖總覽一下BBR的Startup/Drain/ProbeBW階段5.Startup階段擁塞視窗計算的滞後性6.由ACK通告的接收視窗還有意義嗎?7.BBR在計算擁塞視窗時其它的關鍵點8.關于我的Pacing發送引擎結語

那麼,BBR是如何做到不引入這種誤差進而精确測量即時速率的呢?很簡單,将t1改成至資料包x發出時為止,最後一個(S)ACK收到的時間即可。

       詳情請參考核心源碼的net/ipv4/tcp_rate.c檔案,原理非常簡單。

       是以說,BBR的速率測量值并不受延遲ACK,ACK丢失的影響,其測量方法是妥當的。之是以上面給出一個錯誤的方法,是想展示一下什麼樣的做法是不妥當的,以及容易引起質疑的點在哪裡。

       結論很明确,延遲ACK,ACK丢失,并不影響BBR速率的采集值。

       接下來談第二個問題,關于BBR的擁塞視窗大小的問題。

2.為什麼BBR要把計算出來的BDP乘以2作為擁塞視窗值?

這個問題可以換一種問法,即BBR的bbr_cwnd_gain值如何解釋:

/* The gain for deriving steady-state cwnd tolerates delayed/stretched ACKs: */
static const int bbr_cwnd_gain  = BBR_UNIT * 2;           

我們知道,BBR将Pacing Rate作為第一控制要素,按照計算得到的Pacing Rate平緩地發送資料包即可,既然是這樣,擁塞視窗的存在還有何意義呢?

       BBR的擁塞視窗控制已經退化到了規定一個限額,它主要是為了灌滿管道,解決由于ACK丢失導緻的無包可發的問題。

       我先來闡述問題。

       BBR第一次把速率控制計算和實際的傳輸相分離,又一個典型的控制面與資料面相分離的案例。也就是說,BBR核心子產品計算出一個速率,然後就把資料包扔給Pacing發送引擎子產品(目前的實作是FQ,我自己也實作了一個),具體何時發送由Pacing發送引擎來控制,二者之間通過一個發送緩沖區來互動,具體結構如下圖:

使用TCP時序圖解釋BBR擁塞控制算法的幾個細節0.說明1.延遲ACK以及ACK丢失并不會影響TCP的傳輸速率2.為什麼BBR要把計算出來的BDP乘以2作為擁塞視窗值?3.為什麼在探測最小RTT的時候最少要保持4個資料包4.用時序圖總覽一下BBR的Startup/Drain/ProbeBW階段5.Startup階段擁塞視窗計算的滞後性6.由ACK通告的接收視窗還有意義嗎?7.BBR在計算擁塞視窗時其它的關鍵點8.關于我的Pacing發送引擎結語

可見,擁塞視窗控制的是“到底扔多少資料到發送緩沖區合适”的。接下來的問題顯然就是,擁塞視窗到底是多少合适呢?

       雖然BBR分離了控制邏輯和資料發送邏輯,但是TCP的一切都是ACK時鐘驅動的,如果ACK該來的時候沒有來,比如說丢了,比如延遲了,那麼就會影響BBR整個核心的運作,進而影響Pacing發送引擎的資料發送動作,BBR要做的是,即便沒有ACK來驅動,也可以自行發送本該發送的資料包,是以Pacing發送引擎的發送緩沖區的意義重要,說白了就是,發送緩沖區裡一定要有足夠的資料包才行,就算ACK沒有來,引擎還是有包可發的。

       下面來展示一幅圖:

使用TCP時序圖解釋BBR擁塞控制算法的幾個細節0.說明1.延遲ACK以及ACK丢失并不會影響TCP的傳輸速率2.為什麼BBR要把計算出來的BDP乘以2作為擁塞視窗值?3.為什麼在探測最小RTT的時候最少要保持4個資料包4.用時序圖總覽一下BBR的Startup/Drain/ProbeBW階段5.Startup階段擁塞視窗計算的滞後性6.由ACK通告的接收視窗還有意義嗎?7.BBR在計算擁塞視窗時其它的關鍵點8.關于我的Pacing發送引擎結語

如果這個圖有不解之處,像往常一樣,大家一起讨論,但總的來講,我覺得問題不大,是以說才會基于上圖産生了下圖:

使用TCP時序圖解釋BBR擁塞控制算法的幾個細節0.說明1.延遲ACK以及ACK丢失并不會影響TCP的傳輸速率2.為什麼BBR要把計算出來的BDP乘以2作為擁塞視窗值?3.為什麼在探測最小RTT的時候最少要保持4個資料包4.用時序圖總覽一下BBR的Startup/Drain/ProbeBW階段5.Startup階段擁塞視窗計算的滞後性6.由ACK通告的接收視窗還有意義嗎?7.BBR在計算擁塞視窗時其它的關鍵點8.關于我的Pacing發送引擎結語

該圖示中,我把TCP層的BBR核心子產品和FQ的發送子產品都畫了出來,這樣我們可以清晰看出擁塞視窗的作用。實際上,BBR核心子產品按照擁塞視窗即inflight的限制,将N個資料包注入到Pacing發送引擎的發送緩沖區中,這些包會在這個緩沖區内部排隊,最終在輪到自己的時候被發送出去。由于這個緩沖區裡有足夠的資料包,是以即使是ACK丢失了多個,或者接收端有LRO導緻ACK被大面積聚集且延遲,發送緩沖區裡面的資料包也足夠發送一陣子了。

       維護這麼一個發送緩沖區的好處是在緩沖區不溢出(為什麼不溢出?那是算出來的,正好兩倍)的前提下時時刻刻有包可發,然而代價也是有的,就是增加了RTT,因為在發送緩沖區裡排隊的時間也要被算在RTT裡面的。不過這無所謂,這并不影響性能,資料包不管是在TCP層的發送隊列裡,還是在FQ的隊列裡,最終都是要發出去的。值得注意的是,本地的FQ隊列和中間節點的隊列性質完全不同,本地的隊列是獨占的,主動的,而中間節點隊列是共享的,被動的,是以這裡并沒有因為RTT的增加而損失性能。這就好比你有一張銀行卡專門用來還房貸,由于利息的浮動,所有每月還款金額不同,為了不欠款,你每個月總是要存進足額的錢進去,一般要遠多于平均的還貸額度才最保險,但這并不意味着你多存了錢這些錢就虧了,在還清貸款之前,存進去的錢早晚都是要還貸的。

3.為什麼在探測最小RTT的時候最少要保持4個資料包

首先要注意的是,用1個包去探測最小RTT會更好,然而效率可能會更低;用5個包去探測最小RTT效率更好,但是可能會導緻排隊,為什麼4個包不多也不少呢?

       我嘗試用一個時序圖來說明問題:

使用TCP時序圖解釋BBR擁塞控制算法的幾個細節0.說明1.延遲ACK以及ACK丢失并不會影響TCP的傳輸速率2.為什麼BBR要把計算出來的BDP乘以2作為擁塞視窗值?3.為什麼在探測最小RTT的時候最少要保持4個資料包4.用時序圖總覽一下BBR的Startup/Drain/ProbeBW階段5.Startup階段擁塞視窗計算的滞後性6.由ACK通告的接收視窗還有意義嗎?7.BBR在計算擁塞視窗時其它的關鍵點8.關于我的Pacing發送引擎結語

可見,4個包的視窗是合理的,infilght分别是:剛發出的包,已經到達接收端等待延遲應答的包,馬上到達的應答了2個包的ACK。一共4個,隻有1個在鍊路上,另外1個在對端主機裡,另外2個在ACK裡。路上隻有1個包,這絕對合理,如果一條路連1個包都容納不下了,那還玩個屎啊!

       以上的論述,僅僅為了幫大家了解以下一段注釋的深意:

/* Try to keep at least this many packets in flight, if things go smoothly. For
 * smooth functioning, a sliding window protocol ACKing every other packet
 * needs at least 4 packets in flight:
 */
static const u32 bbr_cwnd_min_target = 4;           

4.用時序圖總覽一下BBR的Startup/Drain/ProbeBW階段

我以下面的時序圖展示一下BBR的流程:

使用TCP時序圖解釋BBR擁塞控制算法的幾個細節0.說明1.延遲ACK以及ACK丢失并不會影響TCP的傳輸速率2.為什麼BBR要把計算出來的BDP乘以2作為擁塞視窗值?3.為什麼在探測最小RTT的時候最少要保持4個資料包4.用時序圖總覽一下BBR的Startup/Drain/ProbeBW階段5.Startup階段擁塞視窗計算的滞後性6.由ACK通告的接收視窗還有意義嗎?7.BBR在計算擁塞視窗時其它的關鍵點8.關于我的Pacing發送引擎結語

5.Startup階段擁塞視窗計算的滞後性

我們知道,BBR裡面擁塞視窗已經不再是主要因素,事實上它的名字應該改成“發送緩沖區限額”會比較合适了,為了友善起見,我仍然稱它為擁塞視窗,雖然它的含義已經改變。

       在Startup階段,發送速率每收到一個ACK都會提高bbr_high_gain:

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

這個其實跟傳統擁塞算法的“慢啟動”效果是類似的。

       然而BBR計算擁塞視窗是用“目前采集到的速率”乘以“目前采集到的最小RTT”來計算的,這就造成了“目前發送視窗”和“目前已經提高的速率”之間的不比對,是以,計算擁塞視窗的時候,gain因子也必須是bbr_high_gain,進而可以吸收掉速率的實際提升。

6.由ACK通告的接收視窗還有意義嗎?

在以往的Reno/CUBIC年代,視窗的計算是根據既有的固定數學公式算出來的,完全僅僅由ACK來驅動,無視事實上的傳輸速率,是以彼一時的擁塞視窗僅僅可以代表網絡的情況,即便如此,這種網絡狀态的結論也是猜的。

       到了BBR時代,主動測量傳輸速率,将網絡處理能力和主機處理能力合二為一,如果網絡瓶頸帶寬為10,而主機處理能力為8,那麼顯然采集到的帶寬不會大于8!反之亦然。如果BBR測量的即時速率很準确的話,我想通告視窗就完全沒有意義了,通告的接收視窗會被忠實地反映在發送端采集到的即時速率裡。BBR隻是重構了擁塞控制算法,但還沒有重構TCP處理核心,我想BBR可以重構之!

7.BBR在計算擁塞視窗時其它的關鍵點

1>.延遲ACK的影響

計算擁塞視窗的時候,會将目标擁塞視窗進行一下調整:

/* Reduce delayed ACKs by rounding up cwnd to the next even number. */
cwnd = (cwnd + 1) & ~1U;           

此處向上取偶數就是為了平滑最後一個延遲ACK的影響,如果最後一個延遲ACK該來的沒來,那麼這個向上取偶數可以為之補上。

2>.Offload的影響

* To achieve full performance in high-speed paths, we budget enough cwnd to
 * fit full-sized skbs in-flight on both end hosts to fully utilize the path:
 *   - one skb in sending host Qdisc,
 *   - one skb in sending host TSO/GSO engine
 *   - one skb being received by receiver host LRO/GRO/delayed-ACK engine
...
     /* Allow enough full-sized skbs in flight to utilize end systems. */
    cwnd += 3 * bbr->tso_segs_goal;           

...

8.關于我的Pacing發送引擎

我在今年1月份寫了一版和TCP BBR相結合的Pacing發送引擎,以消除FQ對RTT測量值(增加排隊延遲)的影響,詳見:

《 徹底實作Linux TCP的Pacing發送邏輯-普通timer版》

《 徹底實作Linux TCP的Pacing發送邏輯-高精度hrtimer版》

個人覺得我這個要比FQ那個好很多,畢竟是原湯化原食的做法吧。

       直接在TCP層做Pacing其實并不那麼Cheap,因為三十多年來,TCP并沒有特别嚴重的Buffer bloat問題,是以TCP的核心架構實作幾乎都是突發資料包的,完全靠ACK來驅動發送,這個TCP核心架構比較類似一個令牌桶,而不是一個整型器!

令牌桶:決定能不能發送;

整型器:決定如何發送資料,是突發還是Pacing發送;

可見這兩者是完全不同的機制!要想把一個改成另一個,這個重構的工作量是可想而知。是以我實作的那個TCP Pacing隻是一個簡版。真正要做得好的話,勢必要重構TCP發送隊列的操作政策,比如出隊,入隊,排程政策。

       現階段,我們能使用的一個穩定版本的Pacing替代方案就是FQ,我們看看Linux的注釋怎麼說:

/* Set the sk_pacing_rate to allow proper sizing of TSO packets.
 * Note: TCP stack does not yet implement pacing.
 * FQ packet scheduler can be used to implement cheap but effective
 * TCP pacing, to smooth the burst on large writes when packets
 * in flight is significantly lower than cwnd (or rwin)
 */           

結語

今天是周六,白天我折騰了一天工作,結果沒有什麼結果,也算認了。我又不能讓這麼一天就這麼過去,于是我去超市買了一瓶真露,回到家看了個系列紀錄片(關于甲午戰争的),然後寫完并補充了這篇文章,唉,一想到天亮我就倍感恐懼,老婆一天都要去代課,小小下午還有排練和教育訓練,家裡還有一大堆挂件安裝工作...