> 文章列表 > 从一道面试题看 TCP 的吞吐极限

从一道面试题看 TCP 的吞吐极限

从一道面试题看 TCP 的吞吐极限

分享一个 TCP 面试题:单条 TCP 流如何打满香港到旧金山的 320Gbps 专线?(补充,写成 400Gbps 更具迷惑性,但预测大多数人都会跑偏,320Gbps 也就白给了)

这个题目是上周帮一个朋友想的,建议他别问三次握手,慢启动,快速重传,何时发 RST 了,来个情景分析吧。

如果候选人提到 TCP 序列号空间 4GB,香港到旧金山往返 200ms+,320Gbps 管道容量 8GB+,TCP 最大窗口受限,无法这个管道,至于后面说与不说,都算通过了。上来就 BBR 的,还得继续问三次握手。

下面是这个题目的解析。

先求一下 TCP 最大窗口。
在 32bit 的 unsigned int 圆环域上,两个数字顺时针间隔在一个半圆内才能无歧义比较大小(参考 Linux kernel 的 before,after 宏定义):
从一道面试题看 TCP 的吞吐极限
如上图的无歧义共识,TCP 窗口最大值被限制在 2^31 字节以内。

此外,根据 TCP 滑动窗口原理,sender 和 receiver 的窗口之间最大可相差一整窗:
从一道面试题看 TCP 的吞吐极限

因此,TCP 窗口的最终最大值为 2^31/2 = 2^30 字节。这是 TCP 窗口上限,即 1GB。200ms 链路,TCP 最多只能打满 1GB*8/0.2s = 160Gbps 的带宽。

如果允许稍微 patch TCP,如何填满 8GB 管道呢?这是个更有趣的问题。

Netflix 有个直接的方案,扩展序列号空间:64-bit Sequence Numbers for TCP

我这里重点说另一个 PAWS + Pacing 方案。

timestamp 是个天然升序标识,每 1ms 滴答一下,32bit timestamp 回绕一次要 49 天,值得使用。

320Gbps = 40GBps,大约 1ms 发出 40MB 数据,即每 100ms 发生序列号回绕。但只要控制 sender 的 pacing rate,每 1ms 送出 40MB 数据,就可将 rwnd 最大值扩至 2^31,不再受回绕限制:
从一道面试题看 TCP 的吞吐极限
道理很简单,1ms 粒度 pacing,相当于为 seq 增加了额外的顺序号,按 timestamp 编号每 40MB 数据,receiver 对 100 个 1ms 和 1ms 的 40MB 数据归并排序即可恢复 send 顺序:
从一道面试题看 TCP 的吞吐极限

注意,timestamp 指的是原始报文传输时的时间,即便发生重传,其timstamp 还是原始那个。

若发生丢包,由于 “since the sender and receiver windows can be out of phase by at most the window size【RFC1323-2.3】”,sender 和 receiver 之间最大 2^31 相差,在 2^32B 范围内,每个 seq 只出现一次,以 timestamp 做序号,自然不会有歧义。

大致算一下 2^31B 的窗口用 timestamp 做顺序号可以支撑多大的带宽。以 timestamp 的 1ms 精度,1ms 至少需要将 2^32 的两个半圆区分开,1ms 标识 2^31B 数据,即 16Tbps = 2TBps 的带宽。
从一道面试题看 TCP 的吞吐极限

进一步设想,若 timestamp 精度达到 us,所支撑的带宽将继续扩大,但系统开销也随之扩大,由于二者非线性关系,所以我不敢说 1000 倍。

以上是理论值计算,考虑到丢包,乱序,拥塞控制等因素,理论值几乎达不到,但它展示了一种可能性。

万变不离其中,我经常强调单调递增编码 packet,并设计新协议,但实际上 timestamp 就是一个天然编码标识,它确实编码了 “packet(每一个 segment 包含若干 byte)”,而非 Byte stream。

byte 大小固定没弹性,byte-based 滑动窗口没扩展性,带宽越大,传输字节越多,越快回绕。而 packet 不固定,它提供 1B ~ mss B 的弹性,且 mtu 可随底层传输技术发展而增加(如巨帧),显然 packet-based 滑动窗口可扩展性更佳。可见,2^32B = 4GB 的序列号空间直接参与传输和确认不适应长肥网络,不过在 byte/packet-based 滑动窗口争论正当时(参见:The design philosophy of the darpa internet protocols 第 10 节),管道既不长,更不肥,选择 byte-based 没毛病。

回到这个面试题,为什么不能上来就说 BBR,因为 cwnd limit 前,先碰到 rwnd limit,这才是硬伤,是壁垒。所以必须先解决 rwnd limit 问题,突破 rwnd 限制,至于 BBR,只是优化。

这个题目主要考察 3 个点,首先是对 TCP 协议头字段的理解,主要是 seq 和 wcale option,其次是对数字的敏感,比如东亚到加州湾区的时延,再次就是根本问题和次要问题的甄别能力,刚接触 BBR 的可能觉得 BBR 能解决一切问题,从而把根本问题疏忽了。

至于更多讨论,参见另一篇文章:流控问题两则。

现在来看可靠保序传输的本质。

要在 receiver 恢复 sender 端顺序,需顺序标识数据。将数据顺序装入 packet,然后顺序标识 packet 即可,但这个我已说过太多,现在看如何顺序标识数据本身。

TCP 序列号算一种顺序标识,但它会面临回绕歧义。TCP 将最大窗口限制在 2^30B 消除了歧义,但同时也限制了其填充长肥网络的能力。在引入 timestamp 后,可重新审视这个问题。

无论 seq space 多大,它终究被定义在环形域上(计算机算术一切数据类型都在环形域),环形域无歧义比较大小需在一个半圆内,当这个环形域不够大时,就会遇到 TCP 一样窗口限制,但若将这个环形域定义足够大,却可能占用额外报头空间,最好就是根据实际所需将环形域定义成变长(比如 QUIC)。但还有别的解法。

当我们发现时间序是一个天然顺序后,这个环形域便可分级解释,就像 MMU 分级页表一样。将顺序标识分为两层,外层是时间序,内层是环形域的半圆,设序列号空间 m 位,timestamp 精度为 n,只需要在 1 个 n 时间内能最大识别 2^(m-1)B 即可,也就是说,同一 timestamp 编码一个半圆。

m = 32,n = 1ms 就是 TCP 的情况,解释是,只要在 1ms 内发送数据不超过 2^31B 即可。2^31B 就是 2GB,对于 TCP,它可以支撑的带宽为 2TBps,与 RTT 无关。对于 receiver,执行两层归并排序,首先将时间分割为精度为 n 的 slot,根据 timestamp 将报文对应到 slot,再根据 seq 将报文在该 slot 内排序:
从一道面试题看 TCP 的吞吐极限
为什么 RTT 消失了?

RTT 并没消失,它回绕需要太久的时间,以至于可以将它近似为绝对单调递增量,前面算过,以 1ms 精度滴答的 timestamp 发生回绕需要 49 天,远超一个报文在互联网上最长逗留时间,当然,如果处在比地球大的多的星球,这一点需要修正。

这里有一个 packetization 过程,一个 slot 即一个 packetization 单位,内容是一个没有歧义的半圆内的 seq。

原始报文的 timestamp 时间序和 seq 字节序共同消除了保序歧义,另一面,为保证可靠传输,丢包重传还需要一个报文( whatever 原始报文 or 重传报文)实际发送的时间序列号,用来消除原始报文和重传报文的歧义,这个虽然不是流控必须的,但对拥塞控制意义重大,不得不察,但这方面我强调过太多,不再啰嗦。

现在可以和 Netflix 的方案(见上面链接)对应了,二者殊途同归,Netflix 方案直接采用了 64bit 序列号,而 PASW + Pacing 也使用 64bit 序列号,只是将它分为了 32bit 原始序列号和 32bit timestamp 两层。在我看来,PAWS + Pacing 更灵活,可以适配不同时钟精度和不同带宽。

所以,我有个建议,TCP 在同时启用 timestamp 和 wscale 时,wscale 需另作解释,将其最大值限制在 15 而不是 14,而 timestamp 也另解释为顺序号,为 2 倍的滑动窗口(即以 wscale 最大值 15 取代 RFC7323 规定的 14)消除回绕歧义。

从刚毕业参加工作面试一直到此后的 10 年多,被面试官问了无数次 TCP 三次握手,为什么三次,TimeWait,快速重传之类的问题,自己作为面试官也问了候选人无数次这般问题,我上一次被问这些是大约 5 年前,但也差不多从那时起我也不问候选人这些问题了,我想了一些更好玩的,比如 “如何用 TCP 实现不可靠传输(考察 ACK/SACK/滑动窗口)”,“如何旁路修改传输中的 TCP 数据(考察 RFC5961)?” … 正好有朋友也厌倦了上面那些老掉牙的问题,要我帮忙新出一个面试题,问题已经被问过,所以延迟一周写下本文。

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