前面说过 tcp 协议由碎点构成,因此拧巴,这些碎点拧巴之处的控诉,今天继续安排。
tcp 最初对可靠性的设计非常简单:如果一个报文在一段合理的时间内没有得到应答,就重传它,即原始 rto 机制。
“合理的时间” 是多久?这个问题最终由范雅各布森给出了标准,即 tcp rto 的计算,原则也简单,既要过滤掉噪声,又要考虑到波动,因此 rto 的计算由平滑 rtt 和 rtt 方差共同决定。
tcp 是前向兼容的,即使日后引入 fr,sack,tlp,er 等碎点 ,tcp rto 仍作为兜底策略保持不变,在 tcp 引入这一系列碎点同时,rto 本身也在不断迭代(但我认为并无必要),以提高 tcp 传输性能,虽说这些过程及其令人眼花缭乱,但这正是 tcp 生命力强大的体现:tcp 以 afap(as fast as possible) 为原则被设计,此外再无约束。
归根结底,rack 之前的一切碎点都在 tcp 序列号空间比划,时间序直到 2010 年代中期才被考虑,背景可参见 大历史下的 pacing 之一 和 大历史下的 pacing 之二。
rfc 将这个供算法比划的序列号空间称作 scoreboard,fr,sack,tlp,er,rto,f-rto 全依赖这 scoreboard。
tcp 在时间序中发送和应答,而序列号空间却没有时间信息,这是 tcp 传输歧义的根源,tcp 无法从应答序列反推传输序列,这导致 scoreboard 上的操作及其复杂并富含把戏,误判在所难免,接下来的问题是处理误判。你看,scoreboard 上拙劣的比划引发误判问题,而处理误判又需要新的比划,tcp 终成这类比划的好把式。
捡拾一个碎点来讲,比如 f-rto。
路由重收敛,无线环境都可能导致链路 rtt 突然正常变大,在 tcp 尚未感知到 rtt 变大时,就是超时重传,显然这个重传并无必要,但最大的误伤是 rto 后 tcp 被认为发生了拥塞,它会从非常低的起点重新开始探测网络容量。有三种方案可检测到不必要的超时重传,以避免跌入低效的起点:
- dsack:重传 1 次,收到 2 次应答;
- eifel 算法:应答时间戳比重传更早;
- f-rto:传个新报文,观察应答序列。
都是小成本把戏。 其中 f-rto 能效最高。
发送一个新报文,如果回来的 sack 覆盖了 rto 时的 high 却没有更新 una,它一定为新报文触发并且实锤丢包,如果更新了 una,则可能是确认来迟了,继续发送新数据继续观察。
是不是和 tlp 有点像,tlp 也是优先发送一个新数据,观察应答携带的 ack/sack,如果应答只是迟到,发送新数据什么也不耽误,如果确实丢包,新数据的应答则带回足够的 sack 触发 fr。
你看,f-rto 和 tlp 采用相似方式在 scoreboard 上比划,重点在用新数据触发 scoreboard 更新并试图从这更新上获得新知,可谓水清能濯缨,水浑可洗脚,啥也不耽误。
当 rack 将时间序引入 scoreboard,一维变二维,参见 tcp rack 如何干掉 fack,很多碎点都可重新评估:
- tlp 并入 rack,参见 The RACK-TLP Loss Detection Algorithm for TCP;
- rto 并入 tlp;
大历史决定了发送必须在时间序 pacing,而 rate-based cc 配合 pacing 需要设计拥塞控制算法的新视角,它事实上更简单而不是更复杂了:
- 测量实际 delivery rate 并估算 pacing rate
不同 rate-based cc 的区别在于如何 “估算 pacing rate”,但不管哪种 cc,都要 “测量”,因此传输不能停下来,而停不下来的传输又会导致拥塞,新 cc 和经典 aimd cc 的拥塞避免加性增 cwnd 完全一个意思,都是 capacity-searching,有注定的代价。
拥塞成因由 “太多” 变成了 “太快”,而快慢仅由 pacing rate 控制,序列号空间小心翼翼比划的保守算法将不再必要。曾经的 fr 只有一次机会,rack 后的 fr 将不再受限,换句话说,时间序不认识序列号。
由于 rate-based 收敛性在数学上不完备,tcp 仍需要非时间序算法兜底,除非设计一个全新的时间序传输协议。
再看 rto 的其它方面。
我的观点,已有多种 probe 方案触发 rack-based fr 后,原始的 rto 仅做兜底即可,大可不必继续折腾。应该尽量避免跌入 rto。
昨天同事给发了一个链接 Making TCP More Robust to Long Connectivity Disruptions (TCP-LCD),说的是如果链路断开又恢复,rto backoff 期间的 tcp 连接可能已经退避多次,无法及时探测到链路恢复而耽误了时间。于是折腾出一套基于带外 icmp 信号的 rto 修正算法。
rto backoff 现在对拥塞控制的意义已大大降低,反而影响了链路状态更新的即时性,特别在无线移动环境,无线链路有自己的重试机制,链路冲突导致的传输失败并没有想象的多,相当多传输失败是信号原因导致,比如终端位置改变,进入死角,信号被屏蔽等,这些随机因素并非 backoff 能解决。
有证据支撑这想法:tcp: make the first N SYN RTO backoffs linear。(这种线性重试早就被各公司 cdn 团队纳入魔改手段了,可它直到最近才合入 linux kernel)
linux tcp 建连都要线性 probe 了,rto probe 和建连在链路看来又有什么区别呢?
即使原始的 rto 实现,在获得有效 rtt 样本后,backoff 都要重置,而在链路失败又恢复情形下,显然是因果倒置了。
昨天跟同事聊这个话题,我说 tcp 传输僵死时,直接传一个新报文并用 una 短间隔线性 probe 反而最有效,就是简单的 f-rto/tlp 的 probe 思路,别的花活儿反而没用。骨干网带宽从来不是问题,瓶颈带宽几乎都在 lastmile 甚至公司 wifi。
如果只在边缘枝端拥塞,枝端数量多就足够散列,越散开的枝端拥塞越异步,大量重传报文被足够久的时间平摊,1s 重传 10000000 个报文如果影响到了骨干,但 1000s 重传这么多就无所谓,只要不影响运营商网络拥塞状态,问题就出在计费。比如在一条车流量足够大的高速公路,只要没有实际发生拥堵,就不应该收拥堵税。
…
今天的故事就讲到这里。
浙江温州皮鞋湿,下雨进水不会胖。