1. 程式人生 > >linux核心工程導論-網路:tcp擁塞控制

linux核心工程導論-網路:tcp擁塞控制

這篇文章本來是在tcp那篇裡面的,但是那篇太長了,不專一。就完善了一下提取出來了。

TCP擁塞控制

       擁塞控制討論的是很多個同時存在的tcp連線應該怎麼規劃自己的資料包傳送和接收速度,以在彼此之間共享頻寬,同時與其他實體的機器公平的競爭頻寬,而不是自己全佔。
       擁塞控制的核心是AIMD(additive-increase/multiplicative-decrease ),線性增加乘性減少。為啥不用線性增加線性減少,或者是乘性增加乘性減少呢?這個有人專門研究過,只有AIMD可以收斂聚合使得鏈路公平。具體的過程我也不清楚,只知道結論。

擁塞檢測

視窗(每一段應當補例子)

       由於sequence機制天然的具有對網路擁塞的感知能力。因此感知到擁塞後如何反應處理就是應該考慮的問題了。這部分有些偏數學,所以學術界很喜歡研究這個問題。自然的,相對應的擁塞控制演算法就數不勝數。
       目前最廣泛被接受的思想和演算法是這樣的:基於網路速度突然發生急劇變化的概率小,擁塞是逐漸發生的(人看來仍然很快,但機器看來是有個過程的)。而在這個過程中資料是逐漸變得不可達。因此,雙方應該有學習機制。TCP留下了用於實現這個的域:視窗。這個視窗也是代表記憶體接收快取的大小,快取本身就有緩衝作用,因此即使是突然變化的網路,在快取中也是有一個過程。正常的通訊快取不會滿,但是一方傳送了很多資料(這些資料直到確認前都在傳送快取中),卻沒有被確認,那麼可用的傳送快取就越來越小,所以其傳送量就開始收縮。同樣的,接收方知道自己的接收快取的大小,也就是知道自己的接收能力。所以其也會及時的告知傳送方允許對方傳送的最高速度。意思是,我現在能一次接收N個位元組,你一次性發我,不要多,也最好不要少。
       這個快取還有一個很重要的功能就是亂序重組。接收到的資料包放在快取中,收齊了才會返回給上層,也正是如此,接收快取如果在不穩定的網路中很容易被填滿。
       總結一下:接收快取的作用是規定最大接收速度和亂序重排。傳送快取的作用是丟包重傳和控制傳送速度。
       TCP規定的這個視窗與快取的大小有關,與確認對方傳送資料的sequence組合,表示的就是接下來可以接收的資料序號區間。視窗只用來告知對方自己的接收能力,不用來表達自己的傳送能力。這個傳送能力需要根據對方的接收能力和當前自己對通道的估計自己進行調整。核心的思想就是你不需要再資料包中告訴自己該怎麼操作,你應該建議對方如何。
       通過視窗檢測並控制資料流量的主要演算法有:

RTT

       判斷是否擁塞,不但可以通過視窗檢測擁塞,還可以通過往返時間的直接測量。視窗檢測是觀察,往返時間屬於測量。最早的擁塞控制演算法Vegas就是根據RTT來監測和控制的。但是由於RTT不是根據實際的丟包率來計算的,而是根據往返時間,而網際網路,尤其是無線網,RTT變大並不意味著不可達或者擁塞,這時使用Vegas演算法的就開始主動降低自己的速度(因為它判斷網路擁塞了),而其他的基於丟包率的演算法則並不減小視窗。導致Vegas為人民服務,把可用頻寬讓給別人了。這種損己利他的做法和nagle一樣,是註定要消亡的。
但是RTT仍然對擁塞控制有至關重要的作用(除了後來的完全不依賴rtt的cubic演算法),大部分演算法的都是在收到ack回覆的時候才把視窗增加一個MSS。這就是慢啟動的部分(但是增長速度相當快)的原理。

擁塞避免

       治亂於未亂是最合理的治安方法。害怕發生擁塞首先要想辦法避免擁塞。要避免擁塞就要分析擁塞發生的原因,無疑是網路傳輸問題。然而事情並沒有那麼簡單,是什麼導致了網路傳輸的擁塞呢?你可能不假思索的說是傳輸的資料太多。然而大多並不是這種業務上的問題,而是技術上的問題。
       這裡的擁塞避免有兩個維度的術語。一個是我們不瞭解細節技術時候認為的避免擁塞。另外一個是技術上的慢啟動演算法。整個擁塞控制過程包含了慢啟動(視窗指數增長)/擁塞避免過程(視窗線性增長)/發生了擁塞的處理這三個過程(後兩個從技術上講是一樣的)。

頻寬節省

       很自然的,避免擁塞的最好方法就是別發那麼多資料。但是這對於使用者來說是不現實的,畢竟傳輸資料是網路存在的根本意義。但是核心還是儘可能的從技術上減少使用者的資料。典型的就是nagle演算法。

Nagle

       TCP傳輸的資料有兩種:命令和資料。TCP明不知道它的流中是命令還是資料。命令的特點是短,而且需要立即響應。資料的特點是長,可以多接收一些再進行響應。針對資料,一般吞吐量較大,一般立即傳送即可,因為上層每次提交到核心要傳送的內容就不少。但是針對命令,如果每次都是立即傳送,則本可以合併在一起傳送的資料包被拆成了多個,使得傳送同樣的資料佔用了更多的頻寬。為此,linux設計了nagle演算法。
       Nagle演算法規定傳送了一個包出去,一直要等到該包的回覆才會傳送第二個包,在此過程中,資料在傳送快取中快取累積。如此就可以將盡量多的命令資料合併節省上行頻寬。但是這個演算法的初衷是美好的,但是效果是可悲的,更可悲的是還是預設開啟的。因為,nagle演算法對頻寬的節省是通過對自己發出的命令的延時進行了,超時還是得立即傳送。但是就是這個延時讓自己的應用程式感受到系統響應的緩慢。而且這個緩慢還是自己給別人節省上行頻寬(傳送命令一般佔不了自己的多少頻寬)造成的。這個時候只要自己關閉掉nagle演算法,就會發現自己的傳輸響應明顯加快。典型的應用是samba如果關閉nagle,tcp的傳輸速度一般會提高。Nagle這種捨己為人的演算法設計在市場中是不得人心的,但是初衷是好的。

擁塞控制

       我們能檢測到擁塞,我們也得避免擁塞。現代的所有擁塞避免演算法都是基於4個核心概念展開的:慢啟動、擁塞避免和快速重傳、快速恢復。這4個基本演算法是由Reno擁塞避免演算法首先提出的,後來在TCP NewReno中又對“快速恢復”演算法進行了改進,近些年又出現了選擇性應答( selective acknowledgement,SACK)演算法,還有其他方面的大大小小的改進,成為網路研究的一個熱點。

慢開始(慢啟動)與擁塞避免

       接收方永遠通報自己的視窗,例如300個位元組。傳送方根據接收方發來的視窗計算出自己在當前視窗下可以傳送的資料序號,例如從200-500,共300個,接下來可以任性的傳送,不需要等待ack。假設在接收端回覆了下一次視窗為500,接收確認的是400序號,則傳送端計算接下來可以任性發送的序號是400-900,共500個位元組。如此周而復始。
       慢開始演算法就是:當新建連線時,cwnd初始化為1個最大報文段(MSS)大小(或者整數倍,linux預設是65535),傳送端開始按照擁塞視窗大小發送資料,每當有一個報文段被確認,cwnd就增加1個MSS大小。這樣cwnd的值就隨著網路往返時間(Round Trip Time,RTT)呈指數級增長,事實上,慢啟動的速度一點也不慢,只是它的起點比較低一點而已。
       上面的情況是接收方視窗在不斷的增大,這種情況一般會在所有的TCP連線建立初期發生。由於兩個節點建立TCP連線的時候並不知道鏈路的質量,所以傳送端也不好確認一下子可以任性的傳送多少資料出去,所以作為一個對通道的探測,TCP連線建立的初期,接收端一般會把視窗設的很小,然後成倍的增大。這叫做慢啟動。增大到一定的值後就會慢速增加(線性),是一個逐漸適應的學習過程。從指數增加切換到線性增加的閾值叫做Slow Start Threshold (SSThresh)。很多演算法就在這個值上做文章(例如動態的變化這個值)
       進入的視窗值慢速增加的過程就是擁塞避免的過程。因為剛開始的時候通常不會發生擁塞,這個時候慢開始設定的初始視窗太小,為了快速到達最大速度,視窗是指數增加的,但是到了某一個設定的閾值,增加視窗就得線性增加以防止過快的發生擁塞(快到極限的時候慢速試探),這個線性增加視窗的過程就是擁塞避免的過程。這個設定的閾值叫做慢啟動門限 (SSThresh)。
       在慢啟動階段如果發生了擁塞,接收方就縮小自己的接收視窗至1(或者是系統的預設值65535),如此傳送方就不會發送那麼多的資料,重新還是執行慢開始演算法。這個演算法是TCP Tahoe。而TCP Reno的做法是在慢啟動的過程中當檢測到擁塞時,把慢啟動門限SSThresh設定為當前視窗的一半,所以就會強制立即進入擁塞避免階段。

慢開始的缺陷

       慢開始與擁塞避免使基於一個常識性的假設:收到報文了說明網路質量有提高的空間,而傳送端要推測有多大提高空間呢?這就與rtt和當前的視窗大小相關。所以這個演算法是在收到確認後才增加。而在丟包比較嚴重的環境,比如wifi,其實頻寬是有很大的,只是丟包重,這時候這種依據ack確認的機制表現並不好,增長會比較慢,並且比較容易被誤認為是頻寬不夠。也就是說傳送端無法區別網路擁塞與鏈路質量(兩者帶來的效果都是丟包)。
       並且由於是慢開始,剛開始的時候增長速度雖然很快,但是基數畢竟很小,所以這種對於短連線的效果非常差。人們不是試圖為短連線採用更好的演算法,而是基於現在大家都使用的這種演算法想了很多對策。比如同時開多條tcp連線,或者是儘量的複用同一條連線。但是比如web server這種服務就無法正確的被滿足了。所以你會發現,現在的流量器開啟一個網站的時候會使用很多個tcp連線去下載不同的資源。

快速恢復與快速重傳(如何減少視窗的調整)

       視窗增大的過程是擁塞避免的過程,視窗減小的過程是擁塞控制的過程。擁塞控制就是在檢測到發生了擁塞(或可能的擁塞),通訊雙方的反應情況。擁塞視窗的調整可以實現擁塞控制。前面說的是慢開始演算法的一部分。

快速重傳

       除了增大視窗,還有減小視窗的情況。減小一般是劇烈的。在檢測到網路中發生了擁塞之後(收不到資料),接收到就會縮小自己的接收視窗,但是怎麼縮小就是各個演算法不同的了。所謂的AIMD演算法就是在這裡發揮作用的。AIMD的加性增長就是指衝突避免的過程的視窗線性增大,而乘性減少就是發生了擁塞之後的規避機制(沒發生擁塞之前視窗一直在增大,直到物理的快取記憶體不夠放)。快速重傳就是避免減小視窗而導致視窗波動的演算法。
       網路是不可靠的,這個不可靠在傳送和接收兩端的表現是收到重複的包和沒有收到包。在TCP中,收到重複的包會導致困惑的是傳送方收到重複的ack。其可能認為是網路問題,也可能是接收方一直沒有收到某個資料而傳送的重複的ack。TCP沒有為這種情況設計額外的機制好讓接收方可以在每次傳送同樣的ack時在資料包上有區別。這就給傳送方帶來了困擾。傳統的收到多個ack傳送方會認為是網路重傳,直到其tcp超時機制啟動發現之前傳送的包超時沒有被回覆ack,傳送方才判斷多個ack是自己傳送的包接收方沒有收到,而不是傳送方收到網路原因重複的包。快速恢復演算法就是在收到3個(或4個,看實現)重複的ack時就判斷做出是因為自己傳送的包丟失的結論,從而判斷網路擁塞發生。一判斷擁塞發生就啟動重傳,但是這時候的重傳並不像慢開始演算法,將視窗回到1,重現開始執行演算法。這時候很大概率只是偶然的網路丟失,所以其只是重發丟失的包,按照原來的速度繼續傳送。這叫快重傳。這種機制可以顯著的降低錯誤的收縮視窗的概率。
       由此可見TCP機制設計的精巧,堪稱非同步問題解決的典範。這種設計也是完全建立在物理網路的特性的基礎之上的。因為現在的網路是盡力而為的網路。當需求超出其負荷時,其反應是降低服務質量,而不是限制接入數量。如此的網路設計,就讓工作在其上的所有協議都要考慮丟包和重傳的問題。

快速恢復

快重傳與快恢復是一個演算法,但是由於歷史原因,說起來像兩個演算法的名字。他們分別描述該演算法的兩個過程:快速恢復與快速重傳。快重傳成功了,自然就快速恢復了。

其他服務於快速恢復演算法的機制
SACK

       SACK(選擇性確認)是TCP選項,它使得接收方能告訴傳送方哪些報文段丟失,哪些報文段重傳了,哪些報文段已經提前收到等資訊。
根據這些資訊TCP就可以只重傳哪些真正丟失的報文段。需要注意的是隻有收到失序的分組時才會可能會發送SACK,TCP的ACK還是建立在累積確認的基礎上的。也就是說如果收到的報文段與期望收到的報文段的序號相同就會發送累積的ACK,SACK只是針對失序到達的報文段的。

D-SACK

       重複的SACK。RFC2883中對SACK進行了擴充套件。SACK中的資訊描述的是收到的報文段,這些報文段可能是正常接收的,也可能是重複接收的,通過對SACK進行擴充套件,D-SACK可以在SACK選項中描述它重複收到的報文段。但是需要注意的是D-SACK只用於報告接收端收到的最後一
個報文與已經接收了的報文的重複部分

FACK

       FACK(提前確認)演算法採取激進策略,將所有SACK的未確認區間當做丟失段。雖然這種策略通常帶來更佳的網路效能,但是過於激進,因為SACK未確認的區間段可能只是傳送了重排,而並非丟失。

實際的擁塞控制演算法實現

       前面是理論基礎,但是實現上要考慮不同的東西,設計不同的引數比例。而且網路不斷的出現新的情況,需要針對性的進行調整。近幾年來,隨著高頻寬延時網路(High Bandwidth-Delay product network)的普及,針對提高TCP頻寬利用率這一點上,又湧現出許多新的基於丟包反饋的TCP協議改進,這其中包括HSTCP、STCP、BIC-TCP、CUBIC和H-TCP。現在cubic是linux使用最多的擁塞控制演算法,ubuntu的預設演算法。這裡有一個常見的演算法列表:
這裡寫圖片描述
       總的來說,基於丟包反饋的協議是一種被動式的擁塞控制機制,其依據網路中的丟包事件來做網路擁塞判斷。即便網路中的負載很高時,只要沒有產生擁塞丟包,協議就不會主動降低自己的傳送速度。這種協議可以最大程度的利用網路剩餘頻寬,提高吞吐量。然而,由於基於丟包反饋協議在網路近飽和狀態下所表現出來的侵略性,一方面大大提高了網路的頻寬利用率;但另一方面,對於基於丟包反饋的擁塞控制協議來說,大大提高網路利用率同時意味著下一次擁塞丟包事件為期不遠了,所以這些協議在提高網路頻寬利用率的同時也間接加大了網路的丟包率,造成整個網路的抖動性加劇。這種演算法就相當於明知道網路要飽和了,但是還沒有飽和的餘量我要來佔用,所以只有某個人用會佔盡便宜,但是大家都用就會過快的飽和。TCP擁塞控制是個典型的個人利益最大化集體利益最小化的博弈過程。
       BIC-TCP、HSTCP、STCP等基於丟包反饋的協議在大大提高了自身吞吐率的同時,也嚴重影響了其他流的吞吐率。基於丟包反饋的協議產生如此低劣的TCP友好性的組要原因在於這些協議演算法本身的侵略性擁塞視窗管理機制,這些協議通常認為網路只要沒有產生丟包就一定存在多餘的頻寬,從而不斷提高自己的傳送速率。其傳送速率從時間的巨集觀角度上來看呈現出一種凹形的發展趨勢,越接近網路頻寬的峰值傳送速率增長得越快。這不僅帶來了大量擁塞丟包,同時也惡意吞併了網路中其它共存流的頻寬資源,造成整個網路的公平性下降。但是也正是因為沒有更高的權威,所以這種演算法註定要成為通用的演算法。其實我也認為,擁塞避免不應該是server自己的事情,而應該是路由器qos的事情。我傾向於所有的tcp都直接不要擁塞控制,視窗上來就設定為記憶體支援的最大大小就好了,能不能發成功,是否擁塞,交給路由器去煩心吧。自己只需要檢測丟包率,太高的時候才降低自己的視窗。
       技術上,這些演算法不過都是對Reno演算法在視窗何時以何種幅度增大減小的控制策略上的改變。從表格中可以看到,除了機遇丟包反饋的,還有基於延時和綜合的還有訊號的(不太討論範圍)。由於現在是機遇Loss的統一天下了,這裡只討論這種。

HSTCP(High Speed TCP)

       HSTCP(高速傳輸控制協議)是高速網路中基於AIMD(加性增長和乘性減少)的一種新的擁塞控制演算法,它能在高速度和大時延的網路中更有效地提高網路的吞吐率。它通過對標準TCP擁塞避免演算法的增加和減少引數進行修改,從而實現了視窗的快速增長和慢速減少,使得視窗保持在一個足夠大的範圍,以充分利用頻寬,它在高速網路中能夠獲得比TCP Reno高得多的頻寬,但是它存在很嚴重的RTT不公平性。公平性指共享同一網路瓶頸的多個流之間佔有的網路資源相等。
       TCP傳送端通過網路所期望的丟包率來動態調整HSTCP擁塞視窗的增量函式。
       擁塞避免時的視窗增長方式: cwnd = cwnd + a(cwnd) / cwnd
       丟包後窗口下降方式:cwnd = (1-b(cwnd))*cwnd
       其中,a(cwnd)和b(cwnd)為兩個函式,在標準TCP中,a(cwnd)=1,b(cwnd)=0.5,為了達到TCP的友好性,在視窗較低的情況下,也就是說在非BDP的網路環境下,HSTCP採用的是和標準TCP相同的a和b來保證兩者之間的友好性。當視窗較大時(臨界值LowWindow=38),採取新的a和b來達到高吞吐的要求。具體可以看RFC3649文件。

westwood

       無線網路中,在大量研究的基礎上發現tcpwestwood是一種較理想的演算法,它的主要思想是通過在傳送端持續不斷的檢測ack的到達速率來進行頻寬估計,當擁塞發生時用頻寬估計值來調整擁塞視窗和慢啟動閾值,採用aiad(additive increase and adaptive decrease)擁塞控制機制。它不僅提高了無線網路的吞吐量,而且具有良好的公平性和與現行網路的互操作性。存在的問題是不能很好的區分傳輸過程中的擁塞丟包和無線丟包,導致擁塞機制頻繁呼叫。

H-TCP

       高效能網路中綜合表現比較優秀的演算法是:h-tcp,但它有rtt不公平性和低頻寬不友好性等問題。

BIC-TCP

       BIC-TCP的缺點:首先就是搶佔性較強,BIC-TCP的增長函式在小鏈路頻寬時延短的情況下比起標準的TCP來搶佔性強,它在探測階段相當於是重新啟動一個慢啟動演算法,而TCP在處於穩定後窗口就是一直是線性增長的,不會再次執行慢啟動的過程。其次,BIC-TCP的的視窗控制階段分為binary search increase、max probing,然後還有Smax和Smin的區分,這幾個值增加了演算法上的實現難度,同時也對協議效能的分析模型增加了複雜度。在低RTT網路 和低速環境中,BIC可能會過於“積極”,因而人們對BIC進行了進一步的改進,即CUBIC。是Linux在採用CUBIC之前的預設演算法。
       但是在長肥管道下,BIC的侵略性是正好的。

CUBIC

       CUBIC在設計上簡化了BIC-TCP的視窗調整演算法,在BIC-TCP的視窗調整中會出現一個凹和凸(這裡的凹和凸指的是數學意義上的凹和凸,凹函式/凸函式)的增長曲線,CUBIC使用了一個三次函式(即一個立方函式),在三次函式曲線中同樣存在一個凹和凸的部分,該曲線形狀和BIC-TCP的曲線圖十分相似,於是該部分取代BIC-TCP的增長曲線。另外,CUBIC中最關鍵的點在於它的視窗增長函式僅僅取決於連續的兩次擁塞事件的時間間隔值,從而視窗增長完全獨立於網路的時延RTT,之前講述過的HSTCP存在嚴重的RTT不公平性,而CUBIC的RTT獨立性質使得CUBIC能夠在多條共享瓶頸鍊路的TCP連線之間保持良好的RTT公平性。

STCP,Scalable tcp。

       STCP演算法是由 Tom Kelly於 2003年提出的 ,通過修改 TCP的視窗增加和減少引數來調整發送視窗大小 ,以適應高速網路的環境。該演算法具有很高的鏈路利用率和穩定性,但該機制視窗增加和 RTT成反比 ,在一定的程度上存在著 RTT不公平現象 ,而且和傳統 TCP流共存時 ,過分佔用頻寬 ,其 TCP友好性也較差。

TCP Proportional Rate Reduction

       這是壓軸的主角,核心3.2之後預設使用這個演算法。不過rfc上寫的是試用期的。