共计 4050 个字符,预计需要花费 11 分钟才能阅读完成。
小议 BBR 算法
BBR 全称 Bottleneck Bandwidth and RTT,它是谷歌在 2016 年推出的全新的网络拥塞控制算法。要说明 BBR 算法,就不能不提 TCP 拥塞算法。
传统的 TCP 拥塞控制算法,是基于丢包反馈的协议。基于丢包反馈的协议是一种被动式的拥塞控制机制,其依据网络中的丢包事件来做网络拥塞判断。即便网络中的负载很高时,只要没有产生拥塞丢包,协议就不会主动降低自己的发送速度。
TCP 在发送端维护一个拥塞窗口 cwnd,通过 cwnd 来控制发送量。采用 AIMD,就是加性递增和乘性递减的方式控制 cwnd,在拥塞避免阶段加性增窗,发生丢包则乘性减窗。
这个拥塞控制算法的假定是丢包都是拥塞造成的。
TCP 拥塞控制协议希望最大程度的利用网络剩余带宽,提高吞吐量。然而,由于基于丢包反馈协议在网络近饱和状态下所表现出来的侵略性,一方面大大提高了网络的带宽利用率;但另一方面,对于基于丢包反馈的拥塞控制协议来说,大大提高网络利用率同时意味着下一次拥塞丢包事件为期不远了,所以这些协议在提高网络带宽利用率的同时也间接加大了网络的丢包率,造成整个网络的抖动性加剧。
TCP 拥塞控制算法的假定是丢包都是拥塞造成的,而事实上,丢包并不总是拥塞导致,丢包可能原因是多方面,比如:路由器策略导致的丢包,WIFI 信号干扰导致的错误包,信号的信噪比(SNR)的影响等等。这些丢包并不是网络拥塞造成的,但是却会造成 TCP 控制算法的大幅波动,即使在网络带宽很好的情况下,仍然会出现发送速率上不去的情况。比如长肥管道,带宽很高,RTT 很大。管道中随机丢包的可能性很大,这就会造成 TCP 的发送速度起不来。
Google 的 BBR 出现很好的解决了这个问题。BBR 是一种基于带宽和延迟反馈的拥塞控制算法。它是一个典型的封闭反馈系统,发送多少报文和用多快的速度发送这些报文都是每次反馈中不断调节。
BBR 算法的核心就是找到两个参数,最大带宽和最小延时。最大带宽和最小延时的乘积就是 BDP(Bandwidth Delay Product), BDP 就是网络链路中可以存放数据的最大容量。知道了 BDP 就可以解决应该发送多少数据的问题,而网络最大带宽可以解决用多大速度发送的问题。如果网络比作一条高速公路,把数据比作汽车,最大带宽就是每分钟允许通行的汽车数量,最小 RTT 就是没有拥堵情况下,汽车跑一个来回需要的时间,而 BDP 就是在这条路上排满汽车的数量。
BBR 如何探测最大带宽和最小延时
BBR 是如何探测最大带宽和最小延时呢?首先有一点就是最大带宽和最小延时是无法同时得到的。
如图所示,横轴是网络链路中的数据量,纵轴分别是 RTT 和带宽。可以发现在 RTT 不变的时候,带宽一直在上升,没有达到最大,因为这个时候网络没有拥塞,而带宽停止上涨的时候 RTT 持续变大,一直到发生丢包。因为这个时候,网络开始拥塞,报文累积在路由器的 buffer 中,这样延时持续变大,而带宽不会变大。图中 BDP 的竖线所标识的就是理想情况下最大带宽和最小延时。很明显,要找到 BDP, 很难在同一时刻找到最小的 RTT 和最大带宽。这样最小 RTT 和最大带宽必须分别探测。
探测最大带宽的方法就是尽量多发数据,把网络中的 buffer 占满,带宽在一段时间内不会增加,这样可以得到此时的最大带宽。
探测最小 RTT 的方法就是尽量把 buffer 腾空,让数据交付延时尽量低。
由此,BBR 就引入了基于不同探测阶段的状态机。
状态机分为 4 个阶段,Startup,Drain,ProbeBW,ProbeRTT。
Startup 类似于普通拥塞控制里的慢启动,增益系数是 2ln2,每一个来回都以这个系数增大发包速率,估测到带宽满了就进入 Drain 状态,连续三个来回,测得的最大瓶颈带宽没有比上一轮增大 25% 以上,就算带宽满了。
进入 Drain 状态,增益系数小于 1,也就降速了。一个包来回,把 Startup 状态中产生的拍队排空,怎样才算队列空了?发出去还没有 ACK 的数据包量 inflight,与 BDP 进行比较,inflight < BDP 说明空了,道路不那么满了,如果 inflght > BDP 说明还不能到下一个状态,继续 Drain。
ProbeBW 是稳定状态,这时已经测出来一个最大瓶颈带宽,而且尽量不会产生排队现象。之后的每个来回,在 ProbeBW 状态循环(除非要进入下面提到的 ProbeRTT 状态),轮询下面这些增益系数,[5/4, 3/4, 1, 1, 1, 1, 1, 1],如此,最大瓶颈带宽就会在其停止增长的地方上下徘徊。大部分时间都应该处于 ProbeBW 状态。
前面三种状态,都可能进入 ProbeRTT 状态。超过十秒没有估测到更小的 RTT 值,这时进入 ProbeRTT 状态,把发包量降低,空出道路来比较准确得测一个 RTT 值,至少 200ms 或一个包的来回之后退出这个状态。检查带宽是否是满的,进入不同的状态:如果不满,进入 Startup 状态,如果满,进入 ProbeBW 状态。
BBR 算法不会因为一次或者偶然的丢包就大幅降低吞吐量,这样就比 TCP 就有较强的抗丢包能力。
如图所示,cubic 在丢包率上升的时候,吞吐量下降很快。而 BBR 在 5% 以上的丢包才会出现明显的吞吐量下降。
BBR 与基于丢包反馈的 cubic 和基于延时反馈的 vegas 算法的本质区别在于,BBR 无视随机丢包,无视时延短暂波动,采用了实时采集并保留时间窗口的策略,保持对可用带宽的准确探知。事实上,丢包并不一定会造成带宽减少,延迟增加也不一定会造成带宽减少,cubic 无法判断是否拥塞造成的丢包,vegas 对延时增加过于敏感,会导致竞争性不足。
BBR 可以区分出噪声丢包和拥塞丢包,这样意味着,BBR 比传统 TCP 拥塞控制算法具有更好的抗丢包能力。
BBR 在实时音视频领域的应用
实时音视频系统要求低延时,流畅性好,而实际网络状态却是复杂多变的,丢包,延时和网络带宽都在时刻变化,这就对网络拥塞控制算法提出了很高的要求。它需要一种带宽估计准确,抗丢包和抖动能力好的拥塞控制算法。
目前 Google 的 webrtc 提供了 GCC 控制算法,它是一种发送侧基于延迟和丢包的控制算法,这个算法的原理在很多地方都有详细描述,这里不再赘述。GCC 用于实音视频的主要问题还在于在带宽发生变化时,它的带宽跟踪时间比较长,这样就会造成带宽突变的时候无法及时准确探测带宽,可能造成音视频卡顿。
既然 BBR 有良好的抗丢包能力,自然也被想到应用到实时音视频领域。但是,BBR 并不是为处理实时音视频设计的,所以需要对一些问题做一些优化。
第一,BBR 在丢包率达到 25% 以上,吞吐量会断崖式下降。
这是由 BBR 算法的 pacing_gain 数组 [5/4, 3/4, 1, 1, 1, 1, 1, 1] 的固定参数决定的。
在 pacing_gain 数组中,其增益周期的倍数为 5 /4,增益也就是 25%,可以简单理解为,在增益周期,BBR 可以多发送 25% 的数据。
在增益期,丢包率是否抵消了增益比 25%?也就是说,x 是否大于 25。
假设丢包率固定为 25%,那么,在增益周期,25% 的增益完全被 25% 的丢包所抵消,相当于没有收益,接下来到了排空周期,由于丢包率不变,又会减少了 25% 的发送数据,同时丢包率依然是 25%… 再接下来的 6 个 RTT,持续保持 25% 的丢包率,而发送率却仅仅基于反馈,即每次递减 25%,我们可以看到,在 pacing_gain 标识的所有 8 周期,数据的发送量是只减不增的,并且会一直持续下去,这样就会断崖式下跌。
怎样才能对抗丢包,这就需要在每个周期考虑丢包率,把丢包率补偿进去。比如丢包率达到 25% 的时候,增益系数就变成 50%,这样就可以避免由于丢包带来的反馈减损,然而,你又如何判断这些丢包是噪声丢包还是拥塞丢包呢?答案在于 RTT,只要时间窗口内的 RTT 不增加,那么丢包就不是拥塞导致的。
第二,BBR 的最小 RTT 有个 10s 超时时间,在 10s 超时后,进入 ProbeRTT 状态,并持续最小 200ms,此状态下,为了排空拥塞,inflight 只允许有 4 个包,这会导致音视频数据在这段时间内堆积在发送队列中,使得时延增加。
可行的解决办法是,不再保留 ProbeRTT 状态,采用多轮下降的方式排空拥塞,然后采样最小 RTT,也就是在 infight > bdp 的时候,设置 pacing gain 为 0.75,用 0.75 倍带宽作为发送速率,持续多轮,直到 inflight < bdp, 此外,最小 RTT 的超时时间改成 2.5s,也就是说不采用非常激进的探测方式,避免了发送速率的大幅波动,可以改善探测新的带宽过程中发送队列中产生的延时。
第三,开始提到 pacing gain 数组上探周期为 1.25 倍带宽,随后是 0.75 倍带宽周期,这两个 RTT 周期之间会出现发送速率的剧烈下降,这可能会使音视频数据滞留在 buffer 中发不出去,引入不必要的延时。
解决办法可以考虑减小上探周期和排空周期的幅度,比如使用 [1.1 0.9 1 1 1 1 1 1] 这种 pacing gain 参数,这样做的优点就是可以保证媒体流的平稳发送,发送速率不会大幅波动,缺点是,网络带宽改善的时候,上探时间会变长。
第四,BBR 探测新带宽收敛慢的问题
原始的 BBR 算法的收敛性受到 pacing gain 周期影响,带宽突降的时候,BBR 需要多个轮次才会降到实际带宽。这是由于 BBR 每轮只能降速一次,而 pacing gain 的 6 个 RTT 的保持周期大大加长了这个时间。解决的办法就是随机化 pacing gain 的 6 个保持周期,如果是 0.75 倍周期,就一次降速到位,这样可以极大的减少 BBR 的收敛时间。
最后,BBR 算法看似简单,但是应用到实时音视频却没有那么简单,需要大量的实验优化,谷歌也在 webrtc 中引入 BBR,目前仍在测试中。本文提到的改进方法是网易云信在这方面的一些尝试,希望能够抛砖引玉,有更多有兴趣的人能够为 BBR 应用到实时音视频领域出力。
想要阅读更多技术干货、行业洞察,欢迎关注网易云信博客。
了解网易云信,来自网易核心架构的通信与视频云服务。