BBR 声称 anti-bufferbloat 且内置状态机有主动 drain,所以题目中使用了 “侵占”,也可以用 “吞占”。
BBR ProbeBW ProbeUP 阶段以 1.25x 的增益加速,退出条件:inflight >= 1.25 * maxbw * minrtt。
BBR ProbeBW Drain 阶段以 0.75x 的增益降速。
单流场景下,当 bw full,即使 buffer 排队 maxbw 亦不再增加,接下来的 Drain 阶段可将 queue 完全退回。但与多条异步 Loss-based 流共存场景,当 bw full,即使 buffer 排队,ProbeUP 总能挤出一点带宽(带宽占比越大,加速比越小),maxbw 增量 < 25%,而异步流 base queue delay 几乎不变,BBR 测得 BDP 会随 ProbeUP 一点点增加,幸好有个 ProbeRTT 可以主动回退 queuing,但要在 10s 之后。
用固定 10s 控制 buffer 侵占强度以及公平性不太合理,但也只能靠这个。要么就调参?10s -> 5s?无论怎样,BBR 的侵占行为是固有的。
…
在现实中我们并没有观测到上述怪象,多亏 BBR 还有个 cwnd 作为 secondary controller。如果 cwnd 真如其论文所述,是一个 secondary controller,只需要在 inflight >= cwnd_gain * BDP 时切回 Reno 执行 AIMD 即可,但有趣的是, 与 Loss-based 流共存时,最终 inflight 总是 >= cwnd_gain * BDP,这算是劣币驱良币还是被大融合同化,最终的世界还是属于 AIMD。
BBRv2 差不多就是这意思,融入 AIMD 对 Loss 的反应,但还保留着一张脸。BBRv2 本文不谈,先从 cwnd the secondary controller 开始。
BBR 作为 rate-based cc,如果每一次测量都准确,BBR 按照 delivery rate 测量值对 pacing rate 进行调整即可,在 BBR 状态机约束下按计算的 pacing rate 一直发下去,完全不必引入 cwnd 作为 secondary controller。
但现实中,每次测量滞后至少一个 RTT,时间不可倒流,未来不可预测,BBR 不得不为未来保留 buffer,这是固有的。本文描述 BBR 和 Loss-based 流共存时 cwnd 的意义。
因为 Loss-based 流导致 RTT baseline 可任意改变,若不限制 BBR cwnd,BBR 会被带偏。BBR 只根据测量值被动收敛,不会主动降速,而 ProbeUP 总能挤出带宽,BBR 便陷入和 Loss-based 流的军备竞赛,最终在 Loss-based 流一次次收缩后挤占整个 buffer,这和 BBR 的设计背道而驰。
BBR 引入 cwnd 作为 secondary controller 的动机可能是承认设计上不完备(这是缺陷,BBR 流之间的收敛全靠抢加速比)后的兜底。问题是,cwnd 多大合适,足够大,又不至于太大,足够小,又不至于太小。
cwnd 在传统 cc 意义上等同于 BDP,而 BDP 用 inflight 考量,考虑下图展示的场景:
Loss-based 流的固有排队场景,设 p = b / B,BBR 各测量量如下:
maxbw = (1 - p) * C (发生在 ProbeUP 阶段)
minrtt = p * B / C + R
BDP = (1 - p) * C * (p * B / C + R)
另一方面,ProbeUP 倾向于超速发送探测来挤占带宽,此时的 max-inflight 是 B * (1 - p) + R * maxbw。
设:
BDP = f§ = (1 - p) * C * (p * B / C + R)
max-inflight = g§ = B * (1 - p) + R * (1 - p) *C
B,C,R 为常数,p 不等于 1 时, 化简上式:
f§ 正比于 F§ = C * (p * B / C + R) = p * B + R * C
g§ 正比于 G§ = B + R * C
由于 p 只能在区间 (0, 1) 无限接近 1,因此 F§ < G§。
这意味着 ProbeUP 时的 inflight 被 BDP 限制,BBR 为 BDP 乘以一个 cwnd_gain 以摆脱 cwnd-limited,该值一定要大于 ProbeUP 的 pacing_gain 来承载对未来的过估预测(简单波动原理,ProbeUP gain 的效果一定要在一个 RTT 后才能被 sender 感知,在此之前 sender 根据 maxbw * minrtt 计算的 BDP 体现不出 gain 效果,因此 BDP 需要至少不小于 pacing_gain 的同步增益系数),但又不能太大以避免对 Loss-based 流的过度侵占。
cwnd_gain = g
cwnd = g * BDP 正比于 F’§ = g * F§
比较 F’§ 与 G§,当 F’§ < G§,属 cwnd-limited,F’§ >= G§,BBR 可执行正常逻辑,不受 cwnd 限制。
BBR 限定 cwnd_gain = 2,解释是:
Raising ProbeBW’s cwnd_gain to two allowed BBR to continue sending smoothly at the estimated delivery rate, even when ACKs are delayed by up to one RTT. This largely avoids stalls.
但这只是问题的一面,问题的另一面在当前 BBR 流以外,就是上文提到的那些。
我比较讨厌 BBR 论证的 “精确性”,但凡测量值,都是滞后的,当你测量到某值时,产生该值的瞬间已过去,用这个值预测未来,必须留 buffer。这是测不准的根本:
本质上讲,在 co-existence 场景, RTT 测不准,delivery rate 也测不准。所以围绕精确测量的精确推导,都是瞎扯。明明是统计量,偏要算个精确值。
为什么 cwnd_gain 是 2,而不是 2.8,而不是 3,也不是 1.8,这个 2 只是一个 buffer 量,是一个要参数。问这个问题就像问交换机 buffer 要配多大一样,要用统计方法,而不是单流行为计算。
BBR 的精确计算,就像热力学发展之前,人们对测量温度的态度一样,企图通过测量每一个分子来计算温度。
回到上面关于 g 值的问题。设 cwnd = g * BDP = max-inflight,所以:
g * p = p
所以,当 p < 1 / g,cwnd-limited,p >= 1 / g,BBR 可正常 ProbeUP。因此 BBR 在 ProbeBW 状态 ProbeUP 阶段最多占据 1 / g 的 buffer,这有效保护了其它 Loss-based 流。
可见,g 越大,BBR 越倾向于占更多 buffer,ProbeUP 是 MI 过程,间隔 8 rounds 的 ProbeUP 比长 RTT Loss-based 流的 AIMD 周期短很多,由于 ProbeUP 几乎一定会挤占出带宽,Drain 不会起太大作用,BBR 在这种情况下更有侵占性。
相反的一面,g 越小,BBR 越低效,g 无限接近于 1 时,p 接近于 1,BBR 总是 cwnd-limited,失去 ProbeUP 的空间。
cwnd_gain = 2 意思是,BBR 流最多仅能使用 1 - 1 / g = 1 / 2 的 buffer。
如文初所述,BBR 没有主动降速机制,不响应外部信号(即使丢包也会记住此前的 maxbw),这意味着它无法识别 Loss-based 流数量,无论与 1 条还是 n 条 Loss-based 流共存,对于 BBR 单流而言,它都有能力侵占(注意这个用词) 1 / g 的 buffer,BBR 流之间的共存靠加速比收敛,但对和 Loss-based 流共存时的收敛无能为力。
因此 g 的值与 BBR 流的数量,与 Loss-based 流的数量,以及每条流的 RTT 均有关系,这是个根据场景可调的参数,不可能被 “Delayed and Stretched ACKs” 完全覆盖。类似的还有,pacing_gain = 1.25,2885 / 1000… 为什么是 10s 而不是 8s:
The RTprop filter window (10 seconds) is short enough to allow quick convergence if traffic levels or routes change, but long enough so that interactive applications (e.g., Web pages, remote procedure calls, video chunks) often have natural silences or low-rate periods within the window where the flow’s rate is low enough or long enough to drain its queue in the bottleneck.
这些都是经验值,而不是算出来的。不过以上引用的解释是不是意味着我不能将 RTprop filter window 从 10s 改到 5s 了?管他呢,BBR 已经漏了。
我说过用 E = maxbw / minrtt 来收敛,就能覆盖各种情况了。参见 拉弗曲线与 BBR 收敛点,更合理的 BBR。
所以,BBR 只是一个单流算法,BBR 假设 bw 不再增加且 RTT 开始增加时,即到达最佳点,但这只是自我视角,一旦背景流侵入,退出,Probe,BBR 的假设几乎全失效,完全被吊着走,除了 ECN,没有任何外部信号指示其收敛,BBRv2 好一些,响应丢包而限制 inflight_lo,但也只是稍微。
BBR 之所以看起来好,完全因为人们只关注吞吐,而高吞吐则来自 BBR 的大开合行为,maxbw-filter 维持 10 rounds,minrtt-filter 维持 10s,而 maxbw,minrtt 均是采集到即生效,大开合周期太久,以至于对背景流的感知能力及其差,BBR 敏感度可谓愚钝级。
论 co-existence 吞吐能力,BBR 只比你将 cwnd 单纯调大这种行为好一点点。此外,值得一提的是,Linux 4.9 以后的内核降低了每个人玩大开合的门槛,只要实现 cong_control 回调函数就可以 bypass 几乎所有 Linux TCP 拥塞状态机,只需在这个函数里写一句 cwnd = 1000000000000,仅以吞吐考量而不论代价,你的算法就差不了。
冷眼旁边后对 BBR 再也不是初测时那种认知了,甚至没了当初的态度。模型确实勾画出了带宽的正确利用方式,但也仅此而已,BBR 自身都没能正确使用带宽。BBR 在共存流场景表现非常糟糕,其自身并没有内置任何自适应收敛机制且不对外界信号进行即时反馈,只会遗忘,而遗忘需要时间。在不应该的场合,BBR 对丢包又反应过激,不过这个我准备单独讨论。总之,BBR 不是一个名副其实的好的算法。
浙江温州皮鞋湿,下雨进水不会胖。