原文参见 不可靠的假 tcp。

最近在研究可纠错的编码,新想法就比上述引文成熟了很多。新的魔改更加合理:

  • 当 receiver 收到报文时,根据一个变量 k 来确定回复哪个序列号作为 ack。

应用层可以简单定时设置 k,让 k 匀速往后滑动,就能实现一个固定时延的传输,在 buffer 允许的范围,tcp 重传依然有效,只是过了 buffer 允许的时间,receiver 补洞上送,不再需要重传,强行设置 k,推进 sender 的 una。

可调节的 buffer 为丢包纠错提供了操作时间。

代码的修改并不麻烦,重写少部分 tcp_data_queue 即可。

之所以要这么做而不是设计一个新的协议,原因是设计一个新协议几乎总是会引入更多的问题,一破一立的思路对于网络传输协议并不总合适,因此我也非常讨厌近些年那些层出不穷的新协议总是由揭露现有协议不适合某些场景开始,以一个针对特殊场景的全新协议结束,一篇又一篇地水论文。

我倾向于迭代现有协议。首先为现有协议增加可扩展性支持,然后在此基础上扩展现有协议。

对上述修改而言,如果真有确实不能丢的数据,我将引入一条单独的 nack 通道,或 http over tcp/quic,或 rpc 不重要,用 receiver 主动索要丢失的数据的方式代替 sender 重传才是重点,哪些数据必须要,只有 receiver 最清楚(比如说即使采用了纠错机制依然没有恢复成功)。

该修改的另一优势是为完全的乱序传输增加了灵活性。我一向倾向于 “用素描的方式画油画”,而不是 “一笔画”,根据 receiver 的 qoe 容忍度来决定这幅画的颜料涂到多厚,而不是稍有断笔就擦去重改,任何 sack-based 重传机制都内含了 gbn 的影子。

以传输一幅画为例,画面像素要均匀打在屏幕上,一次一点点,越来越清晰,根据资源情况和 qoe 容忍度随时停止,这才是合理方式,就像素描和油画一样,现实世界我们也如此,而只有乱序传输才能支持。

说了这么多好处,当我测试时才发现,天啊,拥塞控制完全失效了。

我使用了系统自带的 bbrv1,结果 bbr 相关的计算完全失真。原因不难查,由于 receiver 主动推进了 una,大量丢包便没被计入,从而严重影响了 sender 的 delivery rate 计算。

解决这个问题的思路天然地倾斜到 “在那个单独的通道中回传 sack 以及丢包统计信息”,这些信息通过单独通道给到 sender 用于拥塞控制。你看,这个单独的通道是不是自然而然变成了 “控制通道”,拥塞控制就是这样从 tcp 带内自然剥离出来的。

为什么包括我自己在内的几乎所有人都没有剥离控制通道的想法,因为 tcp 从最开始就是带内控制协议,自然就成了缺省标准,这就是思维定势,然而谁告诉我们传输协议就一定要带内控制。

单独的控制通道还没有改完,不过既然控制信息都拉到应用层了,格式都是我自己定,也就没必要迎合哪个协议了,简单。

再次强调,控制信息拉到应用层很重要,只有 receiver 应用层自己知道自己收到没收到哪些数据,单位时间收到多少数据,以及还需要哪些数据。控制通道解耦后更自由,测不准问题也得到了解决。

所以,最终 tcp 几乎还是原来的 tcp,如果不设置 k,就是兼容模式,而设置了 k,单独的控制通道也不影响原始 tcp。

我这个就是 tcp 本身,不是新协议,一个 k 值可用可不用,这对于平滑落地非常重要。

前些天给孩子们讲完课写了一篇 开始干重活儿的互联网,有朋友跟我一个想法,认为 “Xpu 确实是各种很难想到的特定使用场景”,非常类似我最近提到的作为自我检讨用的 “自动切豆腐篦子”,我也就再评论几句 “就跟肥皂剧泛滥一样,你水一篇论文,虚构一个场景,我也必须水一篇论文,虚构一个场景,然后我拿着你的论文说你这个场景不适合我的场景,把你的论文稍微加点东西变成我的,你也觉得我的论文不适合你的场景,稍微改改变成你的,各种虚构+虚构场景,各种魔改版本”。

曾经我不懂,但现在懂了,工程上弹性和余量非常重要,千万不要开足马力奔赴一个特定的目的。举两个例子,一个是服务器负载均衡,一个是 4 发动机大型客机。

集群中的服务器在 cpu 利用率超过 50% 时就要警惕集群中一半服务器的宕机,如果 cpu 利用率超过 90%,哪怕几台服务器宕机,压力也会很快传播到整个集群,连锁反应将干崩整个集群。早期的我经常认为 active-active 远胜 active-standby,我甚至希望把 active 开到接近 100%,因为我只想着性能。

同样,大型客机的 4 个发动机也并不是用于性能,而是用于在某发动机失效后,接管发动机可迫降,而用于跨洋超远距离运输的客机,必须有足够的余量支撑这个原则。

我们现在的传输协议设计几乎忽略了这些弹性和余量的原则,几乎都是开足马力奔赴特定目的,大家普遍都希望自己的协议可以 n for one,但事实上只是由 n 变成了 n + 1。但只要松弛一下这种紧张的局面就能缓解,我用带外信号做控制,而带外意味着无限扩展的弹性和余量。

后记:

  • 我本想用 ebpf 做自定义 ack,太麻烦了,放弃,从此不碰 ebpf。
  • 切豆腐篦子真不需要,不是刚需。新传输协议哪个又是刚需?能改造一下,何必重做一个?

浙江温州皮鞋湿,下雨进水不会胖。