需要澄清的一个误区是,拥塞绝不是发送的数据量太大导致,而是数据在极短的时间段内到达了同一个地方以至于超过了网络处理容量导致,拥塞的成因一定要考虑时间因素。换句话说,拥塞由大突发导致。
只要 pacing,再多的数据量也不会拥塞,1GB,100GB,100TB 的数据遵循同一 pacing 发送只是需要不同时间平滑送完,然而只要 100MB 的数据同时到达就可能导致拥塞,即使再大的瓶颈带宽,再大的 buffer,更大的突发也会导致拥塞必然发生,拥塞绝不是资源不足导致。
如今带宽已足够大,配合丢包,整型,限速,足以应对任何程度的拥塞,但任意一个上述措施都将压力给到了端主机。按 1970 年代网络和主机环境的假设,传输层需要模拟一个无损网络细水长流地送达,端主机必须处理丢包以及波动,这种假设下,tcp 顺势而生的彼时需重点考虑稀缺的带宽和内存,长肥管道怎么办也一直是 tcp 担心遇见却很长一段时间都不存在的环境,可如今的广域网几乎都很长肥,多亏了 cdn。
本节我以不同的视角给我的孩子们描述一个不同的网络传输假设。
现在的带宽足够大且便宜,内存也足够大且便宜,1970 年代的假设已经不复存在,改变主要表现在三方面。
首先,数据的获取方式变成了实时传输,而不再从硬盘上获得。曾经的先下载后使用的方式彻底被改变,曾经的下载过程不需要关注时延抖动,如今的实时传输则要同时关注速率和抖动。其次,足够大且便宜的带宽允许主机在更长的时间段发送更大的数据,数据编码不再一字千金。最后,足够大且便宜的内存允许主机在展示数据之前缓存足够久的时间,这段时间则为纠错等预处理提供了操作空间。
因此,始自 1970 年代的传输方式需要改变,不能通过重传的方式加剧时延抖动,不必过于在意传输数据量大小,通过更冗余的编码在 receiver 纠错。
在继续这个话题之前,我并没有给孩子们过多灌输 tcp/quic/rdma 这类协议是如何做的,因为如果他们对此太了解了反而不好,如果以近乎完美的 tcp 携带 bbr 为标杆,我需要不断解释下面这段话:
冗余编码肯定会更长,这个必须要接受,tcp 以重传构建无损的优化方式是引入 fec 而减少重传,但所有声称具有灵活切换重传(术语叫 arq)和 fec 的协议都是在侥幸赌博,设计者的心理向着压缩数据量靠拢,企图以最少的数据量传输信息,在不得不加入冗余的时刻再加入,而需要加入冗余的时刻恰恰就是拥塞的时刻,但拥塞期间提高冗余只能更拥塞,这也是他们所看到的结果,他们会告诉你,fec 在拥塞期间效果并不好,最终还是要靠重传。他们接受不了将冗余平滑到整个传输期间,因为他们接受不了或者说理解不了在不拥塞的时候为什么还要冗余,他们希望得到免费的午餐,不想增加一点点他们知道哪怕只是可能会输掉的筹码。
…
我让孩子们试着去理解英文和中文的差别,英文听错一个音节无碍于听懂整个词,中文听错一个音节就损失整个词,这是为什么。因为英文单词是多音节编码,多音节自带了冗余,而汉字是单音节编码,没有冗余。英文以多音节单词为书写单位,单词之间有空格,以连读关联两个甚至多个单词来提供语音的黏连冗余,而汉字以单音节单字为书写单位,每个汉字之间有空格并不区分词,连读黏连也就很自然向整体拼读方向压缩,比如 “之于” 本来还是两个音节,被压缩成了 “诸”,“什么” 变成了 “啥”,还有一些正在发生的,比如 “这样” 和 “酱”,“不好” 和 “表”(其实本来就有连音字 “嫑”),还有 “只因” 和 “鸡”。
映射到如今的计算机编码和传输,很容易发现始自英语国家的计算机反而使用中文的编码和传输方式。单独编码字母而不是单词,每个字母是一个单独的 ascii/unicode,字母与字母之间平行无关联,显然就是一种 “单音节,无黏连” 的方式。
用自然语言的编码和传输方式作为模板去设计计算机(特别是 ai 领域)网络传输方式,虽不一定最优,至少合理。
作为 80 后互联网工人,我必须试着从 “压缩数据量结合重传” 的保守策略转换到 “增加冗余量允许丢包” 的激进策略。前者说的是 “压缩数据量也有利于避免拥塞,但不幸拥塞发生了,重传兜底”,后者则意味着 “无需太在意数据量大小,丢包就丢包吧”。
仍以现有的 unicode 码为基础,假想一种简单的拉链式黏连编码,后一个符号编码有一部分是前一个符号编码的纠错码,或者更简单一点,以连续两个字符为单位编码成 “x|y|x + y”,如果 y 丢了,用 x + y - x 来恢复,这些都是很自然的方案。
但考虑到计算机网络传输的特殊性,依然需要更加复杂的处理。
考虑两个环境特征,首先,数据以数据包(packet)为单位传输,每个数据包有 mtu 字节的大小,超过 1KB,连续编码的原始数据和纠错码几乎是一丢全部丢;其次,拥塞非均匀发生,拥塞本身具有突发性和连续丢包概率。在实际传输前需要做两层随机,将上述两件不幸的事平滑开来。第一,将连续编码随机到不同数据包,第二,将拥塞丢包的纠错码随机到非拥塞时期。
下面是一个孩子们能理解的简单方案:
这里仍然存在两个问题,第一个问题,x,y,x+y 所在的数据包丢了两个,就无力恢复了,但这个问题可以缓解,比如引入拉链,“a|b|a+b|c|b+c|d|c+d|…”,但依然无法彻底解决,所以转变观念很重要,也许大不了还是可以反馈一个 nack 的。第二个问题是传输数据量会成倍增加。第二个问题解决后,第一个问题就能进一步缓解,因为可以容纳更多冗余了。
如果全世界只有英文,用计算机网络传输英文和用嘴说英文没有什么不同,人们用 26 个字母排列组合成所有的文字资料,但记忆单位却是多个字母组成的多音节单词甚至短语,习惯用语,人们编码的是单词。计算机也可以直接编码单词,比如用 1 个码来表示 “skinshoe”,而不是 8 个码,这就极大的压缩了编码空间。
如果引入其它字符,比如中文,编码词语依然比编码单个汉字更有效,比如 “皮鞋” 可以只用 1 个码,而现有方案却需要 2 个码,但由于中文本来就已经极度压缩了,比如 “之于” 压缩成了 “诸”,这种词语编码显然没有英文的单词编码性价比更高。
chatgpt 在神经网络的意义空间里就是类似的编码方式,字,单词,词组,短语,句子,段落,文章等信息均被编码到一个用意义关联的多维空间,“h” 和 “e” 不再是孤立的字符,“he” 和 “him”,“his” 距离很近,同理,“桌子” 和 “椅子” 距离很近,因为它们都是家具。这是从意义中恢复的依据,所谓的意义就是个概率空间。“我去你妈的” 和 “我去你奶奶的” 不同,但意思一样。但有点跑题,今天的话题是冗余传输。
一条直线由 2 个点确定,每个单词都可以编码到一条直线,只需要给 2 个点 4 个坐标即可,如果加入冗余,可以传输该直线上的 3 个点,4 个点,甚至更多点,只要 receiver 收到任意两个点,就能恢复这个单词,4 个坐标编码一个单词看起来并不便宜,如果 3 个,5 个点提供冗余就更昂贵了,但如果编码更长的单词呢,就会变得更划算,借鉴哈夫曼编码思想,直线可以编码 “常用但长” 的单词,短语。
m 次曲线需要 m + 1 个点唯一确定,只要被编码数据量和 m + 1 + k 个点的坐标数据量在同一量级,其比值越大越划算。
只是人们并不习惯这种方法,如果仔细观察网络上传输的任何文字类数据,几乎没有不是单词,词组构成的无意义字符串,如果非要传输类似 “dqgfbkjfwkbswnfegeg” 的字符串,自然回退到 ascii/unicode 的代价就是冗余本身的代价。
至于图片,音乐的编码,它们本质上都是对模拟信息的数字化,而模拟信号都自带冗余,蓝天背景缺失的一块依然是蓝天,极小概率是一只恰好飞过的鸟。
讲给小孩子开个脑洞完全 OK,也能指引他们重新思考未来的传输协议,但回到现实中,工程化需要成熟和稳定的兼容性支撑,idea 随时都有,但落地的事只能踩着大规模部署技术的脚印前行。
刚刚跟博士聊到,那么多学界的 idea 其实都只是 idea 和论证,落地的东西还是传统 tcp/ip 相关的那套,实验室的假设在工程实践上很难被满足,好多新玩意儿最初都是军事目的,从军界需求扩散到学界在到民用的转化周期并不短,比如 tcp/ip 本身就经历过这样的过程。
所以,工人好好做工,经理好好开会,士不可以不弘毅,君子不能不经理。
浙江温州皮鞋湿,下雨进水不会胖。