使用 TCP 時序圖解釋 BBR 擁塞控制演算法的幾個細節
週六,由於要趕一個月底的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)
但是這會造成什麼問題呢?這會造成誤差。如下圖所示:
BBR如果依賴這種即時的速率測量機制來運作的話,在ACK丟失或者延遲ACK的情況下會造成測量值偏高。舉一個簡單的例子:
那麼,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傳送引擎來控制,二者之間通過一個傳送緩衝區來互動,具體結構如下圖:
可見,擁塞視窗控制的是“到底扔多少資料到傳送緩衝區合適”的。接下來的問題顯然就是,擁塞視窗到底是多少合適呢?
雖然BBR分離了控制邏輯和資料傳送邏輯,但是TCP的一切都是ACK時鐘驅動的,如果ACK該來的時候沒有來,比如說丟了,比如延遲了,那麼就會影響BBR整個核心的運作,進而影響Pacing傳送引擎的資料傳送動作,BBR要做的是,即便沒有ACK來驅動,也可以自行傳送本該傳送的資料包,因此Pacing傳送引擎的傳送緩衝區的意義重要,說白了就是,傳送緩衝區裡一定要有足夠的資料包才行,就算ACK沒有來,引擎還是有包可發的。
下面來展示一幅圖:
如果這個圖有不解之處,像往常一樣,大家一起討論,但總的來講,我覺得問題不大,所以說才會基於上圖產生了下圖:
該圖示中,我把TCP層的BBR核心模組和FQ的傳送模組都畫了出來,這樣我們可以清晰看出擁塞視窗的作用。實際上,BBR核心模組按照擁塞視窗即inflight的限制,將N個資料包注入到Pacing傳送引擎的傳送緩衝區中,這些包會在這個緩衝區內部排隊,最終在輪到自己的時候被髮送出去。由於這個緩衝區裡有足夠的資料包,所以即使是ACK丟失了多個,或者接收端有LRO導致ACK被大面積聚集且延遲,傳送緩衝區裡面的資料包也足夠傳送一陣子了。
維護這麼一個傳送緩衝區的好處是在緩衝區不溢位(為什麼不溢位?那是算出來的,正好兩倍)的前提下時時刻刻有包可發,然而代價也是有的,就是增加了RTT,因為在傳送緩衝區裡排隊的時間也要被算在RTT裡面的。不過這無所謂,這並不影響效能,資料包不管是在TCP層的傳送佇列裡,還是在FQ的佇列裡,最終都是要發出去的。值得注意的是,本地的FQ佇列和中間節點的佇列性質完全不同,本地的佇列是獨佔的,主動的,而中間節點佇列是共享的,被動的,所以這裡並沒有因為RTT的增加而損失效能。這就好比你有一張銀行卡專門用來還房貸,由於利息的浮動,所有每月還款金額不同,為了不欠款,你每個月總是要存進足額的錢進去,一般要遠多於平均的還貸額度才最保險,但這並不意味著你多存了錢這些錢就虧了,在還清貸款之前,存進去的錢早晚都是要還貸的。
3.為什麼在探測最小RTT的時候最少要保持4個資料包
首先要注意的是,用1個包去探測最小RTT會更好,然而效率可能會更低;用5個包去探測最小RTT效率更好,但是可能會導致排隊,為什麼4個包不多也不少呢?
我嘗試用一個時序圖來說明問題:
可見,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的流程:
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) */
結語
今天是週六,白天我折騰了一天工作,結果沒有什麼結果,也算認了。我又不能讓這麼一天就這麼過去,於是我去超市買了一瓶真露,回到家看了個系列紀錄片(關於甲午戰爭的),然後寫完並補充了這篇文章,唉,一想到天亮我就倍感恐懼,老婆一天都要去代課,小小下午還有排練和培訓,家裡還有一大堆掛件安裝工作…
相關文章
- TCP的擁塞控制TCP
- TCP流量控制、擁塞控制TCP
- TCP流量控制和擁塞控制TCP
- TCP擁塞控制之基礎TCP
- 自頂向下 | TCP擁塞控制TCP
- TCP之 流量控制與擁塞控制TCP
- TCP的滑動視窗和擁塞控制TCP
- 淺談TCP(2):流量控制與擁塞控制TCP
- 【網路協議】TCP的擁塞控制機制協議TCP
- TCP慢啟動與擁塞控制筆記TCP筆記
- TCP擁塞機制TCP
- TCP 重傳、滑動窗⼝、流量控制、擁塞控制TCP
- TCP協議的秘密武器:流量控制與擁塞控制TCP協議
- TCP 封裝的隧道對於擁塞控制的意義TCP封裝
- TCP 擁塞視窗原理TCP
- 擁塞控制演算法的評價標準演算法
- 計算機網路-5-9-TCP擁塞控制計算機網路TCP
- TCP之擁塞視窗原理TCP
- 白話TCP擁塞控制:運糧的河道堵塞了怎麼辦?TCP
- 論TCP協議中的擁塞控制機制與網路穩定性TCP協議
- 《計算機網路微課堂》5-5 TCP的擁塞控制計算機網路TCP
- 計算機網路的擁塞控制計算機網路
- 這個演算法不一般,控制擁塞有一手!演算法
- TCP 中的兩個細節點TCP
- MPTCP 原始碼分析(七) 擁塞控制TCP原始碼
- 一文帶你掌握【TCP擁塞視窗】原理TCP
- 計算機網路之傳輸層TCP與UDP對比、流量控制、擁塞控制、超時重傳時間的選擇、可靠傳輸計算機網路TCPUDP
- 跨境 TCP 傳輸優化實錄 — 使用 BBR 解決 LFN 問題TCP優化
- UML簡單介紹(十)——幾個時序圖的案例展示與分析時序圖
- Astah 使用 流程圖、類圖、時序圖AST流程圖時序圖
- 一站式學習Wireshark(五):TCP視窗與擁塞處理TCP
- Docker 19.03.13的四個使用細節Docker
- 網路擁塞裝置的設計
- TCP/IP模型的一個簡單解釋TCP模型
- 時序圖時序圖
- SQL2017 安裝教程圖解(詳細到每一個細節)SQL圖解
- UML圖,時序圖時序圖
- [技術討論]一些人做分析模型時序圖時常見的幾個問題模型時序圖