1. 程式人生 > >Google's BBR擁塞控制演算法模型解析

Google's BBR擁塞控制演算法模型解析

                在進入這篇文章的正文之前,我還是先交代一下背景。
1.首先,我對這次海馬臺風對深圳的影響非常準確,看過我朋友圈的都知道,沒看過的也沒必要知道,白賺了一天”在家辦公“是收益,但在家辦公著實效率不高,效果不好。
2.我為什麼可以在週五的早上連發3篇部落格,一方面為了彌補因為颱風造成”在家辦公“導致的時間蹉跎,另一方面,我覺的以最快的速度分享最新的東西,是一種精神,符合虔誠基督徒的信仰而不是道德約束。
3.上半年的時候,溫州皮鞋廠老闆推薦了一個會議,大致是說”迄今為止的Linux程序排程器實現都是錯誤的...“,這讓我覺得可以寫一篇科普,但一直沒有動筆,這次BBR又讓我想起這件事。
        由於本文是講BBR,所以關於排程器的內容,請自行閱讀《THE LINUX SCHEDULER:A DECADE OF WASTED CORES
》,關於這個方面的科普,我可能永遠都沒機會寫了,但是大家可以根據我推薦的東西自己去搜索,而且,我記得有一天我在班車上我曾經把這個推薦給我的同事了,不知道他是不是通過別的渠道分享了。
        之前的幾篇關於TCP BBR擁塞控制演算法的文章,我把精力和焦點放在了BBR的外圍以及實現本身,然而這兩者都說不是核心!我為什麼這麼說?海馬剛走,我以颱風為例。颱風外圍和颱風眼都是沒啥影響力的地帶,所以我說,一項技術,你僅僅知道它的外圍-比如它的背景,用法之類,或者僅僅知道它的實現-比如讀過/Debug過其原始碼,或者二者兼得,都不是重要的,都沒啥影響力,技術的核心不在這裡!所以,在介紹了BBR的背景和演算法細節之後,我想簡單說一下其思路後面的模型,這是關鍵的關鍵。
        既然涉及到了BBR的背後思想,我就把它奉為大寫了,不再bbr,而是BBR了!
        本文的內容來自於對BBR演算法個人理解的解說。圖片改自Yuchung Cheng和Neal Cardwell的
PPT
,我增加了自己的理解。

0.模型

模型是最根本的!

我非常討厭把所有的東西雜糅在一起,我比較喜歡各個擊破,所以說,我最喜歡正交基!我希望把待觀測的東西分解成毫無耦合的N個方面,然後各自研究其特性。這個思路我曾經無數次提出,但是幾乎沒人會聽,因為一旦分解,你將看不到目標,看不到結果,拆了的東西並不定能再裝起來...令人欣慰的是,TCP的BBR演算法思路也是這樣,不幸的是,TCP領域的頂級專家並沒有N維拆解,人家只是拆解了2個維度。

頻寬和RTT BandWidth & RTT
我很驚奇Yuchung Cheng(鄭又中)和Neal Cardwell是怎麼發現這個正交基的,為什麼之前30年都沒有人發現這個,最為驚奇的是,他們竟然對了!他們的模型基於下圖展開:


這張圖幾乎完全描述了網路的行為!這就是網路傳輸的本質模型!之所以之前的Reno到CUBIC都是錯的,是因為它們沒有使用這個模型,我先來解釋一下這個模型,然後再看看將Reno/CUBIC套在這個模型上之後,是多麼的荒唐。

        值得注意的是,這個模型是Kleinrock & Gale早在1981年就提出來的,然而直到現在才被證明是有效的。之前的年月裡,人們面臨著實現問題(同時測量問題,後面會講)。因此我把這個模型最終的實現者作為模型的主語,即Yuchung Cheng和Neal Cardwell們的模型,這並不是說他們是模型的發明者。就類似牛頓的定律一樣,其實伽利略已經提出了,只是沒有用數學系統的論證並總結而已。

1.有破才有立

首先,我們先要知道Reno/CUBIC錯在哪裡,然後才能知道BBR是怎麼改進的。
        骨子裡,人們總希望時間成為橫軸,除了生命和做愛,人類幾乎所有的行為都是在於儘量縮短時間。
        效率是什麼?人們只知道效率的分母是時間!人們一旦把時間確定為橫軸,很多時候就會矇蔽很多真相。這是一個哲學問題,我以後再談。
        人們在為TCP建立效能模型的時候,總是用”序列號-時間曲線“,”序列號RTT曲線“等來觀察(比如Wireshark等分析工具),目標呢,很簡單,就是查一下”一個連線最快能突突多少資料“,
        但是,這些都錯了!Why?
        因為時間軸具有滯後性欺騙特徵,所有基於時間軸的效率提升方案都是”先汙染後治理“的方案!
        看一下上面那個模型圖,所有30年來的擁塞控制演算法的目標都是收斂於一個錯誤的收斂點(圖的右邊):不斷地增加資料的傳輸,試圖填滿整個網路以及網路上的所有快取,以為這樣就會達到比較高的頻寬利用率,直到發現丟包,然後迅速降低資料傳送量,之後重新向那個錯誤的收斂點前進,如此反覆。這就是鋸齒的根源!這個現象在以上的模型圖上顯示的非常明確,根本就不用解釋。如果一開始人們就使用這個模型圖,任何人都不禁會問:為什麼要一直在警戒區域徘徊呢?正確的收斂點不應該是暢通模式的最右端嗎??我以經典的VJ版TCP擁塞圖來說明一下:


我們要做蜜蜂而不是老鼠。中北部省區的人們比較喜歡往家裡屯菜屯肉,最後總有大量的食物壞掉造成浪費,看似吞吐很高,實際效率極低,而南方人則不同,南方人從來都是買當天正好夠吃的新鮮食物,絕不會往家裡屯。造成這種差異的原因主要是因為北方冬天非常寒冷,食物難覓,而南方則一年四季都不愁新鮮食物。如果我們深入一下考慮這個問題,正確的做法一定是南方人的做法,任何北方人屯食物並不是說他就喜歡屯食物,他知道屯的食物沒有新鮮的好,是出於沒有辦法才屯食物的,一旦條件成熟,比如大棚,溫室出現,北方人也開始購買當天的新鮮食物了。
        TCP與此不同,TCP的Reno/CUBIC並不是因為條件不具備才往快取裡屯資料包的,對於能搞出CUBIC那麼複雜演算法的人,搞定BBR根本就是小菜一碟,我承認我看不太懂CUBIC的那些算式背後的東西,但我對BBR的理解非常清晰,像我這種半吊子水平幾個月前就差一點實現了BBR類似的東西,但我絕對想不到CUBIC那麼複雜的東西,之所以Reno/CUBIC被使用了30多年,完全是因為人們一直以為那是正確的做法,竟然沒有任何人覺得這種做法是有問題的。
        基於{RTT,Delivery Rate}正交基的新模型一出來,人們好像猛然看到了事實的真相了:



問題的根源在於,BBR擁塞控制模型和BBR之前的擁塞控制模型對BDP的定義不同。BBR的模型中,BDP是不包括網路快取的,而之前的模型中,是包括快取的,這就是說,包括快取的擁塞控制模型中,擁塞控制本身和網路快取之間有了強耦合!要想做一個好的擁塞控制演算法,就必須要徹底理解網路快取的行為,然而作為端到端的TCP協議,是不可能理解網路快取的。所以一直以來,30年以來都沒有出現一個比較好的演算法,Reno到CUBIC,改進的只是演算法本身,模型並沒有變!
        網路快取是複雜的,有基於深佇列的快取,也有基於淺佇列的快取,不管怎樣,都會遇到BufferBloat的問題,這是TCP所解決不了的,雖然如此,TCP還是嘗試填滿包括這些永遠琢磨不透的網路快取在內的BDP,這個填滿的過程是逐步的,開始於慢啟動,然後...這個逐步填滿BDP是兩個過程,首先是RTT不變,逐漸填滿不包括網路快取在內的管道空間(在我的定義中,這個屬於時間延展性的快取空間),然後是逐漸填滿網路快取的過程(在我的定義中,這部分屬於不帶時間延展性的時間牆空間),問題處在TCP對後一個過程的不可知不可測的特性!!
        既然知道了Reno/CUBIC的問題根源,那麼BBR是怎麼解決的呢?換句話說,BBR希望收斂在上圖中紅色圈圈的位置,它怎麼做到的呢?
        列舉做法之前,我先列舉一下{RTT,Delivery Rate}正交基的美妙:


誠然,BBR的模型已經發現了在一個足夠長但不太長的時間視窗內最大頻寬和最小RTT是其收斂點,這就使得BBR有了明確的目標,那就是,求出最大的頻寬,即Delivery Rate,以及求出最小的RTT,這個目標暫且擱置一下,先回答一個問題,為什麼取樣時間視窗要足夠長,是為了這段時間足以過濾掉假擁塞,畢竟時間可以衝破一切謊言,沖淡一切悲哀,那為什麼這段時間又不能過於長,因為要對網路環境的變化有即時適應性。基於此,BBR可以幾乎可以抵禦大多數的假擁塞情形了。
        最終,BBR的BDP如下圖所示,不再包括警戒區的網路快取:


我用一個統一的圖表示RTT和頻寬的關係:


好了,現在我看可以看BBR到底怎麼在這個新模型下探測最大帶寬了。

2.BBR對最大頻寬和最小RTT的探測

從模型圖上可以清楚的看出如何探測最大頻寬:


但是對於最小RTT的探測卻不是很直觀。
        在這裡首先要談一下BBR之前那些”基於時延“的擁塞控制演算法。Reno/CUBIC屬於基於丟包的擁塞控制演算法,而像Vegas之類的屬於基於時延的演算法,其區別在於,Vegas對RTT的變化比較敏感,判斷擁塞的要素是RTT的變化而不是丟包,不管哪種演算法,都需要外部發生一個事件,提示TCP連線已經處於擁塞狀態了。事實證明,Vegas之類的演算法工作的並不好,原因在於它無法抵抗假擁塞,偶爾一次非擁塞造成的RTT增加也會引發TCP主動降窗,這種演算法無法對抗共享深佇列的其它基於丟包的TCP連線的競爭,因此無法普遍被採用。
        BBR需要測量最小RTT,但是它是基於時延的擁塞演算法嗎?
        並不是!起初,當我第一次測BBR的時候,那是在國慶假期我醉酒後的第二天,幾乎動用了家裡的所有裝置(iMac一臺,Macbook Pro一臺,ThinkPad一臺,ROOT-Android平板一個,刷過的榮耀立方一個,樹莓派開發板一塊,ROOT-Android手機一個,極路由一個,iPhone兩個,iPad兩個...),搭建了一個測試網路,讓BBR和CUBIC,Reno一起跑,並製造了RTT突變,發現BBR並沒有降速,甚至對RTT的突變不會有反應,而對RTT的變化的劇烈反應是基於時延的擁塞演算法的基本特徵,但BBR並沒有!任何組合情況下BBR都可以完爆其它演算法。這是為什麼?
        BBR在一個不隨時間滑動的大概10秒的時間視窗中採集最小RTT,BBR只使用這個最小RTT計算Pacing Rate和擁塞視窗。BBR不會對RTT變大進行反應。但是如果整的發生了擁塞,RTT確實會變大,BBR怎麼發現這種情況呢?答案就在於這個時間視窗的超期滑動,如果在一個時間視窗內持續沒有采集到更小的RTT,那麼就會將當前的RTT賦值個最小RTT。BBR就是這樣抵抗假擁塞的。秒級的視窗內,什麼都是瞞不住的。這就是RTT的測量以及使用原則:


現在可以明確,BBR的搶佔性不糊因為其對時延的反應而降低,BBR不會對時延進行直接的反應。BBR沒有搶佔性,但也不示弱,它所做的是正確的做法所要求的,它只是做到了而已。
        之前在網上看到一個美國矽谷工作的實習生質疑BBR會因為時延反應而不利於公平性,我本來想回復的,後來感覺翻次牆太麻煩了,

3.總結

我們一直需要的是一個”可持續發展“的方案來解決效率問題。不幸的是,TCP出現的30年來,我們見到的所有擁塞控制演算法都是這種所謂的先汙染後治理的方案,貫穿從Reno到CUBIC的所有演算法。先把快取填充到爆,然後再主動緩解,因為所有基於丟包的演算法都在搶著填爆所有快取,基於時延的演算法太君子的主動降速行為就顯得競爭性不夠,典型的劣幣驅良幣的場景。
        我一直都以為TCP的加性增,乘性減是一個人人為我,我為人人的策略,確實,可以這麼理解,然而這是一種敬人一杯,自罰一壺的策略,結局就是普遍倒下...
        大家都能隨口說出ssthresh的作用是什麼,比如當視窗大於它就怎麼怎麼樣,當小於它就如何什麼的,但是有人知道它的含義嗎?也許,你會發現ss是slow start的縮寫,而thresh則是threshold的縮寫,門限的意思,但是這不是正確答案,正確的答案在這裡
The capacity of a path can be informally defined by the sum of unused available bandwidth in the forward path and the size of buffers at bottleneck routers.  
Typically, this capacity estimation is given by ssthresh.

真相如此晚近才被揭示,怪不得人們只知道ssthresh的用法而不知其含義啊!稍微詳細一點的東西也可以看我寫的一篇文章《TCP核心概念-慢啟動,ssthresh,擁塞避免,公平性的真實含義》。
        事實上ssthresh定義了路徑上所有快取(包括時間延展快取和時間牆快取),所以說,當檢測到丟包時,會將cwnd減少1/2並賦值給ssthresh,此時因為BDP已經滿載,所以說其容量的1/2就是路徑的快取容量!Perfect!然而這是錯的,TCP誤以為所有的快取都是可以填充的,然而事實卻是,只有時間延展性質的快取,即網路本身才是可以填充的,而時間牆快取卻是應急的快取,所有的TCP只要都避免使用時間牆快取(包括路由器,交換機上的佇列快取),其才能真正起到應急的作用,頻寬利用率才會最大化!
        應急車道是應急的,而不是行車的!
        BBR的新模型把這一切錯誤展示給了所有人,因此,BBR的指示是,保持最大頻寬,並最小化網路快取的利用。事實上,基於新模型額BBR要比之前的演算法簡單多了!千萬不要覺得新演算法一定很難,相反,BBR超級簡單。

        也許你也已經想到了BBR類似的思路,但是它能夠在Linux上實現還是要對Linux的TCP實現動手術的,並不僅僅是一個擁塞模組那麼簡單,前幾天的文章說了N遍,BBR之前的擁塞控制演算法在非Open狀態會被接管,再牛逼的演算法也完全沒用,由於CUBIC試圖填滿整個包括佇列快取在內的所有快取空間,在當下的核心深佇列,邊緣淺佇列高速網路環境中,只有不到40%的時間內TCP的擁塞狀態是處於Open狀態,大部分情況,傳統的演算法根本就跑不到!好在BBR的實現中,作者注意到了這一點,完成了TCP擁塞控制的外科手術,快哉!

        BBR會在4.9或者5.0核心中成為預設的TCP擁塞控制演算法嗎?我覺得可能還需要更多的測試,CUBIC雖然表現不佳,但起碼並沒有因為其表現帶來比較嚴重的問題,CUBIC的執行還是很穩定的。但是我個人希望,我希望BBR趕緊成為所有Linux版本的標配,徹底結束所謂TCP單邊加速這個醜行!

附:時間延展性快取和時間牆快取

我一直強調BBR是簡單的,是因為它確實簡單。因為在上半年的時候,我差一點就想出了類似的演算法,當時我已經準備對PRR做手術了。
        我覺得擁塞控制演算法對擁塞視窗的控制權不夠大,我希望用擁塞演算法本身的邏輯來絕對視窗如何調整,而不是一味地PRR!我不相信檢測到三次重複ACK就一定是發生了丟包,即便是在非Open狀態,比如Recovery狀態,我也希望在我的演算法認為可以的情況下可以增加視窗...我仔細分析了網路的特徵,總結除了兩類快取:
帶有時間延展性的快取,即網路本身(確切的說是網線上跑的資料,從A到B需要時間,所以認為網路是具有儲存功能的);
時間牆快取,即路由器交換機的佇列快取。這類快取的性質就是記憶體。

關於這個,請參見我6月份寫的一篇文章《TCP自時鐘/擁塞控制/頻寬利用之脈絡半景解析》,我在想,CUBIC演算法到底要不要誘發TCP填充時間牆快取,然而就CUBIC本身而言,區別這兩類快取非常困難...我當時沒有一個好的模型,但我確實已經區別出了兩類快取...
        很遺憾,我沒有繼續下去,很多事情並不是我個人所能左右的,也不是我能安排的,不過當我看到BBR的那一刻,著實有一種找到答案的感覺。雖然,雖然很多事情不是我個人所能左右和安排,當我懼怕開始的時候,多數時候不知不覺中,默默地就結束了。你是不是也有這種感覺。
        最後,那個哲學問題,聽著竇唯的老歌,我想可以談談了。效率的分母是時間的問題。
        無論人們怎麼努力,分母也不能是0!但有個例外,那就是一條蛇從尾巴開始吃掉自己的情形!你能想象那種場景嗎?除非可以沿著時間軸任意穿越,否則這就是不可能的,是這樣嗎?我覺得這種事沒那麼複雜,只要上升一個維度去考慮就好了,任何低一級的維度裡任何東西對於高一級維度而言就是一個點,也就是一個小宇宙。一條蛇是一個三維動物,它永遠也意識不到四維時空裡面的一個點,就是它自己吃掉的自己。爆炸!