10.2 CUBIC、BBR、发送节奏与主动队列管理
下载速度很高,交互请求却越来越慢,常见原因是瓶颈前积起了长队。拥塞控制决定发送多少,pacing 决定何时发,队列管理决定如何排队、标记和丢包。本课分开这三项职责,比较 CUBIC、BBR 与 AQM,再用数量级计算检查排队延迟。
1. CUBIC 用时间驱动三次曲线增长
RFC 9438 定义了标准轨道的 CUBIC。它通常归为基于丢包的拥塞控制算法,也会响应 ECN 等拥塞信号。记忆中的拥塞窗口为 W_max,窗口减小后,目标值主要随本轮拥塞避免开始后的时间 t 沿三次曲线增长:
W_cubic(t) = C * (t - K)^3 + W_maxC 决定曲线的尺度,K 表示在没有新拥塞事件时,曲线回到 W_max 所需的时间。它形成三个区间:
- 明显低于
W_max时较快恢复; - 接近上次工作点时放缓增长;
- 超过
W_max后继续探测剩余容量。
完整 CUBIC 还有 Reno 友好区间、快速收敛、应用供数不足时的处理和精度要求。只计算一次三次公式并取 max(current, target),不足以实现整个算法。
RFC 中的 beta_cubic 表示减小后保留的比例,推荐值为 0.7。例如窗口为 100 SMSS 时,乘以 0.7 留下 70 SMSS,而不是留下 30。CUBIC 减少部分 RTT 偏差,但 ACK 节奏、丢包、pacing、队列和其他算法共存仍会影响公平性。RFC 9438
CUBIC 在许多 Linux 系统中可用,但本机默认值和连接实际算法都应查询,不能从发行版名称推断。
2. BBR 估计交付速率与最小 RTT
BBR 系列主要建立两个路径模型:
- 瓶颈带宽估计,来自近期交付速率样本;
- 最小 RTT 估计,尝试减少排队延迟对基础路径时延的影响。
带宽与最小 RTT 的乘积给出 BDP 估计,再用于设置发送速率和在途量上限。BBR 还需要启动、排空、带宽探测和 RTT 探测等阶段,以及 ACK 聚集、应用供数不足和卸载批量等修正,不能简化成始终发送一个固定 BDP。
BBR 并非完全忽略丢包。不同版本会利用丢包率和在途量上界控制过度探测;v1、v2、v3 的策略不同,一个固定的“每 8 RTT 轮流乘 1.25 和 0.75”模型不能代表整个系列。
这里的版本依据是工作组草案 draft-ietf-ccwg-bbr-06,描述 BBRv3,仍是实验性工作。该版没有规定专门的 ECN 响应算法;若连接通过 ECT 宣告支持 ECN,仍必须把 CE 标记当作拥塞信号处理,不能默默忽略。BBR 草案 §3.7
基于模型不保证自动公平或完全无队列。与基于丢包的流共享尾丢弃瓶颈、RTT 不同或存在限速器时,都需要实测。记录内核和 BBR 实现版本,否则“用了 BBR”还不足以复现实验。
3. Pacing 分散突发,不增加带宽
一批 ACK 同时到来时,窗口允许的新数据可能一次性发出。Pacing 按目标速率把发送分散到时间上,减少微突发、队列尖峰和同步丢包。
速率设得太高仍会排队,太低则浪费容量。计时粒度、网卡卸载、队列规则和应用突发都会影响线上间隔。启用 TSO 后,内核交给网卡的大段数据如何切分与发送,也要看硬件的 pacing 能力。
4. Bufferbloat 要看负载下的排队延迟
缓冲可以吸收短突发,长期积压却会抬高 RTT。如果发送端只能等尾丢弃队列装满才收到拥塞信号,交互流量可能在吞吐仍很高时承受很长的额外等待。
相同队列字节数,在不同带宽下的等待时间差别很大;Wi-Fi 和移动网络的速率变化尤其值得留意。先用下面的关系估算量级:
queueing delay ≈ queued bytes / bottleneck service rate它没有模拟调度、包长、无线重传和速率变化。应结合负载下的延迟观测,不能只看到缓冲配置大,就直接断言发生了 bufferbloat。
5. AQM 在队列溢出前标记或丢包
主动队列管理(Active Queue Management,AQM)根据队列状态提前标记 ECN 或丢包,为发送端提供反馈,而不是只在缓冲耗尽时才采取动作。
CoDel 根据报文在队列中的停留时间判断持续排队,而非只看队列字节数。它有 target、interval 等参数和实现限制;目标是减少对带宽等环境参数的人工调优,不能理解成完全没有配置。RFC 8289
FQ-CoDel 先把流分配到多个队列并调度,再分别应用 CoDel,避免一个大流的积压压住稀疏交互流。散列碰撞、隧道把多流聚成一流以及不响应拥塞的流仍需考虑。RFC 8290 CAKE 进一步组合整形、DiffServ、主机隔离和链路开销补偿。iproute2 CAKE 手册
AQM 需要控制真正的瓶颈队列,或通过整形把瓶颈放在自己能管理的位置。若更慢的队列藏在运营商设备中,只修改高速 LAN 接口的 qdisc,未必能减少那里的等待。
6. 先观测,再比较算法
以下为 Linux 的只读检查,依赖 procps 和 iproute2;它们不会切换算法或修改队列:
# 查询当前可用算法与默认算法
sysctl net.ipv4.tcp_available_congestion_control
sysctl net.ipv4.tcp_congestion_control
# 查看连接的 RTT、cwnd、重传与发送速率
ss -tin
# 查看队列规则、丢包、标记和积压计数
tc -s qdisc showss 中 cwnd:100 通常以段为单位,还需结合 MSS、在途量、应用是否供数不足、pacing、rwnd 和 RTT 解读。字段随内核和算法变化;可用算法列表也可能只列出已注册的算法,并不等于内核还能加载哪些模块。
比较 CUBIC、BBR 或 AQM 时,应控制:
- 瓶颈速率、基础 RTT、队列大小与 qdisc;
- 丢包、乱序和 ECN 策略;
- 流数、启动时间和不同流的 RTT;
- 收发端内核版本及卸载配置;
- 预热和测量时长;
- 吞吐、p50/p95/p99 延迟、重传、公平性与 CPU 指标。
一次文件下载无法支持算法优劣的普遍结论。实验应在独立测试环境、网络命名空间或单套接字配置中进行;共享生产主机上的全局 sysctl 变更会影响其他连接,需要单独的变更与回退安排。
7. 计算 BDP 与队列排空时间
下面的 Python 3 示例使用十进制 Mbps、MB 和有限数值输入。保存为 queue_math.py,运行 python3 queue_math.py;代码只做量级计算,不配置网络:
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
print(bdp_bytes(100, 20))
print(queue_delay_ms(1_000_000, 20))预期输出:
250000.0
400.0100 Mbps、基础 RTT 为 20 ms 的路径,BDP 约为 250000 字节;20 Mbps 的瓶颈排空 1 MB 积压数据,需要约 400 ms。它是流体模型估算,不是报文调度或无线链路模拟器。带宽很忙,用户也可能一直在等。
8. 验收问题
W_max、C 与 K 分别怎样影响 CUBIC 曲线?- 为什么不能说 BBR 完全不看丢包,为什么还要注明实现版本?
- Pacing 为什么能降低突发,却不能创造容量?
- CoDel 与 FQ-CoDel 如何区别?
- 为什么控制不到瓶颈队列的 AQM 可能无法降低排队延迟?