4000字詳解TCP超時(shí)與重傳,看完沒收獲算我輸
上一篇介紹 TCP 的文章「TCP 三次握手,四次揮手和一些細(xì)節(jié)」反饋還不錯(cuò),還是蠻開心的,這次接著講一講關(guān)于超時(shí)和重傳那一部分。
我們都知道 TCP 協(xié)議具有重傳機(jī)制,也就是說,如果發(fā)送方認(rèn)為發(fā)生了丟包現(xiàn)象,就重發(fā)這些數(shù)據(jù)包。很顯然,我們需要一個(gè)方法來「猜測」是否發(fā)生了丟包。最簡單的想法就是,接收方每收到一個(gè)包,就向發(fā)送方返回一個(gè) ACK,表示自己已經(jīng)收到了這段數(shù)據(jù),反過來,如果發(fā)送方一段時(shí)間內(nèi)沒有收到 ACK,就知道很可能是數(shù)據(jù)包丟失了,緊接著就重發(fā)該數(shù)據(jù)包,直到收到 ACK 為止。
你可能注意到我用的是「猜測」,因?yàn)榧词故浅瑫r(shí)了,這個(gè)數(shù)據(jù)包也可能并沒有丟,它只是繞了一條遠(yuǎn)路,來的很晚而已。畢竟 TCP 協(xié)議是位于傳輸層的協(xié)議,不可能明確知道數(shù)據(jù)鏈路層和物理層發(fā)生了什么。但這并不妨礙我們的超時(shí)重傳機(jī)制,因?yàn)榻邮辗綍詣?dòng)忽略重復(fù)的包。
超時(shí)和重傳的概念其實(shí)就是這么簡單,但內(nèi)部的細(xì)節(jié)卻是很多,我們最先想到的一個(gè)問題就是,到底多長時(shí)間才能算超時(shí)呢?
一、超時(shí)是怎么確定的?
一刀切的辦法就是,我直接把超時(shí)時(shí)間設(shè)成一個(gè)固定值,比如說 200ms,但這樣肯定是有問題的,我們的電腦和很多服務(wù)器都有交互,這些服務(wù)器位于天南海北,國內(nèi)國外,延遲差異巨大,打個(gè)比方:
- 我的個(gè)人博客搭在國內(nèi),延遲大概 30ms,也就是說正常情況下的數(shù)據(jù)包,60ms 左右就已經(jīng)能收到 ACK 了,但是按照我們的方法,200ms 才能確定丟包(正??赡苁?90 到 120 ms),這效率實(shí)在是有點(diǎn)低。
- 假設(shè)你訪問某國外網(wǎng)站,延遲有 130 ms,這就麻煩了,正常的數(shù)據(jù)包都可能被認(rèn)為是超時(shí),導(dǎo)致大量數(shù)據(jù)包被重發(fā),可以想象,重發(fā)的數(shù)據(jù)包也很容易被誤判為超時(shí)。。。雪崩效應(yīng)的感覺
所以設(shè)置固定值是很不可靠的,我們要根據(jù)網(wǎng)絡(luò)延遲,動(dòng)態(tài)調(diào)整超時(shí)時(shí)間,延遲越大,超時(shí)時(shí)間越長。
在這里先引入兩個(gè)概念:
- RTT(Round Trip Time):往返時(shí)延,也就是**數(shù)據(jù)包從發(fā)出去到收到對應(yīng) ACK 的時(shí)間。**RTT 是針對連接的,每一個(gè)連接都有各自獨(dú)立的 RTT。
- RTO(Retransmission Time Out):重傳超時(shí),也就是前面說的超時(shí)時(shí)間。
比較標(biāo)準(zhǔn)的 RTT 定義:
Measure the elapsed time between sending a data octet with a particular sequence number and receiving an acknowledgment that covers that sequence number (segments sent do not have to match segments received). This measured elapsed time is the Round Trip Time (RTT). |
1. 經(jīng)典方法
最初的規(guī)范「RFC0793」采用了下面的公式來得到平滑的 RTT 估計(jì)值(稱作 SRTT):
- SRTT <- α·SRTT +(1 - α)·RTT
RTT 是指最新的樣本值,這種估算方法叫做「指數(shù)加權(quán)移動(dòng)平均」,名字聽起來比較高大上,但整個(gè)公式比較好理解,就是利用現(xiàn)存的 SRTT 值和最新測量到的 RTT 值取一個(gè)加權(quán)平均。
有了 SRTT,就該設(shè)置對應(yīng)的 RTO 的值了,「RFC0793」是這么算的:
- RTO = min(ubound, max(lbound, (SRTT)·β))
這里面的 ubound 是 RTO 的上邊界,lbound 為 RTO 的下邊界,β 稱為時(shí)延離散因子,推薦值為 1.3 ~ 2.0。這個(gè)計(jì)算公式就是將 (SRTT)·β 的值作為 RTO,只不過另外限制了 RTO 的上下限。
這個(gè)計(jì)算方法,初看是沒有什么問題(至少我是這么感覺的),但是實(shí)際應(yīng)用起來,有兩個(gè)缺陷:
There were two known problems with the RTO calculations specified in RFC-793. First, the accurate measurement of RTTs is difficult when there are retransmissions. Second, the algorithm to compute the smoothed round-trip time is inadequate [TCP:7], because it incorrectly assumed that the variance in RTT values would be small and constant. These problems were solved by Karn's and Jacobson's algorithm, respectively. |
這段話摘自「RFC1122」,我來解釋一下:
當(dāng)出現(xiàn)數(shù)據(jù)包重傳的情況下,RTT 的計(jì)算就會很“麻煩”,我畫了張圖來說明這些情況:
圖上列了兩種情況,這兩種情況下計(jì)算 RTT 的方法是不一樣的(這就是所謂的重傳二義性):
但是對于客戶端來說,它不知道發(fā)生了哪種情況,選錯(cuò)情況的結(jié)果就是 RTT 偏大/偏小,影響到 RTO 的計(jì)算。(最簡單粗暴的解決方法就是忽略有重傳的數(shù)據(jù)包,只計(jì)算那些沒重傳過的,但這樣會導(dǎo)致其他問題。。詳見 Karn's algorithm)
- 情況一:RTT = t2 - t0
- 情況二:RTT = t2 - t1
另一個(gè)問題是,這個(gè)算法假設(shè) RTT 波動(dòng)比較小,因?yàn)檫@個(gè)加權(quán)平均的算法又叫低通濾波器,對突然的網(wǎng)絡(luò)波動(dòng)不敏感。如果網(wǎng)絡(luò)時(shí)延突然增大導(dǎo)致實(shí)際 RTT 值遠(yuǎn)大于估計(jì)值,會導(dǎo)致不必要的重傳,增大網(wǎng)絡(luò)負(fù)擔(dān)。( RTT 增大已經(jīng)表明網(wǎng)絡(luò)出現(xiàn)了過載,這些不必要的重傳會進(jìn)一步加重網(wǎng)絡(luò)負(fù)擔(dān))。
2. 標(biāo)準(zhǔn)方法
說實(shí)話這個(gè)標(biāo)準(zhǔn)方法比較,,,麻煩,我就直接貼公式了:
- SRTT <- (1 - α)·SRTT + α·RTT //跟基本方法一樣,求 SRTT 的加權(quán)平均
- rttvar <- (1 - h)·rttvar + h·(|RTT - SRTT |) //計(jì)算 SRTT 與真實(shí)值的差距(稱之為絕對誤差|Err|),同樣用到加權(quán)平均
- RTO = SRTT + 4·rttvar //估算出來的新的 RTO,rttvar 的系數(shù) 4 是調(diào)參調(diào)出來的
這個(gè)算法的整體思想就是結(jié)合平均值(就是基本方法)和平均偏差來進(jìn)行估算,一波玄學(xué)調(diào)參得到不錯(cuò)的效果。如果想更深入了解這個(gè)算法,參考「RFC6298」。
二、重傳——TCP的重要事件
1. 基于計(jì)時(shí)器的重傳
這種機(jī)制下,每個(gè)數(shù)據(jù)包都有相應(yīng)的計(jì)時(shí)器,一旦超過 RTO 而沒有收到 ACK,就重發(fā)該數(shù)據(jù)包。沒收到 ACK 的數(shù)據(jù)包都會存在重傳緩沖區(qū)里,等到 ACK 后,就從緩沖區(qū)里刪除。
首先明確一點(diǎn),對 TCP 來說,超時(shí)重傳是相當(dāng)重要的事件(RTO 往往大于兩倍的 RTT,超時(shí)往往意味著擁塞),一旦發(fā)生這種情況,TCP 不僅會重傳對應(yīng)數(shù)據(jù)段,還會降低當(dāng)前的數(shù)據(jù)發(fā)送速率,因?yàn)門CP 會認(rèn)為當(dāng)前網(wǎng)絡(luò)發(fā)生了擁塞。
簡單的超時(shí)重傳機(jī)制往往比較低效,如下面這種情況:
假設(shè)數(shù)據(jù)包5丟失,數(shù)據(jù)包 6,7,8,9 都已經(jīng)到達(dá)接收方,這個(gè)時(shí)候客戶端就只能等服務(wù)器發(fā)送 ACK,注意對于包 6,7,8,9,服務(wù)器都不能發(fā)送 ACK,這是滑動(dòng)窗口機(jī)制決定的,因此對于客戶端來說,他完全不知道丟了幾個(gè)包,可能就悲觀的認(rèn)為,5 后面的數(shù)據(jù)包也都丟了,就重傳這 5 個(gè)數(shù)據(jù)包,這就比較浪費(fèi)了。
2. 快速重傳
快速重傳機(jī)制「RFC5681」基于接收端的反饋信息來引發(fā)重傳,而非重傳計(jì)時(shí)器超時(shí)。
剛剛提到過,基于計(jì)時(shí)器的重傳往往要等待很長時(shí)間,而快速重傳使用了很巧妙的方法來解決這個(gè)問題:服務(wù)器如果收到亂序的包,也給客戶端回復(fù) ACK,只不過是重復(fù)的 ACK。就拿剛剛的例子來說,收到亂序的包 6,7,8,9 時(shí),服務(wù)器全都發(fā) ACK = 5。這樣,客戶端就知道 5 發(fā)生了空缺。一般來說,如果客戶端連續(xù)三次收到重復(fù)的 ACK,就會重傳對應(yīng)包,而不需要等到計(jì)時(shí)器超時(shí)。
但快速重傳仍然沒有解決第二個(gè)問題:到底該重傳多少個(gè)包?
3. 帶選擇確認(rèn)的重傳
改進(jìn)的方法就是 SACK(Selective Acknowledgment),簡單來講就是在快速重傳的基礎(chǔ)上,返回最近收到的報(bào)文段的序列號范圍,這樣客戶端就知道,哪些數(shù)據(jù)包已經(jīng)到達(dá)服務(wù)器了。
來幾個(gè)簡單的示例:
case 1:第一個(gè)包丟失,剩下的 7 個(gè)包都被收到了。
當(dāng)收到 7 個(gè)包的任何一個(gè)的時(shí)候,接收方會返回一個(gè)帶 SACK 選項(xiàng)的 ACK,告知發(fā)送方自己收到了哪些亂序包。注:Left Edge,Right Edge 就是這些亂序包的左右邊界。
- Triggering ACK Left Edge Right Edge
- Segment
- 5000 (lost)
- 5500 5000 5500 6000
- 6000 5000 5500 6500
- 6500 5000 5500 7000
- 7000 5000 5500 7500
- 7500 5000 5500 8000
- 8000 5000 5500 8500
- 8500 5000 5500 9000
case 2:第 2, 4, 6, 8 個(gè)數(shù)據(jù)包丟失。
- 收到第一個(gè)包時(shí),沒有亂序的情況,正?;貜?fù) ACK。
- 收到第 3, 5, 7 個(gè)包時(shí),由于出現(xiàn)了亂序包,回復(fù)帶 SACK 的 ACK。
因?yàn)檫@種情況下有很多碎片段,所以相應(yīng)的 Block 段也有很多組,當(dāng)然,因?yàn)檫x項(xiàng)字段大小限制, Block 也有上限。
- Triggering ACK First Block 2nd Block 3rd Block
- Segment Left Right Left Right Left Right
- Edge Edge Edge Edge Edge Edge
- 5000 5500
- 5500 (lost)
- 6000 5500 6000 6500
- 6500 (lost)
- 7000 5500 7000 7500 6000 6500
- 7500 (lost)
- 8000 5500 8000 8500 7000 7500 6000 6500
- 8500 (lost)
不過 SACK 的規(guī)范「RFC2018」有點(diǎn)坑爹,接收方可能會在提供一個(gè) SACK 告訴發(fā)送方這些信息后,又「食言」,也就是說,接收方可能把這些(亂序的)數(shù)據(jù)包刪除掉,然后再通知發(fā)送方。以下摘自「RFC2018」:
Note that the data receiver is permitted to discard data in its queue that has not been acknowledged to the data sender, even if the data has already been reported in a SACK option. Such discarding of SACKed packets is discouraged, but may be used if the receiver runs out of buffer space. |
最后一句是說,當(dāng)接收方緩沖區(qū)快被耗盡時(shí),可以采取這種措施,當(dāng)然并不建議這種行為。。。
由于這個(gè)操作,發(fā)送方在收到 SACK 以后,也不能直接清空重傳緩沖區(qū)里的數(shù)據(jù),一直到接收方發(fā)送普通的,ACK 號大于其最大序列號的值的時(shí)候才能清除。另外,重傳計(jì)時(shí)器也收到影響,重傳計(jì)時(shí)器應(yīng)該忽略 SACK 的影響,畢竟接收方把數(shù)據(jù)刪了跟丟包沒啥區(qū)別。
4. DSACK 擴(kuò)展
DSACK,即重復(fù) SACK,這個(gè)機(jī)制是在 SACK 的基礎(chǔ)上,額外攜帶信息,告知發(fā)送方有哪些數(shù)據(jù)包自己重復(fù)接收了。DSACK 的目的是幫助發(fā)送方判斷,是否發(fā)生了包失序、ACK 丟失、包重復(fù)或偽重傳。讓 TCP 可以更好的做網(wǎng)絡(luò)流控。
關(guān)于 DSACK,「RFC2883」里舉了很多例子,有興趣的讀者可以去閱讀一下,我這里就不講那么細(xì)了。