TCP RTO 是它 40 多年前唯一丟包檢測策略,也是當前最后的丟包檢測兜底策略,它幾乎從沒變過。
有個咨詢挺有趣,以其案例為背景寫篇隨筆。大致意思是,嫌 TCP RTO 太大,游戲場景丟包卡頓怎么辦?我提供了幾行代碼,果見成效。
不得不承認一個事實,如今絕大多數 TCP RTO 均在 200ms 左右,因為 minrto 規定為 200ms,而大多數 TCP 連接均為就近而穩定的 CDN 連接,排除少量貧困偏遠地區(他們也不在乎網絡質量~),裸測 RTT 中位數不超過 50ms,按照 VJ RTO 公式 srtt + 4·rttvar 計算,遠小于 200ms。
依舊將 minrto 設置為 200ms,此中原因在于對 Delayed ACK 的適應,而 Delayed ACK 在 RFC1122 規定,其最大 timeout 為 500ms,但一般而言,Linux 系統設置為 40ms,Windows 系統為 200ms。因此 minrto = 200ms 足以覆蓋大多數場景而不至于 RTO 過于激進產生的不必要重傳加重網絡擁塞,參見 TCP MIN_RTO 辯證考。
大家都以為 Linux 內核有個參數可以配置 minrto,就像以為有個參數可以配置 TCP timewait 時間一樣,然而直到 6.x 內核,minrto 的 sysctl 配置才被支持,但早在 2014 年前后,各大 CDN 廠商就紛紛私下里支持了該配置,每當業務咨詢連接超時問題時,該配置可謂神器,可想而知,如今 Linux 6.x 對其正式支持,也是這些廠商所為,提個派池而已。
我沒有以 “配置一個更小的 minrto” 回應該咨詢,因為這種配置往往會因為眾口難調的原因而弄巧成拙:
- 使用 sysctl 配置全局 minrto 無法適應所有尺度的 TCP 連接;
- 使用 iproute2 配置單路由 minrto 同上,只是范圍縮小些;
- 使用 sockopt,ebpf 配置單連接 minrto 無法適應連接內的尺度變化;
我提供的是根據連接當前性質實時計算 RTO 的細粒度方案,專門針對 thin TCP stream,也就是擁塞無害的細長流,它有一份古老的文檔:Thin-streams and TCP,早在 2.6 時代就部署。修改 tcp_rearm_rto 和 tcp_retransmit_timer:
- 兩個函數為 thin TCP stream 提供與 TLP 一致的超時時間以替換 RTO;
- 重新定義 thin TCP stream;
可以簡單理解上述修改,即放寬了 RFC8985-RACK-TLP “1.2. Motivation” 的約束,加上 “when the entire flight is lost and the stream is thin” 也能享受 TLP。
傳統意義上,packet_out < 4 的連接被認為 thin TCP stream,因為 dupack/sacked 無法超過 3 而觸發 fast retransmit,3 同樣是傳統意義上的閾值。我將其修改:
- inflight < tp->reordering + 1 即為 thin TCP stream;
配合現有 TCP 重傳策略,這就是一整套丟包檢測和重傳算法:
- RFC6675 提供傳統快速重傳方案;
- RACK/TLP 提供 tail drop 重傳,兜底 RFC6675;
- RFC5682 提供傳統 RTO 兜底 RACK;
- 我的算法為 thin TCP stream 優化 RTO;
當然,RTO 本身的計算方式無需改變,若不是有 Delayed ACK 搗亂,它還是要比 TLP PTO = 2·SRTT(若 inflight == 1,還要和 max_ack_delay 膠著) 更加優秀,參見 VJ 對 SRTT/RTO 公式參數的推導。
后面是關于丟包檢測和重傳的形而上話。
TLP 的細節我今年過年期間專門說過,它覆蓋了 AAAL,AALL,ALLL,LLLL,>=5 L 等所有 packet_out > 1 的丟包場景,與 RACK 天然搭配。
我一向覺得 RACK 具有里程碑意義,因為它的引入,TCP 丟包檢測變得更簡單直觀。RACK 取消了 FACK,ER(Early Retrans),thin Fast Retrans,NCR 等一眾票凌亂離散的丟包檢測和重傳策略,全部統一到了自己的時間序中。代碼簡化了不少,統一于 net/ipv4/tcp_recovery.c。
可能正因為 RACK 有如此威力,RTO 變得極少被觸發,反而被遺忘了,沒人單獨優化它。
但一旦涉及 RTO,就必須重新審視關于 TLP 和 RACK 的整個背景和適用前提。
首先,Fast Retrans 和 TLP 均假設有足夠的 seg 觸發重傳,RACK 取消掉的 thin Fast Retrans 亦依賴 ACK-selfclock,如果丟失 ACK-selfclock,還是要掉入 RTO。tail drop 可用 TLP 優化,但 pingpong 模式的應用遭遇 drop 則不能,在 RACK: a time-based fast loss detection algorithm for TCP 有個場景:
Structured request-response traffic turns more losses into tail drops. In such cases, TCP is application-limited, so it cannot send new data to probe losses and has to rely on retransmission timeouts (RTOs).
典型的很 thin 的 stream,很容易全部丟失,如此一來 ACK-selfclock 丟失,RACK,TLP 全都派不上用場。
其次,自定義 TCP 相關參數時代以來,網絡環境已經發生重大變化,但這些參數的缺省值卻很少發生改變,而這些缺省值往往影響 TCP 的性能。其中以 RTO 為嚴重,因為 RTO 由本地 clock 觸發,它不像 ACK-selfclock 可在閉環上玩很多花活,行為和參數相互依賴,只需改變算法,不太依賴參數。
是時候重新測量新的統計值了,但如今龐大的互聯網已經變得難以測量,雖然很難給出一個更合適的 minrto,但可以確定的是,哪些場景下可以不受 minrto 約束,這是我這個優化的初衷。
回到原點,minrto 保守化的原因在于避免不必要的重傳而加重擁塞,但它與丟包不能及時恢復相比,要兩權相害取其輕,對于 block 傳輸,自然要優先考慮避免擁塞問題,但對于 thin stream,比如游戲,遠程登錄等非 capacity-seeking 傳輸,其 inflight 不隨帶寬而增加,不足以對現代網絡的負載產生可觀測影響,因此它們并不是擁塞控制的目標。這是我這個優化的背景。
基于 “對 thin stream 要激進探測丟包并重傳” 的觀點,擁塞無害的 thin stream 不必矜持保守地等待超時,我給出該優化。在此之前,我曾經以三三兩兩倒序發送,尾部跟隨亂序包等花活兒被經理表揚。
此外,有一個關于 TLP && inflight == 1 的討論,標準的做法是 “PTO = 2 srtt + delayed_ack 容忍期”,但本著 “thin stream 擁塞無害” 的觀點,代之以 “在唯一的報文后緊緊尾隨一個曾經發送過亂序字節(或整個報文)以取消 receiver 的 delayed ack” 則更高尚,以無所謂的空間代價換寶貴的時間收益。
最后,說說 RFC4653-Non-Congestion Robustness (NCR) for TCP,從兩個視角看看對它的態度:
- 從 RFC 的視角,NCR 傾向于保守重傳,以有機會區分亂序和丟包,充滿了對 Robustness 的憧憬及展望;
- 從 Linux Kernel 社區的視角,NCR 提供當 reordering 太高不能及時誘發 Fast Retrans 的接力,傾向激進;
換句話說,RFC 嫌現有 Fast Retans 太激進,容易錯把亂序當丟包,以至于不必要降速,因此提出了 NCR 盡量不進入 Fast Retrans,而 Linux Kernel 社區則嫌現有 reordering-based Fast Retrans 太墨跡不能及時重傳,以至 RTO,因此實現了 NCR 盡量進入 Fast Retrans。它們說的同一個東西,但目標不同。
顯然,以 Linux Kernel 社區的視角,一旦 RACK 被引入,很多旨在優化 “不能及時重傳” 的策略都沒了繼續存在的必要,NCR 自然下課了:tcp: remove RFC4653 NCR。
再看 RFC 的視角,下面的引述道出了我一直想表達的意思:
The requirement imposed by TCP for almost in-order packet delivery places a constraint on the design of future technology. Novel routing algorithms, network components, link-layer retransmission mechanisms, and applications could all be looked at with a fresh perspective if TCP were to be more robust to segment reordering.
如果 TCP 再健壯一點,能識別亂序,網絡設備便無需對它做按序假設了,沒有保序約束,并行處理便大行其道,這可以極大提高交換吞吐。
為了盡力實現這種健壯性,或者至少提供一個實驗性的探討,NCR 在確切進入 Fast Retransmit 之前給算法一定的時間來從重復應答中甄別出亂序。算法很簡單,cwnd 個 seg 離開網絡后仍然沒有得到應答,就開始進入 Fast Retrans,而不再是 “收到 3 個重復應答”(早非如此了)。
如 RFC 正文,通過使 TCP 對非擁塞事件更具 Robustness,TCP-NCR 可能為未來互聯網組件的設計開辟空間,其中就包括多路徑傳輸。
NCR 提供兩種策略,它們可以歸為一類,即在進入 Fast Retrans 之前,以多大的兌換比進行 seg 守恒兌換,假設該比值是 p,積累 S 個 SACKed 段進入 Fast Retrans,簡單推導一下實現:
S≥α?(W+p?W)\text{S}\geq \alpha\cdot(\text{W}+p\cdot\text{W})S≥α?(W+p?W)
解得 α=1p+1\alpha=\dfrac{1}{p+1}α=p+11?。因此,如 RFC4653 所述的建議,對于 Careful Limited Transmit,兌換比為 1:2,則 α = 2/3,而對于 Aggressive Limited Transmit,兌換比為 1:1,則 α = 1/2。除此之外,你可以給定任意兌換比,獲得任意程度的 “關于 dupack 原因的最佳決策和響應性之間進行權衡”,當然,我覺得這個跟 PRR 有些許重合,只不過 PRR 是決定降速之后的策略,而 NCR 是降速之前的堅持窗口。
浙江溫州皮鞋濕,下雨進水不會胖。