TCP擁塞控制


0x01 為什么要做擁塞控制

 我們知道TCP是一個可靠的傳輸層協議,與UDP最大的不同首先是可靠,然后是,為了實現可靠性,TCP需要在發送端和接收端維護發送窗口和接收窗口來緩存尚未被確認的報文。發送窗口是擁塞控制算法對當前網絡傳輸能力的一個評估,發送窗口越大,擁塞控制算法認為

 

 

那么什么時候是網絡擁塞呢?根據鄉農公式,每條信道都有其最大容量,比如我們通過ethtool看到的網卡工作帶寬100M,1000M等,當網絡主機向網絡中灌輸的流量大於鏈路瓶頸帶寬時,鏈路瓶頸處的路由器由於無法即時轉發所有報文,需要先將部分報文緩沖在本地,等待端口空閑時轉發,那么這個時候網絡就可以被稱作發生了擁塞。從以下幾個角度看,我們都應該讓主機主動探測網絡狀況,並在擁塞時降低報文的發送速率。

  1. 傳輸效率角度

路由器緩沖報文本是為了平滑鏈路中的突發流量,即路由器希望鏈路中的總體流量和其帶寬基本一致,可能有時候多一點,有時候少一點,多的時候,路由器就緩沖一部分,並在鏈路流量少的時候把緩沖區內的流量也排空,但路由器的緩沖區大小也會有一個上限,一般都為鏈路的一個BDP級別,當路由器實在緩沖不了上游超發的流量的時候,只能丟掉后來的報文,那么這些被丟掉的報文白白浪費了從源點傳輸到該路由器的帶寬,造成了傳輸效率的下降,從端點來看,就是發送端統計的吞吐率(Throughput)和接收端收到報文的速率(Goodput)有較大差距。

  1. 時延角度

當然為了減少鏈路的丟包,我們可以增大路由器的緩沖區大小,把所有上游超發的報文都緩沖下來,等待鏈路空閑時進行轉發,但這又會導致另外一個問題,那就是BufferBloat現象,路由器緩沖區大小被無限制的擴大,甚至出現部分路由器永遠有報文緩沖在緩沖區內,導致所有上游的報文都要在緩沖區內排一會兒隊才能夠轉發到下游,造成了不變要的排隊時延。我們所感受到的網絡時延,其實是由主機協議棧打包解包時延,鏈路傳播時延和路由器內的排隊時延組成,其中協議棧的解包打包由於主機性能提升已經微不足道,基本都在納秒級別,而鏈路的傳播時延和網絡的拓撲,空間距離的遠近有關,受限於物理條件和光速已經很難突破,倒是排隊時延還有很大的降低空間。例如路由器的緩沖區大小設置為一個BDP,而端主機采用諸如Reno,Cubic等擁塞控制算法時,路由器內緩沖區基本就處於高占用率的狀態了,緩沖區達到一個BDP意味着排隊的時延和鏈路的傳播時延基本在一個等級了,這里面的優化空間還是很大的,目前也是有很多針對BufferBloat的算法,例如BBR,Codel等。

圖1  TCP-Reno算法的吞吐率和隊列長度變化

其實,當上游的流量達到路由器出口帶寬時,上游繼續增大發送窗口除了帶來額外的排隊時延,不能帶來一點好處,上游的吞吐率也不會因為發送窗口的增大而增大,這是由窗口機制本身造成的,由於受下游速率的限制,超發的報文也只能緩存在路由器緩沖區中。典型的,比如圖1中的Reno算法,Reno很簡單就是線性增長發送窗口,直到探測到丟包,才會把窗口減小,那么當發送窗口達到BDP后,圖中10秒左右處,繼續增大窗口,上游的吞吐率(紅線)會維持在鏈路瓶頸帶寬,而路由器緩沖隊列(綠線)隨之慢慢增大,直到達到路由器緩沖區的最大長度,並發生丟包。關於這個,早在,TCP-Vegas,TCP-BBR也都是基於這一現象決定擁塞控制的時機的。

  1. 公平性角度

除了上面兩個原因,TCP還要考慮公平性,這也是TCP要進行擁塞控制的重要原因,如果不考慮大家的感受,網絡就是一個囚徒困境,增大自身的吞吐率總會比減小吞吐率收益更大,雖然會有人遭殃,but who cares...也確實有人這么干的,比如某某加速。因此,擁塞控制的一個重要目標就是讓大家能夠一個相對均勢,不單單是一種擁塞控制算法與另外一種擁塞控制算法,一種擁塞控制算法的多條數據流之間也有肯能存在不公平。

 

圖2 TCP-Cubic 公平性收斂

0x02 擁塞控制基本原理

擁塞控制的核心問題是如何獲得當前鏈路的狀態,鏈路是否發生了擁塞,擁塞的程度是多少,或者當前空閑的帶寬是多少,當前共有多少條數據流在共享鏈路等等,主機根據當前的鏈路狀態決定是否增加發送窗口,增大多少等。

基於丟包

最典型的的方法是以丟包為鏈路發生擁塞的依據,在未檢測到丟包時,不斷增大發送窗口去探測網絡的帶寬,而當檢測到丟包時,便認為鏈路擁塞,並減小發送窗口,代表算法有TCP-Reno,TCP-Cubic等。圖2中的Cubic算法就是一個例子,但看第一條流flow0啟動至flow1加入網絡的這一段時間內,其發送窗口一直處於不斷增大的狀態,到500個報文段時,鏈路出現丟包,並開始減小,然后一直處於這一個循環中。這類算法一般都有以下幾個特點:

  1. 一般來說簡單可靠,當信道較好時,丟包基本等同於鏈路發生了擁塞,在網絡環境沒有那么復雜的年代,沒有什么異構化,無線網,大文件等等,簡單可靠的方法都能解決問題為什么不用呢?
  2. 特殊環境下不可靠,比如無線網絡,信道本身就會有好多丟包,發送端感受到的丟包中有大部分都不是路由器主動丟棄的,這時候如果不借助時延,ECN等信息來判斷丟包的類型的話,這類算法就只能來者不拒,把丟包全部當作鏈路發生擁塞的標志了,於是會看到下面這條鏈路利用率隨着丟包率提升而下降的曲線,非常的慘烈,這還只是100Mbps,50ms網絡下的表現,BDP越大,表現也會越慘烈,到了0.3%的丟包率時,基本就無法正常傳輸了。

 

圖3 Cubic吞吐率隨丟包率變化

 

  1. 信息量小,當鏈路出現丟包時,發送端能夠知道的就是鏈路中可能發生了擁塞,至於擁塞到底有多嚴重完全不知道,怎么辦呢,只能把當前窗口減半,或者減到70%之類的,至於減到該值后,能不能緩解當前網絡的擁塞,不知道,也有可能減小到一半后,還是有丟包,那也只能繼續減下去了,這也是為什么這類算法在有隨機丟包的環境下表現那么慘的原因了,這里放上一張實驗室測得的數據圖,
  2. 信號來得較晚,按理說圖2中10秒處,鏈路總流量已經達到瓶頸帶寬了,繼續增大發送窗口沒有任何好處,也就是達到了膝點,然而發送端不知道啊,只有到了20秒處發生了丟包,發送端才知道鏈路出現了擁塞,也就過了涯點,這個點做擁塞控制真的是已經到了末班車了,即使不進行擁塞控制,路由器上的隊列管理算法也會把該丟的報文丟了,這里就有些機制的路由器端隊列管理算法,比如RED,會在路由器緩沖區爆表前提前丟一些報文告訴上游降速。
  3. 鏈路空閑時帶寬利用率低,這個就是以丟包為擁塞控制依據的另外一個問題了,丟包只發生在擁塞時,那么擁塞前呢,怎么增長發送速率,是一個RTT把發送窗口增長一個MSS,還是翻倍,三倍?這時候發送端對鏈路更加迷茫了,只知道路由器緩沖區還沒爆表,至於是還有大量空閑帶寬還是已經到了懸崖邊緣完全不知道,只好硬着頭皮往上走了,當然Bic,Cubic算法相對聰明些,它們記錄了擁塞的歷史信息,以上一次丟包時的窗口大小為鏈路瓶頸帶寬,來加快帶寬探測的速率。

基於網絡測量類

網絡測量類一般指基於時延或者瓶頸帶寬,從圖1可以看到,當上游流量達到瓶頸帶寬時,瓶頸路由器就會產生一個緩沖隊列,而發送端也會感受到時延增大,同時吞吐率也不再隨着發送窗口的增大而增大了,發送端也就能夠獲知當前網絡已經進入了擁塞狀態了,並采取相應的處理。先說優點吧:

1. 首先根據時延變化來判斷網絡是否擁塞能夠有效解決信道不可靠時隨機丟包影響鏈路利用率的問題,時延的測量結果基本不受丟包的影響,如果擁塞控制算法完全依賴時延是否增大來判斷鏈路是否擁塞,即不在檢測到丟包時減小發送窗口,是完全能夠屏蔽掉隨機丟包帶來的影響的。但完全無視丟包是一個非常激進的做法,事實上Linux內核的擁塞控制算法中只有最近加入的BBR才完全無視了丟包,完全無視掉丟包可能會帶來另外一些問題,比如是否對基於丟包類的擁塞控制算法公平,又比如,丟包也有可能是網關限速丟掉的呢,還是有很多要考慮的因素的。

 

基於路由器反饋類

瓶頸帶寬大小 ,當前的總流量,共享該鏈路的數據流數量,有了這三個信息,擁塞控制就可以做的無比精准,最簡單的方法就是路由器直接把當前帶寬平均分配到每條流,並把每條流得到的帶寬寫入報文捎帶給發送端,發送端以此作為吞吐率,這種方法能夠得到理論的最佳值,當然即使是在路由器,准確的估計當前鏈路中流的數量也不是一件容易的事情,但相對於端主機去做這件事情,還是准確多了,基於路由器反饋類的算法需要配套的鏈路狀態獲取協議例如ECN,DCTCP,XCP,RCP等等,相對來說比較小眾,可靠性高,性能好,但需要鏈路上的所有路由器都支持信息的反饋,部署成本太大,但是在數據中心之類的可控范圍內還是很有用處的。

0x04 擁塞控制算法分析

TCP-Reno

Reno應該是最簡單的想法了,發送端根據是否丟包判斷網絡中是否發生了擁塞,沒有發現丟包時,發送端每個RTT把發送窗口調大一個MSS繼續探測網絡的容量,當網絡中出現了一定數量的重復確認報文(dup ack)時認為網絡發生了擁塞,並將發送窗口減半,繼續探測,而當出現RTO時,認為網絡狀況更加糟糕,直接把發送窗口打回兩個MSS。

TCP-Cubic

 Cubic衍生於Bic算法,Bic覺得Reno那樣線性探測帶寬上限的做法過慢,無論是每個RTT增長一個MSS,還是每個RTT增長多個MSS,都無法改變算法O(n)的本質,Reno探測到帶寬上限的時間正比於BDP,帶寬利用率低。BIC認為上一次發生擁塞時的發送窗口大小是一個非常有價值的信息,應當充分利用,短時間來看,網絡帶寬不會出現較大變化,上一次擁塞時的窗口值很有可能是帶寬的上限,因此Bic要做的就是在當前窗口和上次擁塞時的窗口之間進行搜索,來找到合適的窗口值,既然是搜索,那么為什么不用最簡單,最可靠的二分查找呢,把下一個RTT的窗口值 Cwnd_next 設置為當前窗口值和擁塞點窗口值和的一半,如果下一個RTT發生了擁塞則需要在 Cwnd_next 和 Cwnd_now之間進行查找,否則在Cwnd_next 和 Cwnd_high之間進行查找,Bit將查找帶寬上限的速度大大提高了。Bic有一個缺陷,它每進行一次二分查找,需要一個RTT的時間,因此不同RTT數據流查找的頻率不一樣,也就是說RTT小的流更容易獲得較高的帶寬。Cubic對Bic進行了更深層次的建模,從(時間,窗口)圖,如圖2上來看,Bic算法的形狀就是一個三次曲線, 因此Cubic用形如 a(t - t0)^3 的三次函數來模擬,其中參數 a 控制了算法的激進程度,a越大算法越容易搶占帶寬,這樣算法的激進程度就與RTT解耦了。在我看來,Bic和Cubic是非常不錯的算法,它們算是把網絡問題轉化為了數學問題,如果只用丟包作為鏈路擁塞擁塞的依據,真的很難想得出還有什么更好的策略了。

 

圖4 Cubic占據空閑帶寬速度

 

TCP-Vegas

前面介紹到,當發送端的吞吐率達到瓶頸帶寬后,吞吐率不會再隨着發送窗口的增大而增大了,Vegas算法就是利用這一特征來判斷當前網絡是否處於擁塞狀態。Vegas會維護一個當前鏈路的最小時延BaseRTT,這個就是信號從發送端到達接收端的時延,沒有排隊時延(當然,如果在Vegas的整個生命周期內,路由器始終處於擁塞狀態,Vegas是分辨不出來的,此時最小時延也會有排隊時延),然后它的行為也就和Reno一樣了。首先在沒有擁塞時吞吐率等於發送窗口除以RTT,即 expected throughput = cwnd / rtt,Vegas根據ACK報文估算當前實際的吞吐率 actual throughput,如果兩者相等,那么網絡上應該沒有擁塞,此時就和Reno一樣線性增長發送窗口進行探測,而如果實際吞吐率低於根據窗口估計的吞吐率,則網絡發生了擁塞,此時減小發送窗口。Vegas的終極目標是維持估計吞吐率和實際吞吐率基本相等,在兩者相等時,Vegas不再調整發送窗口,保持一個相對平穩的發送速率。但由於實際網絡中存在突發流量,很難絕對相等,於是Vegas設置了一個下限alpha,和一個上線beta,在此區間內,就認為網絡達到了最佳平衡點,路由器既沒有緩存大量報文造成排隊時延,又保證了有少量報文存在緩沖區中等待轉發。

圖5 TCP-Vegas

TCP Westwood

Westwood也算是基於時延的吧,它沒把時延用在判斷是否發生了擁塞上,而是用在了擁塞后,慢啟動閾值的設定上。在傳統擁塞控制模式下,發送窗口小於慢啟動閾值時,發送窗口會指數式地增長,而當發送窗口達到慢啟動閾值時,發送窗口將采用一個更低階的探測方式,因此,慢啟動閾值也可以說是擁塞控制算法對帶寬的一個保守估計值,即最終擁塞窗口大概率會大於慢啟動閾值。在發生擁塞時,擁塞控制算法一般會把發送窗口減半然后再慢慢向上探測來尋找合適的帶寬,當發送窗口小於慢啟動閾值時,窗口可以在很短的時間內增長至慢啟動閾值然后再采用較慢的搜索方式,因此設置一個合適的慢啟動閾值能夠大大提高擁塞后探測到新的帶寬的速度。Westwood就根據測量得到的吞吐率乘以最小RTT得到一個BDP,理論上來說,發送窗口在該BDP以下基本是安全的,於是Westwood也就把慢啟動閾值設置到該BDP來快速跳過安全階段,進入后期較危險的線性探測階段。比如下面這個Westwood的窗口變化圖,擁塞時,窗口總會被過度地減小,Westwood能將窗口迅速拉升至一個較為合理的值然后再去探測。

圖6 TCP-Westwoord窗口變化

 

 

TCP-BBR

BBR,要求使用Pacing,完全摒棄丟包,高吞吐,低時延,說是革新一點都不為過,也許只有Google有能力在Linux內核上做出這么大的改動。本來Vegas已經是擁塞控制本該的樣子,但目前網絡卻不是它該有的樣子,且不說各種加速,單是基於丟包類的算法都能夠把Vegas活活餓死。這也是BBR算法最有意思的地方,BBR並不是在感受到擁塞的第一時間就降低發送速率,它選擇了一種相對保守的做法,也就是等等看,如果一段時間(10秒一次的最低時延探測)后,網絡還是擁塞,那可能真的是自己占的帶寬太大了,這才降低發送速率,而在期間它的目標是維持在一個相對穩定的吞吐率,注意這里是吞吐率而不是發送窗口,其它數據流增大發送窗口造成網絡擁塞加劇,從而導致時延上升,而BBR則根據目標吞吐率和時延的乘積得到新的發送窗口也會增大,因此保障了和其它算法之間的競爭力。然而,基於時延的擁塞控制總是不那么穩定,而且它還存在一個問題,還記得它每隔10秒的一個時延探測周期嗎,如果在它降低速率來探測時延的一剎那,有其它流量搶占了它讓出的帶寬並形成了一定長度的隊列,從而導致它測得的時延並不是最低時延,那么TCP-BBR是不會感受到這部分隊列的,也就造成隊列一直在路由器內無法排出,就像圖8那樣,在20秒(恰好是BBR時延探測階段)處加入UDP數據流,造成了路由器持續的隊列,這也與BBR算法的初衷不符。當然對於用戶來說,把服務器的擁塞控制算法切換到BBR還是能夠很大程度提升性能的,尤其是在高時延,高丟包的網絡下,BBR相較於Cubic還是要好很多的。

 

圖7 TCP-BBR多條數據流競爭

 

圖8 UDP數據流不小心在TCP-BBR時延探測期插入

 

  

RCP

大概在2000年到2010年之間,關於顯式擁塞控制的研究達到了高潮,ECN,XCP,RCP等等,顯式擁塞控制的核心問題是如何讓路由器主動反饋信息,反饋什么樣的信息以及怎樣利用這些信息,在這幾點上RCP都可以說把擁塞控制分析的明明白白。RCP(Rate-Control-Protocol)從名字上就可以看出它的目標就是直接控制發送速率,不再像之前擁塞控制算法那樣通過窗口去探測合適的速率,RCP路由器會通過Piggy-Back的方式直接告訴端主機一個合適的發送速率,端主機直接按照該速率發送就可以了,這樣做至少帶來了以下兩點好處:

1. 顯式擁塞控制該有的優點它都有,包括對隨機丟包的免疫,鏈路利用率高,時延可控等

2. 收斂速度快,相比於一般擁塞控制算法收斂到公平帶寬和收斂到高的鏈路利用率耗時,RCP可以說是指數級的提升了

RCP的基本原理就是把帶寬平均分配到每條數據流,也就是 R = C / N , C為帶寬, N為經過路由器的數據流數量,C是路由器知道的, N是一個比較難獲得的值,常見的有一些算法可以根據5元組估算流的數量,但不適合在擁塞控制算法上用,解包到TCP層對路由器來說開銷太大,RCP使用了一個迭代的過程,當前帶寬有剩余時,證明對N的估計偏大,則將剩余帶寬平均分配給N條流,當擁塞時,證明對N的估計偏小,實際網絡可能有更多數據流,則將擁塞平均分配到N條流,減小每條流的發送速率,通過迭代最終N會收斂到經過路由器的數據流數量,這個收斂過程要比Cubic之類的慢慢爬升然后回退的探測過程要快的多。

DCTCP

微軟在2010年左右提出的DCTCP是ECN最成功的應用之一,一直以來ECN只被應用於判斷鏈路丟包是否是網絡擁塞造成的丟包,也就是返回一個True或者False,並不能提供任何關於鏈路擁塞程度的信息。ECN協議規定協議棧在接收到帶有CE標記的報文后,便會一直向對端發送帶有ECE標記的報文,直到對端返回一個CWR報文,也就是說在收到CE報文后,不管下一個報文有沒有被路由器標記為擁塞報文,接收端都會向對端通告鏈路發生了擁塞,因此,發送端收到ECE報文后,只能確定鏈路在這一段時間內發生了擁塞,並不能確定自己所發的報文里有多少個被標記為了擁塞。微軟對協議棧做了一點點修改,接收端在收到CE報文后,按照下圖的狀態機向對端發送ECE報文,這樣對端便可以推算出自己所發的報文中有多少個造成了擁塞,也就是當前網絡的擁塞程度,並按這個程度減小當前的發送窗口,從而避免了窗口被過度減小。我曾經也對DCTCP進行過試驗,結果簡直完美。。。綠色的線是路由器上的隊列長度,紅色的線是吞吐率,DCTCP基本能夠將路由器的緩沖隊列長度控制在一條直線上,當然通過設置閾值,可以將這個隊列長度向上或向下平移,也就是說DCTCP可以始終保持路由器的各個端口處於滿負載,但又不是擁塞的狀態下。DCTCP也是讓我第一次認識到擁塞控制其實最重要的是控制好路由器端的隊列長度。

 

圖9 DCTCP接收端對CE標記的處理與狀態切換

 

圖10 DCTCP路由器端緩存變化(漂亮><)

 

當然,它也不是毫無缺陷,當發生丟包時,窗口減小的幅度能夠對多條數據流收斂到公平帶寬的速度造成影響的,由於DCTCP在丟包時那么精確地減小發送窗口,它的公平性收斂速度會較Cubic要慢一些,就是下面的這個樣子,當然適當調調參數會好很多。 

圖10 DCTCP相對較慢的公平性收斂

 

0x05 總結

擁塞控制可能真的沒有太多研究的空間了,雖然Google的大神們隔三差五還能搞出個RFC來,基於純粹基於丟包類的算法,Cubic似乎已經足夠好了,探測速度也夠快了,但丟包本身就不可靠,這個問題是通過改進探測模型沒法解決的問題,基於網絡測量類倒是還有很多可以研究的地方,穩定性,可靠性還是有很大提升空間的,像Compound TCP之類的融合丟包和時延的算法還是很有意思的。擁塞控制搞到后面,要低時延還是要高鏈路利用率,要公平性還是要連利用率之類的平衡性考量了, No Silver Bullet。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM