歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> 關於Linux

RACK為TCP BBR提供動力源

在上一篇文章《Google's BBR TCP擁塞控制算法的四個變速引擎》的最後,我提到bbr算法作為一個標稱功率十足的引擎需要源源不斷的能源供給,而這類能源就是數據包。又提到,TCP的快速重傳機制幾乎只會將判斷為LOST的數據包重傳一次,因此當重傳數據包再次丟失,滑動窗口無法滑動的時候,將會無法提供數據包發送,bbr引擎就會失速,此時只能等待TCP的超時!當然,超時的代價有點大,不單單是對於擁塞控制而言,對於整個連接而言,都無異於異常劫難!

因此,bbr需要源源不斷的數據包供給它開大馬力運行,bbr並不在乎這些數據包是新數據包,標記為LOST的數據包,重傳過的數據包,甚至是構造的錯包...只要有數據包即可!bbr真正徹底實現了擁塞控制與數據包標記/發送之間解耦合。

說完了需求,再談方案。

Linux內核在bbr之前就已經引入了RACK機制,旨在快速發現並重傳那些曾經重傳後再次丟失的數據包,這些數據包的處理至關重要,如果不及時處理,便會陷入RTO的深淵。當然,RTO的回調在你自己手上,你也可以處理得不那麼激進,然而對於已經失速的連接,繼續勉強撐著也算是自欺欺人了...RACK解決了這個問題。然而bbr之前的RACK並沒有獲得最大收益,原因在於雖然RACK可以即時地探知哪些數據包丟失,特別是那些重傳後再次丟失的數據包,但是由於此時擁塞窗口的計算已經固定地朝著ssthresh的方向PRR跌落下去,受限於擁塞窗口,即時准備再多的可發送數據也無法著實發送出去!bbr引擎可以即時消化掉RACK送上來的能源,二者配合就開啟了一台動力十足的高端引擎。

RACK(請先看這個draft)從名字上看,Recent ACK,即最近的ACK,當然也包括SACK,因此正確的名字應該是Recent (s)ACK,RACK並不記錄數據被(s)ACK的時間,而是在收到ACK的時候,記錄被該ACK確認的數據包的發送時間,在這些發送時間中取Recent,即最晚發送的。RACK的思想是,記錄這個Recent (s)ACK所確認數據包的發送時間T.rack,然後給定一個時間窗口twin,在時間T.rack-twin之前發送的未被確認的數據包均被標記為LOST,然後這些數據包會被交給發送邏輯去發送。這非常符合常理。

RACK的代碼超級簡單,核心邏輯就一個文件兩個函數,位於net/ipv4/tcp_recovery.c中的:

/*

* 在一次(s)ACK的處理過程中更新被確認的數據包的最晚發送時間rack.mstamp。

* xmit_time-當前處理的被確認的數據包的發送時間

* sacked-當前處理的被確認的數據包的sacked標記,被選擇確認過嗎?被重傳過嗎?等等。

*/

void tcp_rack_advance(struct tcp_sock *tp, const struct skb_mstamp *xmit_time, u8 sacked);

/*

* 根據tcp_rack_advance記錄的最晚發送的被確認的數據包的發送時間rack.mstamp以及

* 重傳隊列裡未被選擇確認的數據包的發送時間skb.mstamp的差,判斷是否標記為LOST。

* RACK內置有一個twin,凡是符合rack.mstamp-skb.mstamp>twin的數據包,均標記為LOST。

* 如果該數據包被重傳過,那麼清除其被重傳過的印記!

*/

int tcp_rack_mark_lost(struct sock *sk);

以上就是關於RACK的兩個接口,TCP在處理ACK的時候會調用這兩個接口:

1).處理ACK攜帶的信息(TCP頭的ACK號或者選項中sACK塊)時調用tcp_rack_advance;

2).在發送ACK並非順序ACK時,進入異常Alert時,調用tcp_rack_mark_lost。

這個RACK機制的簡單性就在於,它不再區分正常的順序ACK以及SACK,它只比較時間戳,不管發送順序如何,只基於確認攜帶的信息來決定一個數據包是不是要被重傳。以下面的序列為例:

1|2|3|4|5|6|7|8|

假設一個ACK確認了4,那麼UNA則是5,假設這個ACK沒有攜帶SACK信息,只是確認了4,那麼rack.mstamp就是4發送的時間了,現在的問題是,4之後的5,6,7,8怎麼可能在4之前發送呢?它明明是位於4之後的啊!RACK的簡單性就體現在這裡!5,6,7,8雖然在4後面但是誰也不能保證它們就一定是按照序列號順序發送的,更加合理的做法是記錄發送時間序列!典型的場景是,4,5,6,7,8均是重傳過的數據包,首先重傳了7,8,然後重傳了6,然後重傳4,最後重傳5,這樣發送的時間序列就是:

1|2|3|7|8|6|4|5|

現在4被確認了,按照上述時間序,我有理由繼續等待5的確認到來,因為5是在4之後發送的,然而7,8,6卻都是在4之前發送的,到底等不等呢?這裡7是最先發送的,要判斷skb7.mstamp與rack.mstamp之間的差值了,如果大於twin,說明繼續等待7的選擇確認是不可忍受的,反之,如果在twin之內,那麼便有理由繼續等待了,可能是亂序了!同樣的策略處理8和6。

這樣處理是不是更加簡單些呢?只需要按照時間序比較數據包最近一次的發送時間與rack.mstamp之間的差即可!完全忽略這個數據包是不是曾經被重傳過,這樣就解決了按照序列號進行LOST判斷的復雜性問題。

然而,在亂序的情況下,你可能會認為RACK機制可能會誤傳很多並未丟失(實際上是亂序到達或者ACK亂序反饋)的數據包,事實上這裡就體現了twin時間窗口的作用,RACK的時間序並非嚴格的時間序,它是一個帶有緩沖的准時間序機制。就算你認為twin也沒用,再不濟你也可以將RACK關掉的!

注意twin的選擇,一般而言是最小RTT的1/4,這裡的最小RTT是與SRTT無關的,它是真實測量出來的離散RTT的win_minmax(請在《Google's BBR TCP擁塞控制算法的四個變速引擎》看win_minmax詳情)最小值,基於一個自動向後滑動的時間窗口采樣的最小RTT,這裡的主要目的不是平滑掉噪點(這有點像鴕鳥策略...),而是過濾掉非擁塞導致的抖動,這是一種主動發現噪點的行為,可以將BufferBloat的影響最小化。

有了RACK機制,bbr再也不用為無包可發這種事發愁了。

只要bbr可以根據(s)ACK采集到帶寬和RTT,那麼bbr就可以根據這些帶寬,RTT的反饋全速率運行,而能讓帶寬和RTT反饋回來的,正是發出去的包,再次重申,無論是新包還是重傳包,只要發送去,它們都可以反饋回結果,不管是ACK,SACK,還是DSACK...我們又繞回來了,RACK機制即使在已經發生丟包/亂序等事件時也能提供源源不斷的可發送的數據包(即標記為LOST的數據包)。這個過程平滑的運行,不必再等待RTO超時!

在bbr之前,一旦發生丟包或者嚴重亂序,TCP就會接管擁塞控制算法,但現在不了!以前的做法是錯誤的,所謂的丟包,亂序,這些都是TCP的擁塞控制狀態機邏輯自己猜的,相當大程度是不真實的,任何算法都無法准確猜出是不是真的發生了丟包,想欺騙一只蝙蝠撞牆是很容易的,同樣,TCP也是一個瞎子!所以,bbr的做法是正確的:

bbr算法本身:計算發送速率和窗口。

TCP擁塞控制狀態機:准備新數據,標記LOST(傳統方式以及RACK方式),即提供可傳輸的數據包,灌入bbr算法提供的食道。

TCP傳輸邏輯:實際傳輸任何可以傳輸的數據包,新的數據包,所有標記為LOST的數據包。

以上3者相互配合,回答了”傳輸多少?“,”傳輸什麼?“,”怎麼傳輸?“等問題,並且三者之間完全基於當下的(s)ACK反饋來交互,彼此之間則完全獨立。

...

接下來干什麼?

接下來我來吐個槽,事情要從工業革命後,人們企圖將蒸汽機裝在馬車上開始...

Copyright © Linux教程網 All Rights Reserved