淺談TCP擁塞控制算法
最近花了些時(shí)間在學(xué)習(xí)TCP/IP協(xié)議上,首要原因是由于本人長(zhǎng)期以來(lái)對(duì)TCP/IP的認(rèn)識(shí)就只限于三次握手四次分手上,所以希望深入了解一下。再者,TCP/IP和Linux系統(tǒng)層級(jí)的很多設(shè)計(jì)都可以用于中間件系統(tǒng)架構(gòu)上,比如說(shuō)TCP 擁塞控制算法也可以用在以響應(yīng)時(shí)間來(lái)限流的中間件上。更深一層,像TCP/IP協(xié)議這種基礎(chǔ)知識(shí)和原理性的技術(shù),都是經(jīng)過(guò)長(zhǎng)時(shí)間的考驗(yàn)的,都是前人智慧的結(jié)晶,可以給大家很多啟示和幫助。
本文中會(huì)出現(xiàn)一些縮寫(xiě),因?yàn)槠鶈?wèn)題,無(wú)法每個(gè)都進(jìn)行解釋?zhuān)绻悴幻靼姿暮x,請(qǐng)自己去搜索了解,做一個(gè)主動(dòng)尋求知識(shí)的人。
TCP協(xié)議有兩個(gè)比較重要的控制算法,一個(gè)是流量控制,另一個(gè)就是阻塞控制。
TCP協(xié)議通過(guò)滑動(dòng)窗口來(lái)進(jìn)行流量控制,它是控制發(fā)送方的發(fā)送速度從而使接受者來(lái)得及接收并處理。而擁塞控制作用于整體網(wǎng)絡(luò),它是防止過(guò)多的包被發(fā)送到網(wǎng)絡(luò)中,避免出現(xiàn)網(wǎng)絡(luò)負(fù)載過(guò)大,網(wǎng)絡(luò)擁塞的情況。
擁塞算法需要掌握其狀態(tài)機(jī)和四種算法。擁塞控制狀態(tài)機(jī)的狀態(tài)有五種,分別是Open,Disorder,CWR,Recovery和Loss狀態(tài)。四個(gè)算法為慢啟動(dòng),擁塞避免,擁塞發(fā)生時(shí)算法和快速恢復(fù)。
Congestion Control State Machine
和TCP一樣,擁塞控制算法也有其狀態(tài)機(jī)。當(dāng)發(fā)送方收到一個(gè)ACK時(shí),Linux TCP通過(guò)狀態(tài)機(jī)的狀態(tài)來(lái)決定其接下來(lái)的行為,是應(yīng)該降低擁塞窗口cwnd大小,或者保持cwnd不變,還是繼續(xù)增加cwnd。如果處理不當(dāng),可能會(huì)導(dǎo)致丟包或者超時(shí)。
1 Open狀態(tài)
Open狀態(tài)是擁塞控制狀態(tài)機(jī)的默認(rèn)狀態(tài)。這種狀態(tài)下,當(dāng)ACK到達(dá)時(shí),發(fā)送方根據(jù)擁塞窗口cwnd(Congestion Window)是小于還是大于慢啟動(dòng)閾值ssthresh(slow start threshold),來(lái)按照慢啟動(dòng)或者擁塞避免算法來(lái)調(diào)整擁塞窗口。
2 Disorder狀態(tài)
當(dāng)發(fā)送方檢測(cè)到DACK(重復(fù)確認(rèn))或者SACK(選擇性確認(rèn))時(shí),狀態(tài)機(jī)將轉(zhuǎn)變?yōu)镈isorder狀態(tài)。在此狀態(tài)下,發(fā)送方遵循飛行(in-flight)包守恒原則,即一個(gè)新包只有在一個(gè)老包離開(kāi)網(wǎng)絡(luò)后才發(fā)送,也就是發(fā)送方收到老包的ACK后,才會(huì)再發(fā)送一個(gè)新包。
3 CWR狀態(tài)
發(fā)送方接收到一個(gè)顯示擁塞通知時(shí),并不會(huì)立刻減少擁塞窗口cwnd,而是每收到兩個(gè)ACK就減少一個(gè)段,直到窗口的大小減半為止。當(dāng)cwnd正在減小并且網(wǎng)絡(luò)中有沒(méi)有重傳包時(shí),這個(gè)狀態(tài)就叫CWR(Congestion Window Reduced,擁塞窗口減少)狀態(tài)。CWR狀態(tài)可以轉(zhuǎn)變成Recovery或者Loss狀態(tài)。
4 Recovery狀態(tài)
當(dāng)發(fā)送方接收到足夠(推薦為三個(gè))的DACK(重復(fù)確認(rèn))后,進(jìn)入該狀態(tài)。在該狀態(tài)下,擁塞窗口cnwd每收到兩個(gè)ACK就減少一個(gè)段(segment),直到cwnd等于慢啟動(dòng)閾值ssthresh,也就是剛進(jìn)入Recover狀態(tài)時(shí)cwnd的一半大小。 發(fā)送方保持 Recovery 狀態(tài)直到所有進(jìn)入 Recovery狀態(tài)時(shí)正在發(fā)送的數(shù)據(jù)段都成功地被確認(rèn),然后發(fā)送方恢復(fù)成Open狀態(tài),重傳超時(shí)有可能中斷 Recovery 狀態(tài),進(jìn)入Loss狀態(tài)。
5 Loss狀態(tài)
當(dāng)一個(gè)RTO(重傳超時(shí)時(shí)間)到期后,發(fā)送方進(jìn)入Loss狀態(tài)。所有正在發(fā)送的數(shù)據(jù)標(biāo)記為丟失,擁塞窗口cwnd設(shè)置為一個(gè)段(segment),發(fā)送方再次以慢啟動(dòng)算法增大擁塞窗口cwnd。
Loss 和 Recovery 狀態(tài)的區(qū)別是:Loss狀態(tài)下,擁塞窗口在發(fā)送方設(shè)置為一個(gè)段后增大,而 Recovery 狀態(tài)下,擁塞窗口只能被減小。Loss 狀態(tài)不能被其他的狀態(tài)中斷,因此,發(fā)送方只有在所有 Loss 開(kāi)始時(shí)正在傳輸?shù)臄?shù)據(jù)都得到成功確認(rèn)后,才能退到 Open 狀態(tài)。
四大算法
擁塞控制主要是四個(gè)算法:1)慢啟動(dòng),2)擁塞避免,3)擁塞發(fā)生,4)快速恢復(fù)。這四個(gè)算法不是一天都搞出來(lái)的,這個(gè)四算法的發(fā)展經(jīng)歷了很多時(shí)間,到今天都還在優(yōu)化中。
慢熱啟動(dòng)算法 – Slow Start
所謂慢啟動(dòng),也就是TCP連接剛建立,一點(diǎn)一點(diǎn)地提速,試探一下網(wǎng)絡(luò)的承受能力,以免直接擾亂了網(wǎng)絡(luò)通道的秩序。
慢啟動(dòng)算法:
1) 連接建好的開(kāi)始先初始化擁塞窗口cwnd大小為1,表明可以傳一個(gè)MSS大小的數(shù)據(jù)。
2) 每當(dāng)收到一個(gè)ACK,cwnd大小加一,呈線(xiàn)性上升。
3) 每當(dāng)過(guò)了一個(gè)往返延遲時(shí)間RTT(Round-Trip Time),cwnd大小直接翻倍,乘以2,呈指數(shù)讓升。
4) 還有一個(gè)ssthresh(slow start threshold),是一個(gè)上限,當(dāng)cwnd >= ssthresh時(shí),就會(huì)進(jìn)入“擁塞避免算法”(后面會(huì)說(shuō)這個(gè)算法)
擁塞避免算法 – Congestion Avoidance
如同前邊說(shuō)的,當(dāng)擁塞窗口大小cwnd大于等于慢啟動(dòng)閾值ssthresh后,就進(jìn)入擁塞避免算法。算法如下:
1) 收到一個(gè)ACK,則cwnd = cwnd + 1 / cwnd 2) 每當(dāng)過(guò)了一個(gè)往返延遲時(shí)間RTT,cwnd大小加一。
過(guò)了慢啟動(dòng)閾值后,擁塞避免算法可以避免窗口增長(zhǎng)過(guò)快導(dǎo)致窗口擁塞,而是緩慢的增加調(diào)整到網(wǎng)絡(luò)的優(yōu)化值。
擁塞狀態(tài)時(shí)的算法
一般來(lái)說(shuō),TCP擁塞控制默認(rèn)認(rèn)為網(wǎng)絡(luò)丟包是由于網(wǎng)絡(luò)擁塞導(dǎo)致的,所以一般的TCP擁塞控制算法以丟包為網(wǎng)絡(luò)進(jìn)入擁塞狀態(tài)的信號(hào)。對(duì)于丟包有兩種判定方式,一種是超時(shí)重傳RTO[Retransmission Timeout]超時(shí),另一個(gè)是收到三個(gè)重復(fù)確認(rèn)ACK。
超時(shí)重傳是TCP協(xié)議保證數(shù)據(jù)可靠性的一個(gè)重要機(jī)制,其原理是在發(fā)送一個(gè)數(shù)據(jù)以后就開(kāi)啟一個(gè)計(jì)時(shí)器,在一定時(shí)間內(nèi)如果沒(méi)有得到發(fā)送數(shù)據(jù)報(bào)的ACK報(bào)文,那么就重新發(fā)送數(shù)據(jù),直到發(fā)送成功為止。
但是如果發(fā)送端接收到3個(gè)以上的重復(fù)ACK,TCP就意識(shí)到數(shù)據(jù)發(fā)生丟失,需要重傳。這個(gè)機(jī)制不需要等到重傳定時(shí)器超時(shí),所以叫做快速重傳,而快速重傳后沒(méi)有使用慢啟動(dòng)算法,而是擁塞避免算法,所以這又叫做快速恢復(fù)算法。
超時(shí)重傳RTO[Retransmission Timeout]超時(shí),TCP會(huì)重傳數(shù)據(jù)包。TCP認(rèn)為這種情況比較糟糕,反應(yīng)也比較強(qiáng)烈:
- 由于發(fā)生丟包,將慢啟動(dòng)閾值ssthresh設(shè)置為當(dāng)前cwnd的一半,即ssthresh = cwnd / 2.
- cwnd重置為1
- 進(jìn)入慢啟動(dòng)過(guò)程
最為早期的TCP Tahoe算法就使用上述處理辦法,但是由于一丟包就一切重來(lái),導(dǎo)致cwnd重置為1,十分不利于網(wǎng)絡(luò)數(shù)據(jù)的穩(wěn)定傳遞。
所以,TCP Reno算法進(jìn)行了優(yōu)化。當(dāng)收到三個(gè)重復(fù)確認(rèn)ACK時(shí),TCP開(kāi)啟快速重傳Fast Retransmit算法,而不用等到RTO超時(shí)再進(jìn)行重傳:
- cwnd大小縮小為當(dāng)前的一半
- ssthresh設(shè)置為縮小后的cwnd大小
- 然后進(jìn)入快速恢復(fù)算法Fast Recovery。
快速恢復(fù)算法 – Fast Recovery
TCP Tahoe是早期的算法,所以沒(méi)有快速恢復(fù)算法,而Reno算法有。在進(jìn)入快速恢復(fù)之前,cwnd和ssthresh已經(jīng)被更改為原有cwnd的一半。快速恢復(fù)算法的邏輯如下:
- cwnd = cwnd + 3 * MSS,加3 * MSS的原因是因?yàn)槭盏?個(gè)重復(fù)的ACK。
- 重傳DACKs指定的數(shù)據(jù)包。
- 如果再收到DACKs,那么cwnd大小增加一。
- 如果收到新的ACK,表明重傳的包成功了,那么退出快速恢復(fù)算法。將cwnd設(shè)置為ssthresh,然后進(jìn)入擁塞避免算法。
如圖所示,第五個(gè)包發(fā)生了丟失,所以導(dǎo)致接收方接收到三次重復(fù)ACK,也就是ACK5。所以將ssthresh設(shè)置為當(dāng)時(shí)cwnd的一半,也就是6/2 = 3,cwnd設(shè)置為3 + 3 = 6。然后重傳第五個(gè)包。當(dāng)收到新的ACK時(shí),也就是ACK11,則退出快速恢復(fù)階段,將cwnd重新設(shè)置為當(dāng)前的ssthresh,也就是3,然后進(jìn)入擁塞避免算法階段。
后記
本文為大家大致描述了TCP擁塞控制的一些機(jī)制,但是這些擁塞控制還是有很多缺陷和待優(yōu)化的地方,業(yè)界也在不斷推出新的擁塞控制算法,比如說(shuō)谷歌的BBR。這些我們后續(xù)也會(huì)繼續(xù)探討,請(qǐng)大家繼續(xù)關(guān)注。