跳到内容

10.2 CUBIC、BBR、pacing 与 active queue management

雨季让瓶颈链路前的队列越积越长,值守员想通过提高发送速率解决,结果延迟反而继续上升。

Congestion-control algorithm 决定 sender 如何根据 feedback 更新 sending model/window/rate。Queue discipline 决定 bottleneck 如何排队、mark 或 drop packet。Pacing 决定 sender 如何在时间上分布 packet。这三层可相互影响,不能用“换一个 TCP algorithm 就修复全网”概括。

1. CUBIC 仍是 loss-based controller,但 window growth 不再只按 ACK count 线性推进

RFC 9438 定义 CUBIC 的 standard form。Loss event 前的 window 记为 W_max,loss 后按 multiplicative factor 减小,随后 window target 主要根据自 congestion epoch 开始后的 elapsed time 在 W_max 附近走 cubic curve:

text
W_cubic(t) = C * (t - K)^3 + W_max

K 使 curve 在上次 W_max 附近有一段平缓区:

  • 远低于 W_max 时较快恢复;
  • 接近 old operating point 时谨慎 probe;
  • 超过 old W_max 后继续探测新 capacity。

CUBIC 还有 Reno-friendly region、fast convergence、application-limited handling 与 overflow/precision requirement。一个只计 cubic formula并用 max(current, target) 的 Python 类不是 RFC-compliant CUBIC。

RFC 的 multiplicative-decrease factor 要按定义区分:beta_cubic 表示保留比例(标准推荐值为 0.7),不要一处把 beta=0.3 当“减少量”,另一处又用 cwnd *= beta

CUBIC 减少 RTT bias,但不保证不同 RTT flow 绝对公平。ACK clock、loss probability、pacing、queue、Reno coexistence 和 implementation 仍影响 outcome。它在很多 Linux system 中常见,但“Linux 到今天在任意发行版都必然默认 CUBIC”不是可依赖 contract,应检查当前 host configuration。

2. BBR 建模 delivery rate 与 minimum RTT

BBR family 估计:

  • Bottleneck bandwidth(BtlBw):recent delivery-rate sample 中对 bottleneck capacity 的 model;
  • Round-trip propagation time(RTprop):一个 time window 内 minimum RTT 的 model,试图减少 queueing component。

Model 用于设 pacing rate 和 in-flight/cwnd target,并通过 gain cycling/probing 发现 bandwidth 变化。BtlBw * RTprop 提供 BDP estimate,但 BBR 不是简单固定在这个数值,它需要 ACK aggregation、policer、loss/ECN、startup/drain、probe scheduling 和 app-limited sample 处理。

“BBR 不看 loss”不准确。BBR 不把 first loss 当作和 Reno/CUBIC 一样的主要 operating point signal,但 implementation/version 会使用 loss、ECN 和 in-flight bound 处理 excessive congestion。BBR v1/v2/v3 的行为也不相同,不应用一个“每 8 RTT 先 1.25x 再 0.75x”的玩具类代表所有版本。

Model-based 不等于自动公平或无队列。BBR 与 loss-based flow 共享 drop-tail bottleneck、不同 RTT 或 policer 时的 fairness/queue behavior 需要实测,也是后续版本改进的重点。

3. Pacing 降低 burst,但不创造 capacity

Window controller 可允许一整个 ACK burst 的 data 立即发出。Pacing 将这些 packet 按 rate 分散到时间上,减少 microburst、queue spike 和 loss synchronization。

Pacing rate 设太高仍会排队,设太低会 underutilize。Timer granularity、NIC offload、qdisc 和 application burst 影响 packet 在 wire 上的真实间隔。TSO 使 kernel 向 NIC 交付 large segment 时,NIC 的 segmentation/pacing capability 决定它是否在 wire 上又变成 burst。

4. Bufferbloat 是过量 queueing delay,不只是“buffer 大”

Buffer 能 absorb short burst,但 persistently full queue 会把 RTT 从 propagation baseline 抬高到 queue drain time。Loss-based sender 如果要等 drop-tail queue 填满才收到 signal,latency-sensitive traffic 会在 throughput 仍很高时受到数百 ms 额外 delay。

Bufferbloat 应用 under-load latency 定义/观测,不只看 configured bytes。Link rate 降低时,同样 1 MB queue 的 drain time 会大幅增加,所以 variable-rate link(Wi-Fi/cellular)尤其难。

text
queueing delay ≈ queued bytes / bottleneck service rate

这是一个量级估算,scheduling、packet size、wireless retransmission 和 rate change 会使 actual sojourn time 不同。

5. AQM 在 queue 溢出前 mark/drop

Active Queue Management(AQM)不等 buffer full 才反应,而是根据 queue state 提前 ECN-mark 或 drop,给 sender congestion signal。

CoDel 根据 packet sojourn time(在 queue 中停留多久)识别 persistent queue,而不只看 queue bytes。它有 targetinterval、MTU/packet-mode 等 parameter/assumption,虽然目标是减少对 link bandwidth/RTT 的手工调参,但“CoDel 无任何配置参数”是错误的。

FQ-CoDel 先将 flow 分 queue/schedule,再对每个 queue 使用 CoDel-style AQM,防止一个 bulk flow 的 queue 完全压住 sparse latency-sensitive flow。Flow hashing 会 collision,non-responsive flow 与 tunnel aggregation 也需要考虑。CAKE 还组合 shaping、diffserv、host isolation 和 overhead compensation 等功能。

AQM 需要放在真 bottleneck queue 或通过 shaping 人为创建的可控 bottleneck。如果 ISP modem 的隐藏 queue 在你的 qdisc 之前就更慢,只在 LAN interface 换 qdisc 可能没有作用。

6. 观测比“切换算法”先一步

bash
# 查 host 可用/默认 congestion-control algorithm
sysctl net.ipv4.tcp_available_congestion_control
sysctl net.ipv4.tcp_congestion_control

# 查连接的 RTT、cwnd、retransmission、pacing/delivery-rate 等
ss -tin

# 查 queue discipline 和 drop/mark/backlog counter
tc -s qdisc show

Field 因 kernel/version/algorithm 而异。ss 看到 cwnd:100 时还要知道 MSS、bytes-in-flight、application-limited state、pacing rate、rwnd 和 RTT,不能用 cwnd 一个数字推算 throughput。

要比较 CUBIC/BBR/AQM,实验必须控制:

  • bottleneck rate、base RTT、queue size/qdisc;
  • loss/reordering/ECN policy;
  • number of flows、flow start time、RTT asymmetry;
  • sender/receiver kernel 与 offload;
  • measurement duration 与 warm-up;
  • throughput、p50/p95/p99 latency、loss/retransmission、fairness 与 CPU。

只测一次 file download 的完成时间,无法支撑“算法 A 比 B 快”的普遍结论。不要在 shared production host 直接全局切 sysctl 做试验;先用 network namespace/testbed 或 per-socket configuration,并准备 rollback。

7. 一个 BDP/queue-delay 量级计算器

python
def bdp_bytes(rate_mbps: float, base_rtt_ms: float) -> float:
    if rate_mbps <= 0 or base_rtt_ms <= 0:
        raise ValueError("rate and RTT must be positive")
    return rate_mbps * 1_000_000 / 8 * base_rtt_ms / 1_000


def queue_delay_ms(queued_bytes: int, rate_mbps: float) -> float:
    if queued_bytes < 0 or rate_mbps <= 0:
        raise ValueError("invalid queue/rate")
    return queued_bytes * 8 / (rate_mbps * 1_000_000) * 1_000


assert bdp_bytes(100, 20) == 250_000
assert queue_delay_ms(1_000_000, 20) == 400

这只是 fluid-model estimate,不是对 packet scheduler 和 wireless link 的精确 simulator。它的用处是检查数量级:20 Mbps bottleneck 上堆 1 MB data 本身就需约 400 ms 才能 drain。

8. 验收问题

  1. CUBIC 的 W_maxK 和 cubic epoch 分别帮助控制什么?
  2. 为什么不能说 BBR “完全不看 loss”?
  3. Pacing 为什么能降低 burst,却不能创造 bottleneck capacity?
  4. CoDel 和 FQ-CoDel 有什么差异?
  5. 为什么 AQM 必须放在真正/可控的 bottleneck 才能管 queue delay?

参考

Built with VitePress | Software Systems Atlas