Published at 2022-01-02 | Last Update 2022-01-02
本文翻译自 Google 2017 的论文:
Cardwell N, Cheng Y, Gunn CS, Yeganeh SH, Jacobson V. BBR: congestion-based congestion control. Communications of the ACM. 2017 Jan 23;60(2):58-66.
论文副标题:Measuring Bottleneck Bandwidth and Round-trip propagation time(测量瓶颈带宽和往返传输时间)。
BBR 之前,主流的 TCP 拥塞控制算法都是基于丢包(loss-based)设计的, 这一假设最早可追溯到上世纪八九十年代,那时的链路带宽和内存容量分别以 Mbps 和 KB 计,链路质量(以今天的标准来说)也很差。
三十年多后,这两个物理容量都已经增长了至少六个数量级,链路质量也不可同日而语。特别地,在现代基础设施中, 丢包和延迟不一定表示网络发生了拥塞,因此原来的假设已经不再成立。 Google 的网络团队从这一根本问题出发,(在前人工作的基础上) 设计并实现了一个基于拥塞本身而非基于丢包或延迟的拥塞控制新算法,缩写为 BBR。
简单来说,BBR 通过应答包(ACK)中的 RTT 信息和已发送字节数来计算 真实传输速率(delivery rate),然后根据后者来调节客户端接下来的 发送速率(sending rate),通过保持合理的 inflight 数据量来使 传输带宽最大、传输延迟最低。另外,它完全运行在发送端,无需协议、 接收端或网络的改动,因此落地相对容易。
Google 的全球广域网(B4)在 2016 年就已经将全部 TCP 流量从 CUBIC 切换到 BBR, 吞吐提升了 2~25 倍;在做了一些配置调优之后,甚至进一步提升到了 133 倍(文中有详细介绍)。
Linux 实现:
include/uapi/linux/inet_diag.h
net/ipv4/tcp_bbr.c
翻译时适当加入了一些小标题,另外插入了一些内核代码片段(基于内核 5.10
),以更方便理解。
由于译者水平有限,本文不免存在遗漏或错误之处。如有疑问,请查阅原文。
以下是译文。
从各方面来说,今天的互联网传输数据的速度都不甚理想:
这些问题是由 TCP 的设计导致的 —— 20 世纪 80 年代设计 TCP 拥塞控制(congestion control) 时,认为丢包是发生了“拥塞”13。 在当时的技术条件下我们可以这样认为,但它并非第一原则 (technology limitations, not first principles)。随着网卡从 Mbps 到 Gbps、 内存从 KB 到 GB,丢包和拥塞之间的关系也变得愈发微弱。
今天,TCP 那些基于丢包的拥塞控制(loss-based congestion control) —— 即使是目前其中最好的 CUBIC11 —— 是导致这些问题的主要原因。
解决这些问题需要一种全新的方式,而这首先需要对以下两点有深入理解:
对于一个(全双工)TCP 连接,在任意时刻,它在每个方向都有且只有一段最慢的链路 (exactly one slowest link)或称瓶颈(bottleneck)。这一点很重要,因为:
瓶颈决定了连接的最大数据传输速率。这是不可压缩流(incompressible flow)的一个常规特性。
例如,考虑交通高峰期的一个六车道高速公路,一起交通事故使其中一段变成了单车道, 那么这整条高速公路的吞吐就不会超过那条唯一还在正常通车的车道的吞吐。
瓶颈也是持久队列(persistent queues)形成的地方。
对于一条链路,只有当它的离开速率(departure rate)大于到达速率(arrival rate)时,这个队列才会开始收缩。对于一条有多段链路、运行在最大传输速率的连 接(connection),除瓶颈点之外的地方都有更大的离开速率,因此队列会朝着瓶颈 移动(migrate to the bottleneck)。
不管一条连接会经过多少条链路,以及每条链路的速度是多少,从 TCP 的角度来看, 任何一条复杂路径的行为,与 RTT(round-trip time)及瓶颈速率相同的单条链路的行为是一样的。 换句话说,以下两个物理特性决定了传输的性能:
RTprop
(round-trip propagation time):往返传输时间BtlBw
(bottleneck bandwidth):瓶颈带宽如果网络路径是一条物理管道,那 RTprop 就是管道的长度,而 BtlBw 则是管道最窄处的直径。
图 1 展示了 inflight 数据量(已发送但还未被确认的数据量)不断增大时, RTT 和传输速率(delivery rate)的变化情况:
直观上,这张图想解释的是:随着正在传输中的数据的不断增多,传输延迟和传输速率将如何变化。 难点在于二者并没有简单的关系可以表示。不仅如此,后文还将看到二者甚至不可同 时测量 —— 就像量子力学中位置和动量不可同时测量。 译注。
Fig 1. Delivery rate and RTT vs. inflight data
- 蓝线展示 RTprop 限制,
- 绿线展示 BtlBw 限制,
- 红线是瓶颈缓冲区(bottleneck buffer)
横轴表示 inflight 数据量,关键的点有三个,依次为 0 -> BDP -> BDP+BtlneckBuffSize
,
后两个点做垂线,将整个空间分为了三个部分:
(0, BDP)
:这个区间内,应用(客户端)发送的数据并未占满瓶颈带宽(容量),因此称为
应用受限(app limited)区域;(BDP, BDP+BtlneckBuffSize)
:这个区间内,已经达到链路瓶颈容量,但还未超过
瓶颈容量+缓冲区容量,此时应用能发送的数据量主要受带宽限制,
因此称为带宽受限(bandwidth limited)区域;(BDP+BtlneckBuffSize, infinity)
:这个区间内,实际发送速率已经超过瓶颈容量+缓冲区容量
,多出来的数据会被丢弃,缓冲区大小决定了丢包多少,因此称为缓冲区受限(buffer limited)区域。以上三者,也可以更直白地称为应用不足、带宽不足、缓冲区不足区域。 译注。
我们来更具体地看一下传输时延(RTprop)和瓶颈带宽(BtlBw)与与 inflight 数据量呈现以上关系:
inflight 数据量在 0 -> BDP
区间内时,发送的数据还未达到瓶颈容量,此时,
因此,这个阶段的行为由 RTprop 决定。
inflight 数据量刚好等于 BDP
时,
inflight = BtlBw × RTprop
;inflight 大于 BDP
之后,管道就满了(超过瓶颈带宽)
inflight 继续增大,超过 BDP+BtlneckBuffSize
之后,即超过链路瓶颈所支持的最大缓冲区之后,就开始丢包。
灰色区域是不可达的,因为它违反了至少其中一个限制。限制条件的不同导致了三个可行区域 (app-limited, bandwidth-limited, and buffer-limited)各自有不同的行为。
再次给出图 1,
Fig 1. Delivery rate and RTT vs. inflight data
直观上来说, 拥塞(congestion)就是 inflight 数据量持续向右侧偏离 BDP 线的行为, 而拥塞控制(congestion control)就是各种在平均程度上控制这种偏离程度的方案或算法。
基于丢包的拥塞控制工作在 bandwidth-limited 区域的右侧,依靠:
将连接的传输速率维持在全速瓶颈带宽(full bottleneck bandwidth)。
Bandwidth-limited 区域的左侧边界是比右侧更好的一个拥塞控制点。
在 Google,我们团队每天都会花数小时来分析从世界各地收集上来的 TCP 包头, 探究各种异常和反常现象背后的意义。
组合这些测量指标(measurements),再引入一个健壮的伺服系统 (基于控制系统领域的近期进展) 12,我们便得到一个能针对对真实拥塞 —— 而非丢包或延迟 —— 做出反应的 分布式拥塞控制协议,能以很大概率收敛到 Kleinrock 的最优工作点。
我们的三年努力也正是从这里开始:试图基于对如下刻画一条路径的两个参数的测量, 来实现一种拥塞控制机制,
当一个连接满足以下两个条件时,它将运行在最高吞吐和最低延迟状态:
速率平衡(rate balance):瓶颈链路的数据包到达速率刚好等于瓶颈带宽 BtlBw
,
这个条件保证了链路瓶颈已达到 100% 利用率。
管道填满(full pipe):传输中的总数据(inflight)等于 BDP(= BtlBw × RTprop
)。
这个条件保证了有恰好足够的数据,既不会产生瓶颈饥饿(bottleneck starvation), 又不会产生管道溢出(overfill the pipe)。
需要注意,
仅凭 rate balance 一个条件并不能确保没有积压。
例如,某个连接在一个 BDP=5 的链路上,开始时它发送了 10 个包组成的 Initial Window,之后就一直稳定运行在瓶颈速率上。那么,在这个链路此后的行为就是:
类似地,仅凭 full pipe 一个条件也无法确保没有积压。
例如,如果某个连接以 BDP/2
的突发方式发送一个 BDP 的数据,
那仍然能达到瓶颈利用率,但却会产生平均 BDP/4 的瓶颈积压。
在链路瓶颈以及整条路径上最小化积压的唯一方式是同时满足以上两个条件。
下文会提到“无偏估计”这个统计学术语,这里先科普下:
In statistics, the bias (or bias function) of an estimator is the difference between this estimator’s expected value and the true value of the parameter being estimated. An estimator or decision rule with zero bias is called unbiased.
Wikipedia: Bias of an estimator
译注。
BtlBw 和 RTprop 在一个连接的生命周期中是不断变化的,因此必须持续对它们做出估计(estimation)。
TCP 目前跟踪了 RTT(从发送一段数据到这段数据被确认接收的时间) ,因为检测是否有丢包要用到这个参数。在任意时刻 t,
其中
前面已经提到,RTprop
是路径(connection’s path)的一个物理特性,只有当路径发
生变化时它才会变化。由于路径变化的时间尺度远大于 RTProp,因此在时刻 T,一个无偏、高效估计是:
例如,在时间窗口 WR 内的移动最小值(running min),其中典型情况下, WR 的持续时间是几十秒到几分钟。
这里直观上的解释是:一段时间内的最小 RTT(可测量),就是 这条路径(在一段时间内)的往返传输延迟。 译注。
不同于 RTT,TCP 规范中并没有要求跟踪 bottleneck bandwidth,但可以通过 跟踪传输速率(delivery rate)来得到一个对瓶颈带宽的不错估计。
当应答包(ACK)到达发送端时,其中除了包含 RTT 信息,还包含包离开时的 inflight data 传输情况。已传输的数据量除以传输时间,就是发送和应答之间的平均传输速率:
deliveryRate = Δdelivered/Δt
<= bottleneck rate
;deliveryRate <= the true delivery rate
,也就是估计出的传输速率,不会超过真实瓶颈带宽,后者是前者的上限。因此,传输速率在一段时间窗口内的最大值(windowed-max),是 BtlBw 的一个高效、 无偏估计:
其中时间窗口 WB 典型情况下是 6~10 个 RTT。
这里直观上的解释是:一段时间内的链路最大传输带宽(可测量),就是这条链路的瓶颈带宽。 译注。
TCP 记录了每个包的离开时间,再加上已传输的数据量,当每个 ACK 包到达发送端时,将产生:
过滤器可以将这两个测量到的值转换成对 RTprop 和 BtlBw 的估计。
注意,这两个变量是完全独立的:
这种无关性也解释了为什么需要同时知道这两个限制条件,才能确定传输路径的发送行为。
由于 RTprop 只对 BDP 的左侧可见,BtlBw 只对右侧可见,因此它们服从不确定性原理( uncertainty principle):当测量其中一个时,另一个将不可测量。
直观上的解释是:管道整个长度被填满 —— 或称溢出(overfilled)—— 时才能达到它的 最大容量,而溢出之后会(在缓冲区中)创建一个队列(queue),后者作为管道的 延伸又反过来模糊了管道的长度。
例如,
这种内在的不确定性意味着,除了依靠测量值来估计(恢复)出两个路径变量之外, 必须有状态来跟踪:
这里说的“工作位置”,是指图 1 中的 RTProp 和 BtlBw 不同组合产生的位置。
BBR 的核心算法分为两部分:
每个 ACK 都为我们提供了 RTT 和平均传输速率 的一次测量,二者又将分别更新对 RTprop 和 BtlBw 的估计。
具体来说,收到一个 ACK 时,BBR 将执行以下逻辑:
function onAck(packet)
rtt = now - packet.sendtime // 收包时间 减去 包中记录的发包时间
update_min_filter(RTpropFilter, rtt) // 根据 2.2 中的方程,更新对 RTT 的估计
delivered += packet.size
delivered_time = now
delivery_rate = (delivered - packet.delivered) / (delivered_time - packet.delivered_time)
if (delivery_rate > BtlBwFilter.current_max // 实际传输速率已经大于当前估计的瓶颈带宽,或
|| !packet.app_limited) // 不是应用受限(应用受限的样本对估计 BtlBw 无意义)
update_max_filter(BtlBwFilter, delivery_rate) // 根据 2.3 中的方程,更新对 BtlBw 的估计
if (app_limited_until > 0) // 达到瓶颈带宽前,仍然可发送的字节数
app_limited_until = app_limited_until - packet.size
几点说明:
应用受限的包
app_limited
(见下面的 send()
伪代码),
即 packet.app_limited = true
。瓶颈带宽 BtlBw 是传输速率的一个硬性上限,因此
delivery_rate > BtlBwFilter.current_max
,就一定更新 BtlBw 估计;否则,科普下 TCP pacing 这个下文将用到的概念:
Paper: Understanding the Performance of TCP Pacing
TCP’s congestion control mechanisms can lead to bursty traffic flows on modern high-speednetworks, with a negative impact on overall network efficiency. A pro-posed solution to this problem is to evenly space, or “pace”, data sent intothe network over an entire round-trip time, so that data is not sent in aburst. In this paper, we quantitatively evaluate this approach.
译注。
为了让数据包的到达速率(packet-arrival rate)能匹配到 瓶颈链路的离开速率(departure rate), BBR 会对每个数据进行 pace (在每个 RTT 窗口内均匀发送数据)。
有两个控制参数:
pacing_rate
:BBR 的主要控制参数。
要求达到速率必须匹配到瓶颈速率,意味着 pacing 是 BBR 设计以及实际操作中 必不可少的部分。
cwnd_gain
:等于 BDP 乘以一个略大于 1 的系数,
用来容忍常见的网络和接收端异常(pathologies) (见后文 Delayed and Stretched
ACKs 小节)。
TCP 发包时的 BBR 逻辑如下:
function send(packet)
bdp = BtlBwFilter.current_max * RTpropFilter.current_min // 计算 BDP
if (inflight >= cwnd_gain * bdp) // 如果正在传输中的数据量超过了允许的最大值
return // 直接返回,接下来就等下一个 ACK,或者等超时重传
// 能执行到这说明 inflight < cwnd_gain * bdp,即正在传输中的数据量 < 瓶颈容量
if (now >= next_send_time)
packet = nextPacketToSend()
if (!packet) // 如果没有数据要发送
app_limited_until = inflight // 更新 “在达到瓶颈容量之前,仍然可发送的数据量”
return
packet.app_limited = (app_limited_until > 0) // 如果仍然能发送若干字节才会达到瓶颈容量,说明处于 app_limited 状态
packet.sendtime = now
packet.delivered = delivered
packet.delivered_time = delivered_time
ship(packet)
next_send_time = now + packet.size / (pacing_gain * BtlBwFilter.current_max)
timerCallbackAt(send, next_send_time)
在 Linux 实现中,BBR 发送过程会用到高效的 FQ/pacing qdisc4, 这使得 BBR 在多条 Gbps 链路上,单条连接的性能就能达到线速;CPU 开销几乎可以忽略 ,就能处理几千条低速 paced 连接。
BBR 的发送速率和发送数据量只是估计出的 BtlBw
和 RTprop
的一个函数,过滤器(filters)除了要对瓶颈进行估计之外,还要控制对传输路径的适配
(control adaptation)。这产生了如图 2 所示的新的控制循环,
Fig 2. RTT (blue), inflight (green), BtlBw max filter (black) and delivery rate (red) detail
从上到下四条横线:
BtlBw 的估计值(状态)
几点说明:
BBR 会定期使用 pacing_gain > 1
来均分每个 RTprop interval,增大了发送速率和 inflight 数据量。
下面这是如何保持(或达到新的)稳态的。
考虑链路瓶颈带宽 BtlBw 的两种可能情况:
如果 BtlBw 恒定,
deliveryRate
此时还是恒定的;pacing_gain < 1
发送数据,从而会将这个 queue 移除。如果 BtlBw 增大了,
deliveryRate
也会增大,那么新的最大值会立即导致 BtlBw 的估计值变大,从而增大了基础 pacing 速率。图 3 展示了一个原本运行在 10-Mbps/40-ms
flow,链路瓶颈突然翻倍到 20 Mbps,平稳运行 20s 之后
又重新回到 10Mbps 的效果:
Fig 3. Bandwidth change
BBR 是一个 max-plus 控制系统(一种基于
max()
和加法操作的代数)的一个具体使用场景,这是一种基于非标准代数进行控制的新方式12。 这种方式允许 adaptation rate (由 max gain 控制)独立于 queue growth (由 average gain 控制)。 应用到这里,得到的就是一个简单、隐含的控制循环,其中,对物理限制的变化的适应过程, 由代表这些限制的 filters 自动处理。而传统控制系统则需要通过一个复杂状态机连接 起来的多个循环,才能完成同样效果。
现有的实现使用事件相关(event-specific)的算法和代码来处理启动、关闭和丢包恢复等事件。
BBR 使用上一节的两个函数 onAck()
和 send()
处理所有事情,
通过序列化一组“状态”来处理事件,其中“状态”定义为一个 table,其中描述的是一组固定增益和退出条件(fixed gains and exit criteria)。
// include/uapi/linux/inet_diag.h
/* BBR has the following modes for deciding how fast to send: */
enum bbr_mode {
BBR_STARTUP, /* ramp up sending rate rapidly to fill pipe */
BBR_DRAIN, /* drain any queue created during startup */
BBR_PROBE_BW, /* discover, share bw: pace around estimated bw */
BBR_PROBE_RTT, /* cut inflight to min to probe min_rtt */
};
/* The pacing_gain values for the PROBE_BW gain cycle, to discover/share bw: */
static const int bbr_pacing_gain[] = {
BBR_UNIT * 5 / 4, /* probe for more available bw */
BBR_UNIT * 3 / 4, /* drain queue and/or yield bw to other flows */
BBR_UNIT, BBR_UNIT, BBR_UNIT, /* cruise at 1.0*bw to utilize pipe, */
BBR_UNIT, BBR_UNIT, BBR_UNIT /* without creating excess queue... */
};
状态机:
// net/ipv4/tcp_bbr.c
|
V
+---> STARTUP ----+
| | |
| V |
| DRAIN ----+
| | |
| V |
+---> PROBE_BW ----+
| ^ | |
| | | |
| +----+ |
| |
+---- PROBE_RTT <--+
Startup 状态发生在连接启动时;
Drain 状态发生在连接启动时;
大部分时间花在 ProbeBW 状态,具体内容就是前面已经介绍的“稳态行为”收敛过程;
图 4 展示了一个 10-Mbps/40-ms
BBR TCP flow 的前 1 秒,
Fig 4. First second of a 10-Mbps, 40-ms BBR flow.
几点解释:
上半部分图展示的是 BBR 和 CUBIC 连接的 RTT 随时间的变化。注意,这份数据的时 间参考是 ACK 到达(蓝色),因此,while they appear to be time shifted, events are shown at the point where BBR learns of them and acts.
下半部分图中可以看出,BBR 和 CUBIC 的初始行为是类似的,但 BBR 能完全排尽它的 startup queue 而 CUBIC 不能。
CUBIC 算法中没有 inflight 信息,因此它后面的 inflight 还会持续增长(虽然没那么激进),直到 瓶颈 buffer 满了导致丢包,或者接收方的 inflight limit (TCP’s receive window) 达到了。
图 5 是这条 flow 前 8 秒的详情,
Fig 5. First eight seconds of 10-Mbps, 40-ms CUBIC and BBR flows.
图 6 展示了共享同一 100-Mbps/10-ms 瓶颈的多条 BBR flow 各自的吞吐,以及如何收敛到公平份额的:
Fig 6. Throughputs of 5 BBR flows sharing a bottleneck
图中向下的几个三角形,是 connection ProbeRTT states,它们的 self-synchronization 加速了最终收敛。
但如果某些 flow 已经(短时地)导致积压,后来者会过高估计 RTProp,可能一段时间内 的不公平(收敛时间超出几个 ProbeBW 周期)。
大 flow 进入 ProbeRTT 之后会从积压队列中 drain 很多包,因此一些 flows 就能观测 到一个新的 RTprop (new minimum RTT)。这使得它们的 RTprop estimates 在同一时间过 期,因此集中进入 ProbeRTT 状态,这会进一步使积压的数据更快消化,从而使更多 flow 看到新的 RTprop,以此类推。这种分布式协调(distributed coordination)是公平性和稳定性(fairness and stability)的关键。
B4 是 Google 用商用交换机(commodity switches)构建的高速广域网15。
图 7 可以看出,BBR 的吞吐稳定地比 CUBIC 高 2~25 倍,
Fig 7. BBR vs. CUBIC relative throughput improvement.
以我们某个活跃的探测器(prober)服务作为实验对象,该服务会创建到远程数据中心的持久 BBR 和 CUBIC 连接,
然后每分钟传输 8MB 数据。探测器之间会通过北美、欧洲和亚洲的多条跨洲或洲内 B4 路径通信。
但按照我们的理论估计,BBR 的性能应该比这个更好,
这巨大的提升是 BBR 没有将丢包作为拥塞指示器(not using loss as a congestion indicator)的一个直接结果。 为达到全速带宽,现有的基于丢包的拥塞控制,要求丢包率低于 BDP 的 inverse square 17 (e.g., 小于 one loss per 30 million packets for a 10-Gbps/100-ms path)
Figure 8 比较了不同丢包率下的实际吞吐量:
Fig 8. BBR vs. CUBIC goodput for 60-second flows on a 100-Mbps/100-ms link with 0.001 to 50 percent random loss.
最大可能的吞吐是 链路速率乘以 * (1 - lossRate)
,
0.1%
时下降到了原来的 1/10 倍,在丢包率 1% 时完全跌零了。15%
之后才开始急剧下降。CUBIC 的 loss tolerance 是其算法的一个结构特性(structural property), 而 BBR 中它是一个配置参数。当 BBR 的丢包率接近 ProbeBW 峰值增益时, 测量真实 BtlBw 的传输速率的概率将剧烈下降,导致最大值过滤器(the max filter)将 产生过低估计(underestimate)。
BBR 已经部署到了 Google.com 和 YouTube 视频服务器上。我们在小规模灰度,一小部分用户会随机被分到 BBR 或 CUBIC。
图 9 展示了 BBR vs. CUBIC 的中值 RTT 提升,数据来自一个星期内从五个大洲采集的超 过 2 亿条 YouTube playback 连接,
8-114kbps
的 2.5G 网络接入的5,
这会导致很多已知问题,问题的根源在于,基于丢包的拥塞控制有所谓的缓冲区填充倾向
(buffer-filling propensities)3;Fig 9. BBR vs. CUBIC median RTT improvement.
图 10 比较(仿真)了 BBR 和 CUBIC 的 SGSN Internet-to-mobile delay。
Fig 10. Steady-state median RTT variation with link buffer size.
稳态时的 RTT 随链路缓冲区大小的变化,链路物理特性:128-Kbps/40-ms,8 BBR (green) / CUBIC (red) flows。
不管瓶颈缓冲区大小和活跃的 flow 数量如何,BBR 几乎一直将队列(延迟)保持在最小;
CUBIC flow 永远会填满缓冲区, 因此延迟随着 buffer size 线性增长。
几条水平线处表示的含义非常重要:
Linux 的初始超时时间是 1s,见 Customize TCP initial RTO (retransmission timeout) with BPF。 译注。
蜂窝网络会根据每个用户的数据包排队(queue of packets)情况来调整每个用户的带宽 (adapt per-subscriber bandwidth)。
早期版本的 BBR 通过针对性调优来产生非常小的队列,导致在低速情况下连接会卡住(stuck at low rates)。
增大峰值 ProbeBW pacing_gain
来产生更大的队列,会使得卡住的情况大大减少,这暗示
大队列(缓冲区)对某些网络也不是坏事。
使用当前的 1.25 × BtlBwpeak gain,BBR 在任何网络上都不会比 CUBIC 差。
蜂窝、WIFI 和有线宽带网络经常会延迟和聚合 ACK(delay and aggregate) 1。当 inflight 限制到单个 BDP 时,这会导致因为数据太少而导致的卡顿( throughput-reducing stalls)。
将增大 ProbeBW cwnd_gain 增大到 2 使得 BBR 能在估计的传输速率上持续平稳发送,即使 ACK 被延迟了一个 RTT,这显著避免了卡顿(stalls)。
BBR 在 YouTube 部署之后,我们发现世界上大部分 ISP 都会用 TBP 来对接收到的流量进行处理(mangle) 7。
为最小化上游带宽浪费,以及最小化因为丢包而导致的应用延迟增加,我们给 BBR 添加了 policer detection 和一个显式 policer 模型,并还在持续研究更好的方式来减缓 policer 带来的损失。
更多入向流量整形(policing)内容,可参考: (译)《Linux 高级路由与流量控制手册(2012)》第九章:用 tc qdisc 管理 Linux 网络带宽。 译注。
不管是与其他 BBR flow 竞争,还是与基于丢包的拥塞控制竞争, BBR 都能收敛到瓶颈带宽的一个公平份额。
即使基于丢包的拥塞控制填满了可用缓冲区,ProbeBW 仍然能健壮地将 BtlBw estimate 朝着 flow 的公平份额移动,ProbeRTT 也是类似。
但是,不受通信双方控制的路由器缓冲区会比 BDP 大好几倍,导致 long-lived loss-based competitors 占用大量队列,因此获得超出其公平的份额。 如何解决(缓解)这个问题也是目前业内很热门的一个研究方向。
重新思考拥塞控制让我们发掘出了这个古老世界的许多新东西。 相比于使用丢包、缓冲区大小等等这些与拥塞只是弱相关的事件, BBR 以 Kleinrock 关于拥塞的规范化模型和与之相关的最佳工作位置作为出发点。
关键的两个参数 —— 延迟和带宽 —— 无法同时确定, 这一结论看似绝望,但我们发现通过对二者进行顺序估计(estimated sequentially)能绕开这一限制。具体来说,我们使用控制与估计理论领域的一些最新进 展,创建了一个简单的分布式控制循环,后者能接近最优值,在充分利用网络的 同时还能保持一个很小的缓冲队列。
Google BBR 的实现已经合并到 Linux 内核,关于一些实现细节见本文附录。
BBR is deployed on Google’s B4 backbone, improving throughput by orders of magnitude compared with CUBIC. It is also being deployed on Google and YouTube Web servers, substantially reducing latency on all five continents tested to date, most dramatically in developing regions.
BBR 只运行在发送端,无需协议、接收端或网络的改动,因此可以做到 灰度(增量)部署。它只依赖 RTT 和应答包,因此大部分互联网传输协议都能实现这个算法。
The authors are members of Google’s make-tcp-fast project, whose goal is to evolve Internet transport via fundamental research and open source software. Project contributions include TFO (TCP Fast Open), TLP (Tail Loss Probe), RACK loss recovery, fq/pacing, and a large fraction of the git commits to the Linux kernel TCP code for the past five years.
The authors can be contacted at [email protected].
The pacing_gain controls how fast packets are sent relative to BtlBw and is key
to BBR’s ability to learn. A pacing_gain > 1 increases inflight and decreases
packet inter-arrival time, moving the connection to the right on figure 1. A
pacing_gain < 1
has the opposite effect, moving the connection to the left.
BBR uses this pacing_gain to implement a simple sequential probing state machine that alternates between testing for higher bandwidths and then testing for lower round-trip times. (It’s not necessary to probe for less bandwidth since that is handled automatically by the BtlBw max filter: new measurements reflect the drop, so BtlBw will correct itself as soon as the last old measurement times out of the filter. The RTprop min filter automatically handles path length increases similarly.)
If the bottleneck bandwidth increases, BBR must send faster to discover this. Likewise, if the actual round-trip propagation delay changes, this changes the BDP, and thus BBR must send slower to get inflight below BDP in order to measure the new RTprop. Thus, the only way to discover these changes is to run experiments, sending faster to check for BtlBw increases or sending slower to check for RTprop decreases. The frequency, magnitude, duration, and structure of these experiments differ depending on what’s already known (startup or steady-state) and sending app behavior (intermittent or continuous).
BBR flows spend the vast majority of their time in ProbeBW state, probing for bandwidth using an approach called gain cycling, which helps BBR flows reach high throughput, low queuing delay, and convergence to a fair share of bandwidth. With gain cycling, BBR cycles through a sequence of values for the pacing_gain. It uses an eight-phase cycle with the following pacing_gain values: 5/4, 3/4, 1, 1, 1, 1, 1, 1. Each phase normally lasts for the estimated RTprop. This design allows the gain cycle first to probe for more bandwidth with a pacing_gain above 1.0, then drain any resulting queue with a pacing_gain an equal distance below 1.0, and then cruise with a short queue using a pacing_gain of 1.0. The average gain across all phases is 1.0 because ProbeBW aims for its average pacing rate to equal the available bandwidth and thus maintain high utilization, while maintaining a small, well-bounded queue. Note that while gain cycling varies the pacing_gain value, the cwnd_gain stays constant at two, since delayed and stretched acks can strike at any time (see the section on Delayed and Stretched Acks).
Furthermore, to improve mixing and fairness, and to reduce queues when multiple BBR flows share a bottleneck, BBR randomizes the phases of ProbeBW gain cycling by randomly picking an initial phase—from among all but the 3/4 phase—when entering ProbeBW. Why not start cycling with 3/4? The main advantage of the 3/4 pacing_gain is to drain any queue that can be created by running a 5/4 pacing_gain when the pipe is already full. When exiting Drain or ProbeRTT and entering ProbeBW, there is no queue to drain, so the 3/4 gain does not provide that advantage. Using 3/4 in those contexts only has a cost: a link utilization for that round of 3/4 instead of 1. Since starting with 3/4 would have a cost but no benefit, and since entering ProbeBW happens at the start of any connection long enough to have a Drain, BBR uses this small optimization.
BBR flows cooperate to periodically drain the bottleneck queue using a state called ProbeRTT. In any state other than ProbeRTT itself, if the RTProp estimate has not been updated (i.e., by getting a lower RTT measurement) for more than 10 seconds, then BBR enters ProbeRTT and reduces the cwnd to a very small value (four packets). After maintaining this minimum number of packets in flight for at least 200 ms and one round trip, BBR leaves ProbeRTT and transitions to either Startup or ProbeBW, depending on whether it estimates the pipe was filled already.
BBR was designed to spend the vast majority of its time (about 98 percent) in ProbeBW and the rest in ProbeRTT, based on a set of tradeoffs. ProbeRTT lasts long enough (at least 200 ms) to allow flows with different RTTs to have overlapping ProbeRTT states, while still being short enough to bound the performance penalty of ProbeRTT’s cwnd capping to roughly 2 percent (200 ms/10 seconds). The RTprop filter window (10 seconds) is short enough to allow quick convergence if traffic levels or routes change, but long enough so that interactive applications (e.g., Web pages, remote procedure calls, video chunks) often have natural silences or low-rate periods within the window where the flow’s rate is low enough or long enough to drain its queue in the bottleneck. Then the RTprop filter opportunistically picks up these RTprop measurements, and RTProp refreshes without requiring ProbeRTT. This way, flows typically need only pay the 2 percent penalty if there are multiple bulk flows busy sending over the entire RTProp window.
When a BBR flow starts up, it performs its first (and most rapid) sequential probe/drain process. Network-link bandwidths span a range of 1012— from a few bits to 100 gigabits per second. To learn BtlBw, given this huge range to explore, BBR does a binary search of the rate space. This finds BtlBw very quickly (log2BDP round trips) but at the expense of creating a 2BDP queue on the final step of the search. BBR’s Startup state does this search and then the Drain state drains the resulting queue.
First, Startup grows the sending rate exponentially, doubling it each round. To achieve this rapid probing in the smoothest possible fashion, in Startup the pacing_gain and cwnd_gain are set to 2/ln2, the minimum value that will allow the sending rate to double each round. Once the pipe is full, the cwnd_gain bounds the queue to (cwnd_gain - 1) × BDP.
During Startup, BBR estimates whether the pipe is full by looking for a plateau in the BtlBw estimate. If it notices that there are several (three) rounds where attempts to double the delivery rate actually result in little increase (less than 25 percent), then it estimates that it has reached BtlBw and exits Startup and enters Drain. BBR waits three rounds in order to have solid evidence that the sender is not detecting a delivery-rate plateau that was temporarily imposed by the receive window. Allowing three rounds provides time for the receiver’s receive-window autotuning to open up the receive window and for the BBR sender to realize that BtlBw should be higher: in the first round the receive-window autotuning algorithm grows the receive window; in the second round the sender fills the higher receive window; in the third round the sender gets higher delivery-rate samples. This three-round threshold was validated by YouTube experimental data.
In Drain, BBR aims to quickly drain any queue created in Startup by switching to a pacing_gain that is the inverse of the value used during Startup, which drains the queue in one round. When the number of packets in flight matches the estimated BDP, meaning BBR estimates that the queue has been fully drained but the pipe is still full, then BBR leaves Drain and enters ProbeBW.
Note that BBR’s Startup and CUBIC’s slow start both explore the bottleneck capacity exponentially, doubling their sending rate each round; they differ in major ways, however. First, BBR is more robust in discovering available bandwidth, since it does not exit the search upon packet loss or (as in CUBIC’s Hystart10) delay increases. Second, BBR smoothly accelerates its sending rate, while within every round CUBIC (even with pacing) sends a burst of packets and then imposes a gap of silence. Figure 4 demonstrates the number of packets in flight and the RTT observed on each acknowledgment for BBR and CUBIC.
The network path and traffic traveling over it can make sudden dramatic changes. To adapt to these smoothly and robustly, and reduce packet losses in such cases, BBR uses a number of strategies to implement the core model. First, BBR treats cwnd_gain×BDP as a target that the current cwnd approaches cautiously from below, increasing cwnd by no more than the amount of data acknowledged at any time. Second, upon a retransmission timeout, meaning the sender thinks all in-flight packets are lost, BBR conservatively reduces cwnd to one packet and sends a single packet (just like loss-based congestion-control algorithms such as CUBIC). Finally, when the sender detects packet loss but there are still packets in flight, on the first round of the loss-repair process BBR temporarily reduces the sending rate to match the current delivery rate; on second and later rounds of loss repair it ensures the sending rate never exceeds twice the current delivery rate. This significantly reduces transient losses when BBR encounters policers or competes with other flows on a BDP-scale buffer.