删除 Inflight Bounds:为什么 KCC 放弃了 BDP 钳位


一段被继承的设计

BBR 的核心循环只有三个增益——1.25× 探测、0.75× 排水、1.0× 巡航。cwnd 不被人为设限;它由 pacing rate 和 ACK clock 共同决定,自然收敛到 BDP 附近。

但 BBRv2 引入了一对 cwnd 钳位:inflight_lo(默认 1.25× BDP)和 inflight_hi(默认 2.0× BDP)。目标是在探测阶段把 cwnd 锁在一个"安全"区间——不要过低导致欠填充,不要过高导致丢包。

KCC 在最初构建时继承了这个设计。参数化更灵活(低端默认 1.0×,高端默认 2.0×),但本质是同一件事。

后来我们删掉了它。


两把钳子,两条路径

要理解为什么删,先要看钳子夹在什么地方。

cwnd 路径——kcc_set_cwnd() 每收到一个 ACK 就计算一次目标 cwnd。把钳子放在这里意味着:每一个 ACK 都要检查"我算出来的 cwnd 是不是太低/太高了?"低就垫上去,高就压下来。

pacing 路径——bbr_set_pacing_rate() 决定发包速度。内核 BBR 在这里有一个 bbr_inflight_hi_from_bw(),它对 pacing 的 inflight 做了 2× BDP 上限。但这不夹 cwnd——它限制的是允许在途的数据量,不是窗口本身。

两者的区别微妙但重要:cwnd 钳位改变的是算法认为自己需要多少空间;pacing 钳位改变的是算法实际发送多少数据。 前者是容量估计偏差,后者是行为约束。


1.0× 下限:自己挡自己

KCC 的 DRAIN 模式(STARTUP 过渡到 PROBE_BW):pacing gain = 0.344x,但 cwnd_gain 保持在 2.885x。所以下限想挡也没东西可挡——target cwnd 已经是 2.885x,远超 1.0x。

那下限会挡什么?挡 PROBE_BW 周期内的 0.75x 探测阶段。每 8 轮 PROBE_BW,有 6 轮巡航、1 轮上探、1 轮下探。下探时 pacing gain = 0.75x。如果算法算出一轮内 inflight 略微低于 BDP,1.0x 下限把它垫回 BDP——这是保护,但也是过度保护:0.75x 阶段的目的就是让 inflight 有机会降到 BDP 以下,以便确认 BDP 还是对的。

但在多流场景,1.0x 下限才是真问题——不是挡排水,是聚合过配(见下文)。

单独说:1.0x 下限放在 cwnd 路径并无实际害处。把它和 2.0x 上限一起删掉的理由不是"它对 drain 有害"——是"它和上限是同一逻辑的两面,而这个逻辑该由其他机制处理"。

/* The pacing gain of 1/high_gain in BBR_DRAIN is calculated to typically drain
 * the queue created in BBR_STARTUP in a single round:
 */
static const int bbr_drain_gain = BBR_UNIT * 1000 / 2885;

从上限看关注点分离

2.0× BDP 上限放在 cwnd 路径,逻辑是这样的:ACK 聚合可能让一个 ACK 确认超过 1 BDP 的数据,cwnd 会爆炸。所以要夹。

但内核 BBR 处理同样的问题用的是一个更精确的工具——bbr_quantization_budget()。它不是硬夹到 2×,而是按 TSO 粒度、偶数轮次、probe 余量计算一个刚好够的量化头寸。然后 ACK 聚合有专门的 bbr_ack_aggregation_cwnd() 补偿,有额外数据才加,没有就不加。

换言之:cwnd 路径本身就有两个工具精确处理上限问题。2.0× 钳位是一个更粗糙的兜底——它覆盖了这两个工具本该处理好的情况。

是否需要这个兜底?如果你的量化预算和聚合补偿是正确的,它从来不会触发。如果触发了,说明前两个工具有问题,你应该修它们,而不是加一个钳子。


多流过配:1.25× 的聚合效应

假设瓶颈带宽 B,8 条流共享。每条流认为自己的公平均速大约是 B/8。

如果每条流都放了一个 1.25× BDP 下限,那总会有人感到压力:

Σ inflight_floor = 8 × (0.25 × B/8 + B/8)
                 = 8 × 1.25 × B/8
                 = 1.25 × B

八条流的 cwnd 总和不低于瓶颈的 1.25 倍。这不是八个人分一张饼——每个人说"我至少要吃 1.25 口的份额",一共预定了 10 口的量。池子里只有 8 口。

所以 1.25× 下限在一个多流路径上不是保险,是过配的根源。每条流都觉得"我的下限很安全",聚合起来就是"大家都不安全"。

这就是为什么 KCC 从一开始就没用 BBRv2 的 1.25× 默认而用 1.0×——不是因为 1.0× 更"好",而是因为 1.25× 的聚合效应在共享瓶颈上制造了它声称要避免的问题。


删掉之后:少了两把钳子,what changes?

inflight 钳位被取下后:

PROBE_BW 下探阶段不会被下限强制垫回 BDP——可以暂时低于 BDP,确认容量估计仍然正确。不会因为钳子而跳过排水周期。

STARTUP 阶段不受影响——KCC 的 STARTUP 用的是指数爬升(cwnd += acked),钳子本来就只设下限不设上限。下限和 BDP 目标在 STARTUP 本来就不是语义正确的约束。

cruise 阶段的收敛完全交给 ACK clock——这和内核 BBR v5.4 一模一样。pacing rate 决定发送速度,cwnd 提供空间。两者各司其职。

多流场景从代码层面不会再有一个硬编码的过配因子。聚合 inflight 接近瓶颈容量,而非高于它。


选路而非修路

inflight bounds 不是错的。它们在另一种路径上完全合理——浅缓冲区路径、单流主导的场景、BBRv2 需要兼容 CUBIC 共存策略的地方。

KCC 选的路是另一条:跟着内核 BBR v5.4 的 cwnd 模型,把更极致的边界处理留给 pacing 路径和独立工具。ACK 聚合有补偿,TSO 有量化预算,多流过配有 drain-to-target 排水——每个问题有它对应的解法,而不是让一个通用钳位覆盖所有。

删掉两把钳子不是"简化",是"各归其位"。


Tags: TCP, BBR, KCC, congestion control, inflight bounds, separation of concerns

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐