做者:LeanCloud 後端高級工程師html
> 視頻版本連接請點擊這裏 <linux
延續前面說的,要把發送速率調整到跟 BDP 差很少大是最優的。由於網絡環境會持續變化,因此須要持續監控 RTprop 和 BtlBw 的值。算法
RTprop 是鏈路固有傳輸延遲,咱們沒法直接監控到它,咱們只能經過監控數據包的 RTT 來間接的獲得 RTprop 的趨近值。RTT 和 RTprop 關係以下:segmentfault
η 是其它因素致使的 noise 延遲,包括接收端延遲 ack 等緣由致使的延遲。由於 RTprop 是鏈路上的固有延遲,能夠認爲只有鏈路上選擇的路徑變化時候它纔會變化,並認爲它變化頻率很低,變化間隔時間遠大於 η。η 必定是正數,RTT 不管如何不可能低於 RTprop,因此能夠在一個時間窗口 Wr 內(通常是在幾十秒到幾分鐘之間)監控最小的 RTT 的最小值,認爲就是近似爲 RTprop。後端
BtlBw 也是靠 Ack 來監控。每收到一個 Ack 一方面是知道數據延遲 RTT 是多少,再有送達的數據量是多少。咱們在一個短的時間窗口 deltaT 內經過 Ack 計算對面收到了多少數據,獲得 deliveryRate = deltaT 時間內送達數據量 / deltaT
,這個速率必定低於鏈路上瓶頸速率,bottleneck rate。由於咱們計算 deliveryRate 時使用的數據送達量是精確值,是在 Ack 裏數據接收方明確告訴咱們的。而咱們爲了作帶寬計算等待的 deltaT 時間必定大於實際時間,因此 deltaT 時間內送達數據量 / deltaT
計算獲得的 deliveryRate 必定小於等於真實 deliveryRate,這個真實 deliveryRate 又必定小於等於鏈路物理 bottleneck rate。因此:緩存
這裏時間窗口 Wb 通常是 10 個 RTT。微信
RTprop 和 BtlBw 是兩個獨立的量,RTprop 變化好比選擇的鏈路變化時,bottleneck 多是同樣的 BltBw 不變;BtlBw 變化時候選擇的鏈路可能沒有變化,好比一個無線網絡改變了發送速率,鏈路不變但帶寬變大了。網絡
從以前看過的下面這個圖能知道,RTprop 只能在 BDP 線左側被觀測到,即發送數據比較少,inflight 數據量沒有到 BDP 的時候;BtlBw 只能在 BDP 線右側能被觀察到,即 inflight 數據超過 BDP,延遲開始增大時被觀測到。直觀一點說就是鏈路上隊列沒排隊時候,你知道鏈路延遲是多少,但不知道隊列最大消費速度是多少,當隊列開始出現排隊後,你在發送端計算獲得接收率不變了,RTT 延遲也升高了,知道最大接收率是多少,但又不知道鏈路上實際延遲是多大了,由於多了數據排隊的時間。app
估計帶寬時候還一個須要處理的,跟 RTT 不一樣,RTT 是隻要有應用層數據發就能測得,就能更新估計值,但 BtlBw 的話必須應用層有足夠數據發才能估計。因此下圖能夠看到有個 App Limited。BBR 會將由於 app 沒足夠數據發而致使測得的 BtlBw 太小的狀況丟棄,只記錄有足夠數據發的狀況下獲得的 BtlBw。實際上鍊路中實際 BtlBw 是個 hard limit,就是不管咱們怎麼測都不可能超過鏈路上實際 BtlBw,因此只要在窗口內測得小的 BtlBw 均可以丟掉,就用測試周期內最大的 BtlBw。tcp
來自[4]
BBR 在控制擁塞時候會在一組狀態之間進行切換,以下圖:
來自[2]
先簡單介紹一下下面再針對每一個狀態更細一些說。初始狀態就是 Startup,BBR 由於不知道當前帶寬究竟是多少,也會有相似 Slow Start 的過程,逐步增長髮送速度探測 BtlBw。等到探測到 BtlBw 後由於 Startup 階段發了不少數據出去,inflights 很大,可能鏈路會有排隊,因此進入 Drain 階段去作清理,讓鏈路上的隊列清空;以後進入穩按期,會週期性的在 ProbeBW 和 ProbeRTT 兩個狀態之間進行切換,間歇的探測 BtlBw 和 RTprop。
先記錄穩按期再寫 Startup 等。BBR 每收到一個 ACK ,就會估計 RTprop 和 BtlBW (deliveryRate),盡力保證 inflight 數據量跟估計獲得的 BDP 差很少。BBR 下 inflights 數據量由內部的 target_cwnd 控制,而 target_cwnd 是個比 BDP 稍微大一點點的量。
假設鏈路上 RTprop 和 deliveryRate 一直保持不變,BBR 處在穩定狀態一直髮送數據,它保證 inflights 的數據量不會超估計的 BDP 不少。此時鏈路上 Bottleneck 在發送端,由發送端控制發送速度。若是鏈路上帶寬提升了,由於 Bottleneck 在發送端,發送端會感知不到帶寬變化。因此 BBR 須要週期性的提升發送速率將 Bottleneck 從發送端移到鏈路上去探測鏈路上的帶寬,這就是 ProbeBW 狀態的來源。名字也能夠看出它含義是探測帶寬,探測的是鏈路上 Bottleneck 的帶寬。
ProbeBW 狀態下先開始增週期,即提升發送率到穩按期的 1.25 倍,直到出現丟包或 inflights 數據量達到 1.25 倍 BDP 爲止。觀察延遲是否升高,若是延遲升高且 deliveryRate 不變,說明鏈路上帶寬沒有變化且產生了隊列堆積;接下來會進入減週期,下降發送率到穩按期的 0.75 倍,等待一個 RTprop 或 inflights 數據量低於 BDP 爲止,用以讓鏈路上在增週期出現堆積的隊列清空。以後再保持 inflights 等於 BDP 穩定數個 RTprop 後再次開始增週期。
以前提到 BBR 每次收到 ACK 會嘗試更新 RTprop,而 RTprop 取的是窗口期內最低的 RTprop。若是 BBR 運行了很長時間一直沒有更新 RTprop,即很長一段時間都沒有比當前使用的 RTprop 更低的 RTprop 時,BBR 會進入 ProbeRTT 狀態,用於探測 RTprop。好比鏈路上帶寬出現減小,鏈路上出現堆積,保持發送速度或繼續進行 ProbeBW 的話會讓鏈路上堆積更加嚴重,RTT 上升。因此 RTprop 一直不會被更新。
ProbeRTT 下 BBR 將 CWND 降到很低的值,典型的是 4 個 MSS,持續一段至少 200ms 或一個 RTprop。這麼一來 Inflights 數據量會突降。再判斷鏈路是否處在 full_pipe 狀態,是的話則進入 ProbeBW,不是則進入 Startup。
full_pipe 判斷主要是靠最近的增週期中,發送率提升後 deliveryRate 是否有大幅度增長,有相應幅度的增長說明鏈路可能尚未滿載,咱們加快發送速率仍是能發的出去,沒增長則表示鏈路是滿載的,發送速度加快可是接收速率沒跟上。有個 Linux 內的 BBR 實現,能夠看:tcp_bbr.c source code linux/net/ipv4/tcp_bbr.c - Woboq Code Browser
以前提到了 RTprop 和 BtlBw 兩個量是測準一個另外一個就測不許,穩按期測量這兩個值的任務大多數時候都由 ProbeBW 獨自完成,在增週期提升發送帶寬發多數據到鏈路探測 BtlBW,在減週期減少發送帶寬減小發送速率從而減少 Inflights,看估計的 RTprop 是否下降,下降了則說明測到了最新的 RTprop。若是很長時間都沒有更低的 RTprop 出現,可能鏈路發生了切換,這時候才須要切換到 ProbeRTT 去探測最新的 RTprop。從 ProbeRTT 的機制能看到它對性能是有影響的,因此 BBR 是盡力減小 ProbeRTT 時間佔比,大部分時間都在 ProbeBW 狀態。
總體過程以下圖,在一個 10Mbps 帶寬,延遲 40ms 的網絡下取了 700ms 時長的數據。圖中縱軸有 RTT,inflight,和 Bandwidth (BW)。帶寬部分有兩個值,紅色的是發送方計算出來的 Delivery Rate,緊挨着紅色線的黑線是當前估計出來的 BtlBw。能夠看到 Delivery Rate 波動但估計出來的 BtlBw 變更不大,由於是每次有估計出 Delivery Rate 後將這個值放入估計 BtlBw 的滑動統計窗口內,取滑動窗口內的最大值爲 BtlBw,也即那個黑色線,因此紅色線變更後,黑色線不會當即變更。黑色線上面的灰色框是 cycle gain 用於控制發送速度,cycle gain * 估計的 BtlBw
就是發送方當前須要的發送速度。每一個週期經過控制 cycle gain 來按期的提升發送速度,以探測當前鏈路上 BtlBw 是否提升。
對於黑色的圈咱們從起點開始說,起點是紅線,即 Delivery Rate 計算出來後,放入 max BtlBw 滑動窗口估算 BtlBw 是多少。以後 1.25 這個 cycle_gain 時用以前估算的 BtlBw 乘以 1.25 做爲發送速度發消息,增週期發消息多了之後鏈路 Inflights 增大,RTT 增大,發出的消息看到是在 1.00 這個 cycle_gain 才被 Ack,計入 Delivery Rate,並讓 max BtlBw 稍微增長了一點點,看到紅線峯值比紅線上面的細黑線高了一點,因而將細黑線也向上推了一點點。
圖中 RTT,Inflights,cycle_gain,Delivery Rate 大體都是對齊的,能夠看到增週期時候 Delivery Rate ,Inflights 和 RTT 增長,減週期是減小。
來自[4]
下圖是帶寬從 10Mbps,40ms 延遲提到到 20Mbps 又降回 10Mbps 的過程。帶寬提升後看到在 ProbeBW 的增週期, BBR 發現提升發送速度後 RTT 沒有變化,且計算出來的 deliveryRate 有升高,更新 BtlBw 到新值,穩按期發送速度比原來高了 25%。在接下來三個週期內,每一次發送速率提升 25% 後延遲都沒有變化,且 deliveryRate 獲得提升,最終在第四個探測週期從新出現藍色小三角,即鏈路上隊列有排隊後說明鏈路進入 full_pipe 狀態開始穩定發送速率。
下半部分是帶寬從 20Mbps 下降到 10Mbps,BBR 內由於維護了 BtlBW max filter 即從一個窗口期內採樣獲得的 BtlBw 值中取最大值做爲當前鏈路的 BtlBw。因此即便帶寬出現突降,由於 20Mbps 的 BtlBw 還在 max filter 內緩存着,這段時間 BBR 依然認爲 BtlBw 是 20Mbps,會按照原來的發送速度繼續發數據。因而帶寬突降後延遲和 inflight 數據量大幅度增長。 但 inflights 數據量最大不能超過 BBR 內的 target_cwnd,其值等於 cwnd_gain * 估計的 BDP
,BBR 會始終控制發送速率保持 inflights 在 target_cwnd 內,cwnd_gain 比 1 大,但不會大不少,因此帶寬忽然變小後 inflights 不會無限增長,而且會維持一個固定值,在圖中表現爲 40s ~ 42s 之間的一條水平線。這個期間即便處在 ProbeBW 階段也沒法執行增週期按 1.25 倍速率發數據,由於 target_cwnd 是滿的,必須聽從它的限制,它限制了發送端不能讓 inflights 數據量比它大。Inflights 和 RTT 能是一條水平線說明鏈路上 Buffer 比較大,能承載 target_cwnd 下的數據量且不出現丟包。
補充一下 BBR 裏有兩個聽起來很像的東西,pacing_gain 和 cwnd_gain。pacing_gain 用來在 ProbeBW 內週期性的控制發送速率,發送速率等於估計的 BtlBw * pacing_gain
。而 cwnd_gain 用於控制 target_cwnd,限制 inflights 數據量。
在 42s 開始,以前 20Mbps 的 BtlBw 估計值過時了,從 BtlBw max filter 的採樣窗口中滑出,根據過去一段時間 Ack 計算出來的到達從新估算 BtlBw,根據這個從新估算的 BtlBw 調整發送速度,因此發送速率大幅度下滑。由於當前的 Inflights 數量遠大於當前的 target_cwnd 也即cwnd_gain * 新估計的 BtlBw
,因此發送方會徹底不發數據等待 Inflights 降低,在圖上表現出 Inflights 忽然掉下來。當 Inflights 小於當前 target_cwnd 後,ProbeBW 的週期特性又開始顯現,一個一個的小三角開始出來了。最終發送速率從新回到穩態。
看到這裏時候我開始一直有個疑問,在 42s 到 44s 期間,隊列內一直有堆積,一個週期發送速度是 1.25,一個週期發送速度是 0.75,豈不是恰好把發多少數據消費掉,但隊列內堆積的 inflights 不是依然保持沒有被消費掉嗎?不應是降低趨勢。
後來知道,ProbeBW 期間一個週期大體上是一個 RTT 時間,但發送速度 1.25 的週期和發送速度 0.75 的週期並非嚴格等長的。1.25 週期時長是 inflights 數量到達 1.25 倍估計的 BDP 或有數據丟包。0.75 週期長度是一個完整的估計的 RTprop,或者 inflights 低於估計的 BDP。發送者能感知到 inflights 低於 BDP 的時候實際 inflights 必定低不少了,因此 BBR 的 inflights 曲線都是像心跳同樣,上面一個三角下面一個三角,而不是隻有上面的三角。在 19 ~ 20s 的時候,上三角比下三角大。20 ~ 21s 由於帶寬變大了,但發送帶寬只是緩慢增長上去,因此下三角比上三角大不少,等到穩態之後又變成上三角大於下三角。42s 之後由於每一個減週期都要等到 Inflights 低於估計的 BDP,因此綠線一路向下。
對於藍色的線咱們知道鏈路上延遲是固有時間,因此它最低點是一條直線,增週期時延遲只會有上三角,沒下三角。
注意下圖沒有 ProbeRTT 出現。我理解是由於 RTprop 還未超時就被更小的值更新了。
來自[4]
鏈路上帶寬跨度很大,從幾個 bps 到上百 Gbps,因此 BBR 一開始也會以指數形式增大 BtlBw,每個 RTT 下發送速度都增大 2/ln(2)
約 2.89 倍,從而在 O(log(BDP))
個 RTT 內找到鏈路的 BtlBw,log 的底是 2。BBR 在發現提升發送速度但 deliveryRate 提升很小的時候標記 full_pipe,開始進入 Drain 階段,將排隊的數據包都消費完。BBR 能保證排隊的數據最多爲實際 BDP 的 1.89 倍。BBR 下並無 ssthresh,即 CUBIC 那樣增長到某個配置值後開始進入線性增長 CWND 階段。
Drain 階段就是把發送速率調整爲 Startup 階段的倒數。好比 Startup 階段發送速度是 2.89,那 Drain 階段發送速度是 1/ 2.89
。BBR 會計算 inflights 數據包量,當與估計的 BDP 差很少的時候,BBR 進入 ProbeBW 狀態,後續就在 ProbeBW 和 ProbeRTT 之間切換。
下圖是在 10Mbps,40ms 延遲網絡下的 Startup 階段的圖,綠色是發送方發送數據量,藍色是接收方收到的數據量。紅色是 CUBIC 在一樣環境的發送數據量,做爲對比。下圖綠色線的斜率就是發送速度,同一時間點綠色線上的點和藍色線上的點的差值就是那個時刻 inflights 數據量。
看到 BBR 在 0.25s 以前是曲線,10Mbps 40ms 延遲的網絡下 BBR 容許的 BDP 爲 0.04Mb,0.25 時間點上綠色和藍色線差值大約 0.1Mb,大體是 0.04Mb 的 2.89 倍。即 BBR 在一開始指數的快速提高發送速度,但快到了 0.25s 的時候,RTT 開始升高,inflights 提升後 deliveryRate 並無相應提升,BBR 開始維持在一個速度等待幾個 RTT 週期以確認鏈路確實承載不了當前的發送速度,因此 0.25s ~ drain 以前綠線斜率不變。到 Drain 後 RTT 迅速降低,Acked 數據量圖的斜率也下降不少。在找到 RTprop 以後進入 ProbeBW 狀態。
紅線的話就是看到 CUBIC 更早的切換到線性增加,但以後會逐步增長髮送包數量直到丟包。但下圖上半部分看紅線看不太出來發送率在增長,只是能隱約的感受到 0.25 時間點時紅線和藍線的間隔彷佛小於 0.75 時間點時他倆的間隔。
來自[4]
下面是 BBR 和 Cubic 在延遲時間上的對比。BBR 開始延遲大但隨即恢復。CUBIC 一直增加下去直到丟包,再減少再增加。
來自[4]
當多個 BBR 流在同一個鏈路時以下圖。基本上就是靠 BtlBw max filter 內 BtlBw 超時過時以及 ProbeRTT 逐步讓每一個 BBR 流都找到本身合適的份額。在一開始只有紅色的線,以後綠的來了,紅色線繼續按原來速度發數據鏈路必定會擁塞,而且由於有兩個流了紅線的 deliveryRate 必定會降低,BtlBw 超時後紅線下降發送速率。大體上就是這麼相互做用,每來一個流須要調整一下直到穩定。
來自[4]
列一下 Reno,CUBIC,BBR 三個在時間和發送速率上的圖作對比。
來自[1]
以前提到 TCP Vegas,Vegas 會被基於丟包的算法擠掉份額,不能跟這些算法共存,因此沒法流行起來。對於 BBR 的話,它搶佔份額的方式主要靠 ProbeBW 期間按 1.25 倍估計值發送數據,會給鏈路帶去壓力,爲本身搶佔生存空間;再有就是 Startup 階段,BBR 相對 CUBIC 來講會爲鏈路帶去更多數據去搶佔份額,這個只在初始階段有用,但若是 BBR 上的數據若是能持續發送,初始期佔的份額可能就能一直保持下去。
在網上搜不少文章都說 BBR 根本無論丟包,徹底基於本身的週期即 ProbeBW 內的週期去計算髮送率,CWND 來發數據包,因此 BBR 根本無視丟包。但實際上 BBR 做者寫的 BBR 介紹裏明確說了 BBR 是會管丟包狀況的,在這 BBR: Congestion-Based Congestion Control - ACM Queue。以及這裏:Modulating cwnd in Loss Recovery
總之就是說 BBR 處理丟包的時候須要區分 RTO 和 Fast Retransmission 兩個場景,RTO 下與 CUBIC 差很少;只有 Fast Retransmission 下,發送速率還能保持在較高位置,但也是受影響的。
下面是 CUBIC (紅色)和 BRR (綠色)在面對丟包的時候吞吐量表現。CUBIC 在丟包 1% 的時候基本就沒法工做了,BRR 能扛到 20% 的丟包率。黑線是理想情況下的吞吐量,由於有丟包存在,因此吞吐量隨着丟包率升高而遞減。
來自[4]
對於右側綠線是個斷崖式有這麼一些緣由:
對於爲何是丟包率接近 ProbeBW 增週期增益 (1.25) 時會出現吞吐量大幅度下滑我理解是這樣的:
在估計 BtlBw 是是取過去一段時間內的最大值,好比丟包率是 10%,若是沒有增週期發送者經過 Delivery Rate 計算獲得的帶寬比實際 BtlBw 會低 10%,以後會調整發送速度變成 90% BtlBw,由於依然有 10% 的丟包率,在 90% BtlBw 從估計帶寬用的滑動窗口過時移除後,發送速率會降爲 90% * 90% BtlBw = 81% BtlBw
,最終這麼一步步降低到 0,這是沒有增週期的場景。
可是由於有增週期的存在,假設當前估算的帶寬爲 BtlBw,普通週期發送速率就是 BtlBw,增週期是 1.25 BtlBw,減週期是 0.75 BtlBw。當丟包率是 10%,普通週期時計算出來的 Delivery Rate 是 90% BtlBw,增週期計算出來的是 1.25 * 90% * BtlBw = 1.125 BtlBw
,但實際上 BtlBw 是鏈路帶寬不可能超越 BtlBw,因此增週期 Delivery Rate 仍是 BtlBw,減週期是 0.75 * 90% * BtlBw = 0.675 BtlBw
,這麼一來根據過去一段時間 Delivery Rate 最大值估算帶寬時獲得的帶寬仍是 BtlBw,因此 10% 丟包率的時候吞吐量受丟包的影響不大。
當丟包率增加到 20% 的時候,根據上面的算法,增週期計算出來的 Delivery Rate 就馬馬虎虎恰好是 1 倍 BtlBw 了,由於 1.25 * 80% * BtlBw = 1 BtlBw
。只要有風吹草動讓計算的 Delivery Rate 低於最初的 BtlBw,那一步步的計算的 Delivery Rate 就會和實際帶寬偏離愈來愈大,最終降到 0。也就是說增週期的增益和丟包率的乘積能大於 1,就能保持發送速度,小於 1 則會斷崖式下滑,大於 1 是不可能的。
因此 BBR 對 CUBIC 在丟包方面的優點是說,CUBIC 是算法結構上致使丟包必定會讓性能大幅度下滑。而 BBR 是在必定程度上能經過配置去容忍丟包。好比增週期增益提高到 1.5,抗丟包能力必定會大幅度提高,但性能受重傳數據影響會愈來愈大。
參考:
其餘分享內容:
更多內容歡迎關注 LeanCloud 官方微信號:「LeanCloud 通信」