从 VJ 拥塞控制到 BBR:ACK 自时钟和 pacing
如果不是 1980~1990 年代主机技术发展超过网络技术发展速度,端到端传输协议拥塞控制可能永远不会出现。
自 BBR 被用来解决数据传输的 bufferbloat 问题,pacing 控制逐渐进入人们视野,在此之前,可能人们并不太在意 burst 方式发送数据有什么危害,甚至不知道它有什么好处,一切显得自然而然。
事实上早在 1990 年代初,pacing 就被提出用来解决拥塞控制的时延抖动问题,而此时正是 VJ 将 AIMD 拥塞控制引入端到端传输后的 5 年之内,属于一个理论刚被论证和实践之后的百花齐放时。
早期的 pacing 之于 burst 的优势可谓仅存在于理论臆想,现实中全是劣势。首先,早期应用对 pacing 没有硬性需求,其次,早期主机并不具备实现高精度测量的设施(比如高精度时钟 timer,高频中断处理),最后,pacing 被怼违背了刚刚提出的数据包守恒,彼时只能说数据包守恒可以解决拥塞崩溃问题,但没有任何证据证明这就是唯一的解决方案,因此这个理由多少有些牵强,但由于 VJ 的巨大贡献,数据包守恒已经成了事实上的标准。
我们追溯一下 VJ 拥塞控制,正如他自己所说 “我无法弄明白它失效的原因是我不理解它究竟如何开始运转的(不失效时正常运转的机理),它迫使我们弄明白了驱动 TCP 运转的 ACK 时钟,从那以后,其它事就容易了。”,如此,TCP 的 ACK 自时钟是拥塞控制的核心。
引用自 VJ88 论文中的一幅经典图(在《TCP/IP 详解(卷 1)》中可见)诠释 VJ 管道:
它看起来非常普通和自然,核心就只讲述了 “数据包守恒”:基于 Little 定律定义的,表现为带宽时延乘积的网络管道的承载极限内,当一个数据包离开网络时放入一个数据包是安全的。而如何获得管道的承载极限则需要某种探测机制。
将端到端路径抽象成一个表现为 BDP 管道是高尚的,否则,可想而知事情将会变得多么复杂。端到端吞吐行为的测量值由路径上每一跳链路的 buffer 动力学共同决定,而每一跳都是统计复用的,端主机能控制的只有释放多少数据包,表现为维持多大的 inflight。
因此,能做的只有释放一定的 inflight,然后让链路上所有 buffer 来决定吞吐行为,若要避免拥塞崩溃,只要监控一个指标,即 “不丢包意味着所有这些链路共同组成的 ‘容量’ 能容纳这些 inflight”,剩下的就是选择一个调整 inflight 的算法,执行容量探测并保证能趋向公平。
假设已知网络 BDP 管道的极限容量为 C,安全的做法就是数据包守恒,离开一个进入一个,而 TCP ACK 正是提供一个数据包已经离开网络的可信任信号,这意味着可以再放一个数据包进入网络,这便是 VJ 管道和 TCP 自时钟的推导过程,拥塞控制全靠它。这个理论决定了后面 30 年端到端拥塞控制理论的基本走向,不管是 TCP 的还是非 TCP 的。
但这个管道容量守恒理论有个缺点,它强烈依赖管道本身的特性,而管道的定义并未区分带宽和 buffer,如果 buffer 过大,这部分 buffer 将影响端到端的另一个性能指标,时延。为解决此问题,需要严格甄别带宽和 buffer 的构成组分并确保不占用过多的 buffer,但这对于早期的端主机而言似乎是不可能做到的(现在也不容易)。除此之外,早期应用程序对时延也是不那么在乎的。
事情到了 2010 年代变得不得不解决,移动物联网和 3/4/5G 催生了实时视频类应用,时延变得比吞吐更重要,于是 AIMD 的 bufferbloat 问题变得日益突出,pacing 再次被讨论,最终 BBR 被放出,成了自早期 Loss-based AIMD,Delay-based Vegas 之后拥塞控制新一个里程碑,而后 BBR 被迅速推广。
谢天谢地,一切又是机缘巧合,2010 年代的网络已经足够快,以至于可以应对短时间段内对数据包守恒的违背(虽然还是无法数理证明)。另一方面,BBR 本就对 buffer 占用收敛,现代交换设备均配置不小的 buffer,足以吸收短期数据包不守恒造成的容量过载。
所谓基于数据包守恒的拥塞控制,在实现上表现为一个 RTT 周期内维持一定的 inflight,该行为滞后一个 RTT 时间,偏差由丢包 MD 收敛来纠正,AIMD 趋向到公平。而 pacing 在实现上则表现为测量一个 RTT 周期内的数据吞吐,由此来指导发送 pacing,这实际上就是一种区间测速,最小的区间就是一个 RTT。
这意味着区间测速只能获取一个 RTT 内的平均吞吐,于严格 ACK 自时钟数据包守恒 “没有 ACK 可以等” 不同,pacing 在每时每刻必须有一个 pacing rate,当区间测量吞吐大于当前 ACK 自时钟吞吐时,管道便过载,反之,当测量吞吐小于当前 ACK 自时钟时,管道便欠载了。在一个固定的时间区间求 pacing,只是一个平均值,便丢失了该段内的守恒。
固定时刻,pacing 的不同意味着对链路 buffer 的抢占力不同,特别是竞争流抢占周期小于 pacing 周期(比如 1 个 RTT)时尤为严重,高速公路维持安全车距与此类似,如果你保持车距,总会有车加塞插入,你若坚持保持车距就不得不继续退让,最终维持不住原有时速,事实上很少有人会遵守安全车距的规则,在网络传输中也一样,保持 pacing 可能会丢失吞吐,进而丢失 pacing,BBR 的做法是维持一个 maxbw-win,大约 10-round,在此期间不理会由于 pacing 丢失的吞吐。
ACK 自时钟更能自适应,因为 ACK 到达的模式是一个自动的行为调制过程,它体现了整个所有链路 buffer 动力学(包括调度,限速,整形)过程本身。
我此前有过一些建议,pacing 解决 bufferbloat 的方案是 “发送慢一些”,特别是如今端主机网络带宽如此大,且 buffer 也如此时,burst 很容易造成 bufferbloat,但这并不是要基于 delivery-rate 来设置 pacing,相反,还不如固定按照 100Mbps 来 pacing,或者至多 10000Mbps,这里的缘由很简单,带宽可能会无限增加,这对核心转发好处巨大,但这种能力的提升显然不是为主机服务的。Sally Floyd 也预测过,将来的应用数据传输越来越多趋向于 Application limited,时代变了,这意味着传输协议要改掉 capacity-seeking 的毛病。
pacing 并非一定与 ACK 自时钟相对,它们完全可以共同完成一个目标,避免拥塞崩溃,避免 bufferbloat 的前提下,提高传输效率。So?发送行为可以基于下面的式子完成 min(ACK 自时钟驱动, maxbw-win(delivery_rate))。
具体实现方面。数据包守恒过程的探测其实很容易执行,AIMD 的 AI 过程便是,只要不丢包就可以一直试探上限,但 pacing 来做这个事就麻烦,因为更大的 pacing rate 并不一定能带来更大的 delivery rate,如果不能怎么办,是维持还是退避。BBR 的做法依然是释放 1.25X 的 BDP 来试探更大的带宽,但由于 delivery rate 信号远没有丢包信号那么稳定,与 AIMD 每 1 个 round 执行 cwnd ++ 不同,BBR 的 probebw CYCLE_LEN 足足有 8 个 round 之久。
皮鞋湿,不会胖。
最后再看一段历史。如果不是 1980~1990 年代主机技术发展超过网络技术发展速度,端到端传输协议拥塞控制可能永远不会出现。
1980 年代 CPU,内存的发展侧重于提升性能和处理能力,网络则在标准化和普及方面取得重要进展,整体发展速度网络慢于主机技术。1986 年拥塞崩溃部分源自于此,如果主机很慢很慢,发不出多少数据或者接收端处理不过来而内存又十分有限时,rwnd 流控将截止网络流量在一个很低的水平,拥塞便不会发生。
进入 21 世纪后,网络技术获得迅猛发展(特别是光纤,网卡模块),而进入 2010 年代后,主机则遭遇摩尔定律瓶颈,性能优化曲线开始向下弯折,直到最近,主机计算资源依然比带宽更加昂贵。换句话说,相比 1980 年代,如今的网络已经非常不容易拥塞了,部分原因来自于主机能力有限,特别是终端小型化,服务器 CDN 化,带宽持续发展后,拥塞更不容易发生。而国内的拥塞问题更多是人为掣肘而非技术原因。
这给了我们什么启示呢。特别是当接到一个设计广域网拥塞控制算法的工作时。再有一个开放式问题,与广域网的复杂情况相对,对比在完全不同的时间尺度下操作的数据中心场景,pacing 是必须的吗?
浙江温州皮鞋湿,下雨进水不会胖。