24、拥塞控制性能指标:吞吐量、延迟、抖动、公平性、收敛速度、评估方法

聊到拥塞控制,大家第一反应往往是算法本身——GCC、BBR、NADA,哪个更牛?但说实话,我在实际项目中踩过最大的坑,反而不是算法选型,而是不知道怎么衡量算法好不好

你想想看,没有一把好用的尺子,你怎么知道调参是调对了还是调歪了?

所以这一章,咱们先把尺子定下来。我个人习惯把这六个指标称为「拥塞控制的六边形战士」:吞吐量、延迟、抖动、公平性、收敛速度,再加上评估方法。嗯,一个一个来拆。

吞吐量(Throughput)

吞吐量是最直观的指标。说白了,就是单位时间内能传多少数据。单位通常是 Mbps 或 Kbps。

但这里有个坑——吞吐量不等于带宽利用率。我曾经在某个项目中,看到吞吐量跑满了 10Mbps,但实际视频质量还是一塌糊涂。为什么?因为吞吐量高不代表数据都是有效的,重传、冗余包全算进去了。

避坑指南: 我曾经只盯着吞吐量调优,结果发现 30% 的流量都是重传包。后来我养成了一个习惯:同时看「有效吞吐量」和「总吞吐量」两个指标。

吞吐量的测量方式也很简单:

// 伪代码:接收端统计吞吐量
uint64_t totalBytes = 0;
uint64_t startTime = now_ms();

// 每收到一个包就累加
onReceive(packet) {
    totalBytes += packet.size;
}

// 每秒计算一次
everySecond() {
    uint64_t elapsed = now_ms() - startTime;
    double throughput = (totalBytes * 8) / (elapsed / 1000.0); // bps
    reset();
}

延迟(Latency)

延迟是实时通信的命门。WebRTC 里我们通常关注两种延迟:

  • 单向延迟(One-way Delay):从发送到接收的时间差
  • 往返时间(RTT):发出去到收到确认的时间

我个人更看重单向延迟,因为 RTT 有时候会骗人。举个例子:如果网络是对称的,RTT/2 约等于单向延迟。但现实网络往往不对称——上行拥塞、下行通畅,这时候 RTT/2 就完全不准了。

小技巧: 我在做 WebRTC 优化时,会同时部署 NTP 时间同步,然后用接收时间戳减去发送时间戳来算单向延迟。虽然 NTP 本身有误差,但比 RTT/2 靠谱多了。

抖动(Jitter)

抖动,说白了就是延迟的变化量。你想想看,如果延迟稳定在 50ms,哪怕绝对值高一点,接收端也能通过 jitter buffer 平滑处理。但如果延迟在 10ms 到 200ms 之间反复横跳,那视频就会一卡一卡的。

WebRTC 里常用的抖动计算公式是:

// 基于 RFC 3550 的抖动计算
double jitter = 0;
double previousJitter = 0;

onReceive(packet) {
    double D = |(R_i - R_{i-1}) - (S_i - S_{i-1})|;
    jitter = previousJitter + (|D| - previousJitter) / 16;
    previousJitter = jitter;
}

这个公式有个特点:它用了一个 1/16 的平滑因子,不会因为单个异常包就剧烈抖动。嗯,这里要注意,如果网络突然变差,这个指标反应会慢半拍。我在项目中遇到过这种情况,后来加了一个「瞬时抖动」的辅助指标来弥补。

公平性(Fairness)

公平性是个很有意思的指标。它衡量的是:当多个流共享同一条链路时,每个流能不能分到「合理」的带宽。

常用的公平性指标是 Jain's Fairness Index

公式 说明
F = (∑x_i)² / (n * ∑x_i²) x_i 是第 i 个流的吞吐量,n 是流数量
F = 1 完全公平,所有流吞吐量相等
F = 1/n 最不公平,一个流占用了全部带宽

我记得有一次做多流测试,GCC 和 BBR 混跑,公平性指数只有 0.6 左右。BBR 把带宽抢走了大半,GCC 被压得死死的。后来我们调整了 BBR 的 pacing 参数,才把公平性拉到 0.85 以上。

核心观点: 公平性不是越高越好。有时候为了保障某个高优先级流(比如屏幕共享),我们会有意降低公平性。关键是要「可控」。

收敛速度(Convergence Speed)

收敛速度衡量的是:当网络条件发生变化时,算法需要多久才能稳定到新的工作点。

举个例子:带宽从 5Mbps 突然降到 1Mbps,好的拥塞控制算法应该在 1-2 个 RTT 内就降速到位。如果花了 5 秒才反应过来,那这 5 秒里用户看到的全是卡顿和花屏。

我一般用两个指标来衡量收敛速度:

  • 上升收敛时间:带宽增加时,吞吐量达到新带宽 90% 所需的时间
  • 下降收敛时间:带宽减少时,吞吐量降到新带宽 110% 以内所需的时间

下降收敛时间比上升收敛时间更重要。为什么?因为带宽下降时如果不快速反应,就会造成大量丢包。我在项目中遇到过,某个算法上升收敛只要 200ms,但下降收敛要 2 秒——结果就是每次带宽抖动,视频就断流 2 秒。

评估方法

指标有了,怎么评估?我常用的方法有三种:

  1. 仿真测试:用 ns-3 或自定义的丢包/延迟模型,跑大量场景
  2. 回放测试:录制真实的网络 trace(比如 4G 网络下的带宽变化),回放给算法
  3. 实际网络测试:在真实互联网上跑,收集数据

我个人最推荐「回放测试」。因为仿真太理想化,实际网络又不可控。回放测试既能保证场景可复现,又能贴近真实网络的行为。

经验之谈: 我通常会准备 10 个典型的网络 trace:3 个稳定场景、3 个抖动场景、2 个带宽骤降场景、2 个带宽骤升场景。每次算法改版,先跑一遍这 10 个 trace,看六个指标的变化。

下面这张图是我常用的评估流程,你可以参考一下:

拥塞控制评估流程 网络 Trace 准备 10个典型场景 拥塞控制算法运行 GCC / BBR / NADA 指标采集 吞吐量/延迟/抖动 数据分析 公平性/收敛速度 对比基线 与历史版本/竞品对比 决策 通过 / 回滚 / 调参 调参后重测 每个场景至少跑 3 次,取中位数作为最终结果

最后说一句,这六个指标不是孤立的。吞吐量和延迟之间就有天然的 trade-off——你追求极致吞吐量,延迟就会上升;你死磕低延迟,吞吐量就会下降。好的拥塞控制算法,就是在这些指标之间找到一个「甜点」。

嗯,这一章的内容就到这里。下一章我们会深入 GCC 算法的具体实现,看看 Google 是怎么在实时通信场景下平衡这些指标的。


公众号:蓝海资料掘金营,微信deep3321