擁塞控制是TCP在設(shè)計(jì)之時(shí)就考慮到的全局配置,因?yàn)閭鬏斅窂降墓?jié)點(diǎn)上路由器是有瓶頸的,他不能接收無(wú)限大的數(shù)據(jù)包。當(dāng)很多tcp連接都在試圖發(fā)送更多的數(shù)據(jù)包之時(shí),就有可能發(fā)生惡性擁塞事件,導(dǎo)致所有的tcp連接都無(wú)法正常工作,擁塞控制的出現(xiàn)使得tcp連接在偵測(cè)到網(wǎng)絡(luò)擁塞事件后,會(huì)降低自己的發(fā)包速率。

為了實(shí)現(xiàn)擁塞控制,tcp引入了如下幾個(gè)概念。
- 慢啟動(dòng)
- 擁塞避免
- 快速重傳
- 快速恢復(fù)
TCP 擁塞控制
-
最初的樣子 tahoe
在最初的時(shí)候是 tahoe 版本的擁塞控制算法。這種落伍的擁塞控制算法,在小幅度丟包時(shí)就會(huì)造成擁塞窗口 cwnd (congestion window)直接降低為初始擁塞窗口大?。ㄒ话銥?MSS),慢啟動(dòng)閾值 ssthresh (slow start threshold)降低為當(dāng)前擁塞窗口 cwnd 的一半。然后重新進(jìn)行一次慢啟動(dòng),再到擁塞避免狀態(tài),所以早已經(jīng)淘汰。
tahoe 和 reno 比較
-
教科書(shū)的最?lèi)?ài) reno
后來(lái)引入了reno。reno也是眾多教科書(shū)上講授擁塞控制算法的一個(gè)典范,reno的特點(diǎn)是當(dāng)收到三個(gè)dup ack 時(shí),表示可能存在丟包了。這時(shí)會(huì)觸發(fā)快速重傳(這時(shí)引入了快重傳的概念)。擁塞窗口 cwnd (congestion window)降低為當(dāng)前擁塞窗口的一半。慢啟動(dòng)閾值 ssthresh (slow start threshold)也降低為當(dāng)前擁塞窗口的一半。然后進(jìn)入快速恢復(fù) (Fast Recovery)階段,當(dāng)收到新的ACK后退出快速恢復(fù)階段接著擁塞避免狀態(tài)進(jìn)行線性增長(zhǎng)。
存在問(wèn)題,一般而言,發(fā)生擁塞后大量丟包,reno算法在快速恢復(fù)階段收到某個(gè)包重傳完成會(huì)立即退出快速恢復(fù),之后的丟包只能等待超時(shí)重傳。 改革春風(fēng)吹滿地 new reno,sack
可以看出reno只是針對(duì)一個(gè)數(shù)據(jù)包的,而實(shí)際情況下都是很多個(gè)包都出現(xiàn)了丟失的情況。所以多個(gè)數(shù)據(jù)包的丟失重傳問(wèn)題就產(chǎn)生問(wèn)題了,因此New Reno出現(xiàn)了,它在Reno快速恢復(fù)的基礎(chǔ)上稍加了修改,可以恢復(fù)一個(gè)窗口內(nèi)多個(gè)包丟失的情況。具體來(lái)講就是:Reno在收到一個(gè)新的數(shù)據(jù)的ACK時(shí)就退出了快速恢復(fù)狀態(tài)了,而New Reno需要收到該窗口內(nèi)所有數(shù)據(jù)包的確認(rèn)后才會(huì)退出快速恢復(fù)狀態(tài),從而更一步提高吞吐量。
針對(duì) new reno 舉個(gè)例子。當(dāng)A給B傳輸了 1,2,3,4,5,6,7,8 號(hào)包時(shí) 2,3 號(hào)包丟了,
new reno 的做法是先重傳 2 號(hào)包,傳完之后B這下收到了2,所以回ACK3。
A就知道原來(lái) 3號(hào)包也丟了,A再重傳3。B回復(fù)ACK9。A 就知道快恢復(fù)狀態(tài)結(jié)束了,這下1-8 號(hào)包都傳過(guò)去了。
而SACK呢,它改變TCP的確認(rèn)機(jī)制,最初的TCP只確認(rèn)當(dāng)前已連續(xù)收到的數(shù)據(jù),SACK則把亂序等信息會(huì)全部告訴對(duì)方,從而減少數(shù)據(jù)發(fā)送方重傳的盲目性。比如說(shuō)序號(hào)1,2,3,5,7的數(shù)據(jù)收到了,那么普通的ACK只會(huì)確認(rèn)序列號(hào)4,而SACK會(huì)把當(dāng)前的5,7已經(jīng)收到的信息在SACK選項(xiàng)里面告知對(duì)端,從而提高性能,當(dāng)使用SACK的時(shí)候,NewReno算法可以不使用,因?yàn)镾ACK本身攜帶的信息就可以使得發(fā)送方有足夠的信息來(lái)知道需要重傳哪些包,而不需要重傳哪些包。
- 百花齊放
隨著tcp擁塞控制算法的進(jìn)化,大致可以分為以下幾個(gè)階段。
以丟包作為依據(jù)
1、New reno: RFC6582
2、BIC :Linux 2.6.8 - Linux 2.6.18
3、Cubic:RFC8321 Linux 2.6.19
這類(lèi)算法通過(guò)ACK帶回的丟包信息來(lái)調(diào)整發(fā)送側(cè)的擁塞窗口。
以路徑時(shí)延為依據(jù)
1、Google BBR:Linux 4.9
前面階段的擁塞控制算法都是以丟包作為依據(jù)的。RTT相對(duì)于丟包信息反饋更加靈敏,能更及時(shí)的反映出一般的網(wǎng)絡(luò)擁塞狀態(tài),但是太靈敏也有可能是劣勢(shì),會(huì)導(dǎo)致算法反應(yīng)過(guò)激。
基于顯式擁塞反饋
典型的ECN利用中間節(jié)點(diǎn)檢測(cè)擁塞狀態(tài),如路由器的反饋,直接反饋給發(fā)送方,以此調(diào)節(jié)發(fā)送源的窗口值和發(fā)送速率。
慢啟動(dòng)
慢啟動(dòng)過(guò)程是為了在未知網(wǎng)絡(luò)情況下的帶寬,所以以一個(gè)非常小的初始擁塞窗口 cwnd (congestion window)進(jìn)行發(fā)送,這里的擁塞窗口會(huì)以每次×2的速度遞增。

發(fā)送窗口 scwnd = Min(cwnd,rwnd) 。也就是實(shí)際的發(fā)送的窗口是擁塞窗口的與接收窗口的最小值。
慢啟動(dòng)的初始擁塞窗口
這里又要講到過(guò)時(shí)的教科書(shū)了,過(guò)時(shí)的教科書(shū)上經(jīng)常說(shuō)初始窗口大小是1個(gè)MSS。實(shí)際上現(xiàn)在的初始窗口大小早就有10MSS了。
1、1MSS 初始擁塞窗口 RFC2001 (1997年定義,教科書(shū)的最?lèi)?ài))
2、2-4 MSS 初始擁塞窗口 RFC2421 (1998年定義,也是教科書(shū)的最?lèi)?ài))
3、10MSS 初始擁塞窗口 RFC6928 (2013年定義,也是目前最廣泛的)
擁塞避免
慢啟動(dòng)是以每次x2的速度增大擁塞窗口的,這樣激進(jìn)的方式不能一直這么使用,所以需要在到達(dá)一個(gè)點(diǎn)的時(shí)候,將擁塞窗口的擴(kuò)大變成線性的,這個(gè)點(diǎn)就是慢啟動(dòng)閾值 ssthresh (slow start threshold) 超過(guò)這個(gè)點(diǎn)就進(jìn)入擁塞避免狀態(tài)。而擁塞避免狀態(tài)的線性遞增也不是簡(jiǎn)單的加1(是不是又被過(guò)時(shí)的教科書(shū)給騙了)。
實(shí)際上的線性遞增
cwnd += SMSS * SMSS / cwnd

快速重傳與快速恢復(fù)
當(dāng)發(fā)送方發(fā)送的數(shù)據(jù)包存在丟失,如果還沒(méi)有觸發(fā)超時(shí)重傳,那么會(huì)觸發(fā)快速重傳,(超時(shí)重傳對(duì)性能是影響最大的,因?yàn)樗幸欢螘r(shí)間RTO都沒(méi)有任何數(shù)據(jù)傳輸,且擁塞窗口又要降低為0,又要進(jìn)入一次慢啟動(dòng),所以快重傳和快恢復(fù)就是為了避免超時(shí)重傳而設(shè)計(jì)出來(lái)的)即當(dāng)接收端收到比期望序號(hào)大的報(bào)文段時(shí),便會(huì)重復(fù)發(fā)送最近一次確認(rèn)的報(bào)文段的確認(rèn)信號(hào),我們稱(chēng)之為冗余ACK(duplicate ACK),如下圖。2號(hào)包丟了,接收方會(huì)發(fā)送很多ACK 2 。當(dāng) dup ack 達(dá)到3個(gè)的時(shí)候就會(huì)觸發(fā)快速重傳。

快速重傳是在RFC2581 中定義的 http://www.cnpaf.net/rfc/rfc2581.txt
快速重傳配合快速恢復(fù)可以讓丟包只是適當(dāng)減少
擁塞窗口 cwnd (congestion window)和慢啟動(dòng)閾值 ssthresh (slow start threshold) 而不是重新進(jìn)入一次慢啟動(dòng)過(guò)程。在 RFC2581 中定義,當(dāng)接收到3個(gè)dup ack 的話將進(jìn)入快速重傳。
此時(shí)的
慢啟動(dòng)閾值 ssthresh (slow start threshold) 將設(shè)置為當(dāng)前擁塞窗口 cwnd (congestion window)的一半。而當(dāng)前的擁塞窗口 cwnd (congestion window)為 ssthresh + 3MSS。每收到一個(gè)重復(fù)的ACK就 擁塞窗口 cwnd + 1。當(dāng)有新的ACK收到后,表示都重傳OK了。所以退出快恢復(fù)階段,此時(shí)將 cwnd 重新設(shè)置為 ssthresh 。
這里提一下,丟包對(duì)小文件的影響是比大文件的影響嚴(yán)重的,因?yàn)樽x寫(xiě)一個(gè)小文件需要的包數(shù)很少,
所以丟包時(shí)往往湊不滿3個(gè) Dup ACK。只能等待超時(shí)了,而大文件有較大的可能觸發(fā)快速重傳
傳統(tǒng)TCP存在的問(wèn)題
長(zhǎng)距離高帶寬(Long Fat Network)
- 高帶寬延遲積 (Bandwidth * Latency)
假設(shè) 100Mbps的網(wǎng)絡(luò),時(shí)延為 100ms,那么BDP為 100,000,000 * 0.1 / 8 = 1,250,000B = 1220.7 KB ,TCP 的接收窗口是 16 bit,加上 window scale opetion,最大可以為 65,535B * 2^14。 - 傳統(tǒng)的AIMD的增長(zhǎng)方式導(dǎo)致達(dá)到最大帶寬的時(shí)間過(guò)長(zhǎng)。
在擁塞避免狀態(tài)中,每一個(gè)RTT,擁塞窗口+1,而一個(gè)RTT在以太網(wǎng)中只能傳 1500 B,想要將帶寬為 1Gbps 打滿,需要 8333 個(gè) RTT
被udp擠占帶寬
其實(shí)這個(gè)不應(yīng)該是 tcp 存在的問(wèn)題,因?yàn)楹芏嘁曨l媒體文件傳輸協(xié)議用的udp,udp毫無(wú)擁塞控制可言,瘋狂擠占帶寬,而TCP只能做退讓。
百家爭(zhēng)鳴
一個(gè)擁塞控制算法就是在對(duì) 慢啟動(dòng),擁塞避免,快重傳,快恢復(fù) 這4個(gè)階段進(jìn)行自己的改寫(xiě)。最多的改動(dòng)就是對(duì) 擁塞避免,快重傳,快恢復(fù)的改動(dòng),其實(shí)想一想一個(gè)tcp的擁塞算法也沒(méi)什么大不了的,自己都有設(shè)計(jì)一套的沖動(dòng)。
1、Westwood算法
當(dāng)出現(xiàn) 快恢復(fù) 階段時(shí),就要將 擁塞窗口 cwnd (congestion window)降低一半,這是不是降的有些狠了... 一位叫 Saverio Mascolo 的意大利人和我有一樣的觀點(diǎn),這樣直接砍一半太浪費(fèi)了。
比如兩個(gè)場(chǎng)景,當(dāng)A 傳了 16個(gè)包丟了1個(gè),B傳了16個(gè)包,丟了12個(gè),他們都被快重傳了 Duplicate ACK。而且他們的擁塞窗口都被重置為了8。這就太不科學(xué)了,A 顯然不應(yīng)該降低的這么狠。理想的算法是先推算出有多少個(gè)包已經(jīng)被對(duì)方收到了,從而更為精確的估算發(fā)生擁塞時(shí)的帶寬,Saverio 就設(shè)計(jì)出了Westwood算法。(具體實(shí)現(xiàn)當(dāng)然沒(méi)這么簡(jiǎn)單了)當(dāng)丟包輕微的時(shí)候,westwood算法可以推斷丟包不是特別嚴(yán)重,所以不會(huì)大幅度減小臨界窗口值,這在經(jīng)常發(fā)生非擁塞性丟包的環(huán)境中(如無(wú)線網(wǎng)絡(luò))就能體現(xiàn)出其優(yōu)勢(shì)。
2、Vegas算法
其他算法都是基于丟包驅(qū)動(dòng)的,而vegas是通過(guò)監(jiān)控網(wǎng)絡(luò)狀況來(lái)調(diào)整發(fā)包速率的,從而實(shí)現(xiàn)真正的“擁塞避免”。當(dāng)數(shù)據(jù)包的RTT比較穩(wěn)定,就可以增大擁塞窗口,而數(shù)據(jù)包開(kāi)始排隊(duì)阻塞就會(huì)引起RTT變大,這時(shí)就可以通過(guò)RTT預(yù)測(cè)到,進(jìn)而減小擁塞窗口。
3、Compound
Windows 的解決方案,這個(gè)不講了.....
不過(guò)可以自己設(shè)置玩一下。
windows 10 玩家
netsh int tcp set global dca=enabled
netsh int tcp set global autotuninglevel=normal
netsh int tcp set supplemental template=internet congestionprovider=ctcp
netsh int tcp set global timestamps=enabled
查看命令
netsh int tcp show global
BBR
2016 年 google 提出了 測(cè)量驅(qū)動(dòng)的擁塞控制算法。Google 的BBR提出后,對(duì)整個(gè)網(wǎng)絡(luò)的傳輸效率給出了巨大的提升。
BBR解決了什么問(wèn)題?
現(xiàn)如今的網(wǎng)絡(luò)中,隨著內(nèi)存設(shè)備的價(jià)格下降,路由器則都加上了大內(nèi)存。這樣就使得數(shù)據(jù)包開(kāi)始排隊(duì)。就像下面的管子,第一個(gè)就不說(shuō)了,流量小,第二個(gè),看起來(lái)沒(méi)丟包,但是中間的節(jié)點(diǎn)也被擠的滿滿的,緩存都被填滿了,時(shí)延肯定也隨之增大,最理想的狀態(tài)是帶寬占滿,每個(gè)路由器節(jié)點(diǎn)不要排隊(duì),以最高效的方式進(jìn)行傳輸。
(一般來(lái)說(shuō)丟包就是發(fā)生在排隊(duì)之后的,所以盡量不要出現(xiàn)排隊(duì)現(xiàn)象)

Google的BBR為Youtube在全球范圍內(nèi)將網(wǎng)絡(luò)吞吐平均提高了4%,在某些國(guó)家和地區(qū)甚至提高了14%。
什么是BBR?
BBR("B 長(zhǎng)度 B寬度 R往返時(shí)間"),自1980年以后,互聯(lián)網(wǎng)大量使用基于丟包的擁塞控制算法。這種僅靠丟包作為信號(hào)減慢發(fā)包的速率,因?yàn)榛ヂ?lián)網(wǎng)交換機(jī)和路由器的小緩沖區(qū)可以很好匹配互聯(lián)網(wǎng)的低帶寬,因此很多年以來(lái)都是很好的效果,但是,當(dāng)互聯(lián)網(wǎng)迎來(lái)高速發(fā)展時(shí),更快的發(fā)包速度以及更大的發(fā)送緩沖區(qū),緩沖區(qū)往往會(huì)一直填充并丟棄多余的數(shù)據(jù)包。
改被動(dòng)接收丟包信息為主動(dòng)網(wǎng)絡(luò)探測(cè)
但是如今的互聯(lián)網(wǎng)還在基于丟包的擁塞控制算法是存在問(wèn)題的
- 在淺層緩沖區(qū)中,數(shù)據(jù)包丟失發(fā)生在擁塞之前,基于丟包的擁塞控制算法導(dǎo)致吞吐量過(guò)低。因?yàn)樗麜?huì)反應(yīng)過(guò)度,即使數(shù)據(jù)包丟失是因?yàn)樗查g的流量突發(fā),也會(huì)因?yàn)閿?shù)據(jù)包丟失而減半發(fā)送窗口。
- 在深層緩沖區(qū)中,擁塞發(fā)生在數(shù)據(jù)包丟失之前,在當(dāng)今互聯(lián)網(wǎng)的邊緣,基于丟包的擁塞控制因?yàn)榇缶彌_區(qū),所以不會(huì)發(fā)生丟包,但是RTT會(huì)增大。從而導(dǎo)致臭名昭著的“緩沖區(qū)膨脹問(wèn)題”。
為了確定網(wǎng)絡(luò)發(fā)送數(shù)據(jù)的速率,BBR考慮網(wǎng)絡(luò)傳遞數(shù)據(jù)的速度,對(duì)于給定的網(wǎng)絡(luò)連接,它使用對(duì)網(wǎng)絡(luò)傳輸速率和往返時(shí)間的最新測(cè)量結(jié)果建立一個(gè)顯式的模型,這個(gè)模型包含了鏈接可用的最大帶寬和最小延時(shí)。

網(wǎng)絡(luò)傳輸模型:正交的RTT和BW
- RTT不變,填充帶寬
- RTT增大,帶寬不變
- RTT急劇增大,帶寬急劇減小
BBR的目的就是求得最大帶寬和最小RTT
持續(xù)探測(cè)最大帶寬和最小RTT,當(dāng)最大帶寬不再增加且最小RTT開(kāi)始增加表明開(kāi)始堆積管道。
RTT變高,帶寬不變的情況下有一個(gè)點(diǎn),在這個(gè)點(diǎn)控制發(fā)包速率可以有更好的效果,而不是等到丟包的時(shí)候。
那么就是怎么找到這個(gè)點(diǎn)的問(wèn)題了。
BBR算法是如何找到RTprop和BtlBw的呢
- RTT 排隊(duì)噪聲去除掉就是 RTprop(噪聲是指中間路由器的排隊(duì)時(shí)間)
- BtlBw 按發(fā)送率最大的時(shí)候的帶寬。
BBR會(huì)在路由器緩沖隊(duì)列開(kāi)始排隊(duì)時(shí)降低發(fā)送速率,排空隊(duì)列,而Cubic感知不到已經(jīng)排隊(duì)了,還是在緩慢的增加。
參考推薦 https://blog.apnic.net/2017/05/09/bbr-new-kid-tcp-block/
查看當(dāng)前內(nèi)核支持的擁塞控制算法
$ cat /proc/sys/net/ipv4/tcp_allowed_congestion_control
reno cubic
查看內(nèi)核當(dāng)前運(yùn)行的擁塞控制算法
$ cat /proc/sys/net/ipv4/tcp_congestion_control
cubic
修改內(nèi)核中運(yùn)行的擁塞控制算法
sudo sysctl net.ipv4.tcp_congestion_control=cubic

