BBR: Congestion-Based Congestion Control 论文阅读

BBR = Bottleneck Bandwidth and Round-trip propagation time

前置内容

propagation:传播,Round-trip propagation time 是往返传播时间,也就是指在一个空网络上端到端传输一个包的时延。这和 RTT 有很大区别,因为 RTT 是一个测量值,但是 RTprop 是理论值。

probe:探查,探测,后面还有什么 probe_bw,指的是探测到的带宽。

jitter:抖动。

in flight:传输中的数据量,已发送但尚未 ACK 的数据,可以理解为在管道内的数据。

bufferbloat:缓存膨胀。指过度缓存包导致包交换网络上高延迟和抖动。缓存太深,导致拥塞了没办法快速丢包,等到满了才丢,又容易引起更大的拥塞,如果那边拥塞缓解了,缓存内的数据包也等了很长时间,延迟增加。可以把缓存满了之后丢包看成缓解时间趋于正无穷。

目前 CC 的类型:

  • loss based:基于丢包
  • delay based:基于延迟
  • ECN based:基于 ECN,在路由器上产生拥塞通知
  • AIMD based:基于 AIMD,其实大伙有点 AIMD,但是真 AIMD 还得看 Reno Cubic 这些

BBR 不属于上述任何一类,这是演讲 PPT 里自己说的。

AIMD 是公平的,参考 TCP AIMD 为什么收敛。十分的控制论,这里面的证明是有道理的,但是感觉不是很数学。

pace:定速,这是 Network Algorithmics 导师钦定的。其实就是 TCP Pacing,给发包确定一个速度,而不是当突发的时候把包全塞上链路。

Abstract

目前的网络并没有按它应有的那样传输数据。蜂窝网络用户延迟高,机场和会议厅的公共 WiFi 情况更糟。物理和气象研究者需要在全球范围内传输 PB 级数据,但是发现在几 Gbps 的基础设施上传输速率也就几 Mbps。

连一刻都没有为蜂窝网络和公共 WiFi 的高延迟哀悼,立刻赶到战场的是大文件传输低吞吐量。

这些问题来自 80 年代设计 TCP CC 的时候把丢包理解为拥塞。当时这种等价是正确的,这是由于技术限制,而不是因为这是第一性的。现在网卡已经到了 Gbps 级,存储芯片也从 KB 级发展到了 GB 级,丢包和拥塞之间的关系就很弱了。

说到底什么是拥塞,是指链路运输的数据量超过了它的承载能力,导致 QoS 下降。那根据这个定义,拥塞了不一定丢包,考虑一个水库,虽然已经超出了承载能力,但被 buffer 拦下来了,不一定直接在路由器或者主机上丢了。丢包也不一定拥塞,比如无线网络上包传输坏了就导致丢包。但是当时技术不支持大 buffer,也没有无线网络,所以采取了这一等价。当然上面总结的也不一定全面,但是确实有这一些原因。它是一个正确的定义,但现在来看不是一个容易实现控制的定义。怎么监控一个链路的承载能力,主动监控别想了,被动监控难度也比较高。

现在的 TCP 基于丢包的 CC 是上述现实问题的主要原因。当瓶颈处的 buffer 大时,基于丢包的 CC 会让 buffer 保持满的状态,这样就 bufferbloat 了。当瓶颈处的 buffer 小时,基于丢包的 CC 会把丢包当做拥塞信号,导致吞吐量低。

丢包未必拥塞,拥塞也未必丢包。

Congestion And Bottlenecks

在任何时刻,一条全双工 TCP 连接在每个方向恰好只有一条最慢的链路或者瓶颈。瓶颈很重要,因为:

  • 它决定了连接的最大数据传输速率,这是不可压缩流的一般特性。比如考虑一个高峰期的六车道高速路,因为有事故导致一条车道的某一小段过不了车,那么事故上游的车流速度不超过通过该车道的车流速度。
  • 这是形成持久队列的地方。队列长度只会在发送速率超过接收速率时变小。对于以最大传输速率传输的连接,瓶颈链路上游的所有链路都具有更快的发送速率,因此其队列会迁移到瓶颈链路。

意思应该是只有瓶颈链路速率低于其他链路,发送速率小于接收速率,这样队列就会堆积,当然,数据包是守恒的,所以才导致迁移到瓶颈链路。这段写得不明不白的,感觉上意思应该是这样。

无论连接经过多少条链路或它们各自的速度如何,从 TCP 的角度来看,任意的复杂路径都表现为具有与复杂路径相同 RTT 和瓶颈速率的单一链路。

就是 TCP 是一个端到端协议,网络是黑盒,屏蔽掉网络部分,端上只知道有一条路。

RTprop(往返传播时间)和 BtlBw(瓶颈带宽)这两个物理约束条件限制了传输性能。如果网络路径是一条物理管道,RTprop 就是它的长度,BtlBw 就是它的最小直径。

下图显示了 RTT 和传输速率随传输中的数据量(已发送但尚未确认的数据)的变化情况。蓝线表示 RTprop 限制,绿线表示 BtlBw 限制,红线表示瓶颈缓冲区。在阴影区域内运行是不可能的,因为至少会违反一个约束条件。约束条件之间的转换会产生三个不同的区域(应用限制、带宽限制和缓冲区限制),其行为也有本质区别。

Figure 1

图中左边是最佳运行状态,右边是基于丢包的 CC 的操作位置。好像皮鞋哥把左边叫最佳操作点来着。这张图要分段来看,没 hit 到 BDP 的时候说明管道没满,这种情况下怎么传 RTT 都是 RTprop,但是由于刚好达到 BDP 时带宽是 BltBw,所以斜率是那样。这里要复习 BDP 定义,带宽乘时延。管道满了之后带宽保持 BltBw,因为还要增加 inflight,也就是类比速度距离公式里速度不变,距离增加,那么时间就增加了,这里时间增加的具体原因在于路由器上排队。之后就是把缓存爆了之后的事情,这时候实际上不知道怎么变化,是根据策略的,比如随机丢包,权重丢包啥的。

当传输中的数据不足以填满管道时,RTprop 决定行为;否则,BtlBw 占主导地位。这两条线相交于 inflight = BtlBw × RTprop,即管道的 BDP。由于过了这一点,管道就满了,超出的 inflight - BDP 会在瓶颈处产生一个队列,从而导致上图所示的 RTT 与 inflight 数据的线性关系。当过量超过缓冲区容量时就会丢包。拥塞只是在 BDP 线右侧持续运行的状态,而拥塞控制则是对连接状态平均向右的程度进行约束的方案。

基于丢包的拥塞控制在带宽受限区域的右边缘运行,以高延迟和频繁丢包为代价提供全部瓶颈带宽。当内存价格高昂时,缓冲区的大小仅略大于 BDP,这就最大限度地减少了基于丢包的拥塞控制所带来的过多延迟。随后内存价格的下降导致缓冲区的大小远远大于 ISP 链路的 BDP,由此产生的缓存膨胀导致 RTT 从几毫秒增加到几秒。

这里可以看出为什么在当年基于丢包的 CC 可以有一个好的效果。有个十分激进的方案,直接把路由器的缓存砍了,路由器只负责转发,出现排队就丢包,这样我们 back to CSMA/CD 了,可以参考 hostCC。它是一个好的方案吗?不知道。

带宽受限区域的左边缘是一个比右边更好的运行点。1979 年,Leonard Kleinrock 证明了这一运行点是最佳的,它能最大限度地提高传输带宽,同时最大限度地降低延迟和丢包,无论是对单个连接还是对整个网络而言都是如此。不幸的是,大约在同一时期,Jeffrey M. Jaffe 证明了不可能找到一个收敛到这一运行点的分布式算法。这一结果改变了研究方向,从寻找能达到 Kleinrock 最佳运行点的分布式算法,转向研究不同的拥塞控制方法。

Jaffe 的论文是 1981 年的那篇,之前应该引述过。

我们在谷歌的小组每天都要花费数小时来监测来自世界各地的 TCP 数据包头,找出行为异常和出现原因。我们通常的第一步是找到基本路径特征 RTprop 和 BtlBw。从包跟踪中可以推断出这些特征,这表明 Jaffe 的结果可能并不像它曾经看起来那样具有局限性。他的结果基于基本的测量不确定性(例如,测量到的 RTT 增加是由路径长度变化、瓶颈带宽减少还是由另一个连接的流量造成的队列延迟增加引起的)。虽然不可能消除任何单一测量结果的模糊,但连接在一段时间内的行为却能说明更多问题,这表明采用旨在解决模糊的测量策略是有可能的。

总之就是要采用测量的方法,但是广域网上测量也是有难度的,测数据量倒是没问题,主要是时间上大 jitter 怎么做平均,平均得不好这个 jitter 直接影响效果。虽然也有很多平均方法,但是在选择有代表性的数据上平均法做得确实不行,因为平均的方式要一个连续采样的话,并不是有代表性的数据总是连续的;如果不是一个连续采样,什么样的数据是有代表性的,可能这里可以学习一下?

利用最新的控制系统技术,将这些测量结果与稳健的服务环路相结合,就能产生一种分布式拥塞控制协议,它能对实际拥塞情况(而非数据包丢失或瞬时队列延迟)做出反应,并极有可能收敛到 Kleinrock 的最佳运行点。就这样,我们开始了长达三年的探索,通过测量路径的两个特征参数:瓶颈带宽和往返传播时间来建立拥塞控制方案(或称 BBR)。

Characterizing The Bottleneck

  • (流量平衡)瓶颈包到达速率等于 BltBw;
  • (管道充满)传输中数据总量等于 BDP。

时,一条连接以最高吞吐和最低时延传输数据。

也就是达到最佳运行点。

第一个条件保证了瓶颈处可以达到 100% 的带宽利用率。第二个可以保证有足够的数据,不会使瓶颈饥饿,并且也不会过度填充管道。流量平衡本身并不能确保不存在排队,只能确保队列的大小不会改变(例如,如果连接开始时将 10 个数据包的初始窗口发送到 5 个数据包的 BDP 中,然后以瓶颈速率运行,10 个初始数据包中的 5 个会填满管道,因此多余的数据包会在瓶颈处形成一个无法消散的队列)。同样,管道充满也不能保证没有队列(例如,一个连接以 BDP/2 的突发量发送 BDP,瓶颈带宽利用率会达到 100%,但平均队列长度为 BDP/4)。只有同时满足这两个条件,才能最大限度地减少瓶颈处和整个路径上的队列长度。

没整明白为啥平均队列长度有 1/4 BDP,但是突发确实会产生队列。

BtlBw 和 RTprop 在连接的整个过程中都会发生变化,因此必须不断对它们进行估算。TCP 目前会跟踪 RTT(从发送数据包到确认数据包的时间间隔),因为它是检测丢包所必需的。在任何时间 ,满足

其中 表示由路径上的队列、接收方的延迟 ack 策略、ack 聚合等引入的「噪声」。RTprop 是一条链路的物理属性,并且只当链路改变时改变。因为链路改变的时间尺度远大于 RTprop,一个在时间 对 RTprop 的无偏且有效的估计为

即,在时间窗口 上取最小值(通常是几十秒到几分钟)。

与 RTT 不同的是,TCP 规范中并没有要求实现者跟踪瓶颈带宽,但跟踪传输速率可以得出一个很好的估计。当某个数据包的 ack 返回发送方时,它将传递该数据包的 RTT 和该数据包出发时的 inflight。收发两端之间的平均传输率是传输的数据与经过的时间之比:

这个比率一定小于等于瓶颈传输率(到达量是完全已知的,因此所有的不确定性都在 ,它一定大于等于真正的到达间隔;因此,比率一定小于等于真正的传输率,而传输率又受瓶颈容量的上限限制)。因此,传输率的窗口最大值是对 BtlBw 有效的无偏估计:

考虑一条空且充分大的链路,两个包 A 和 B 相隔一段时间发送,那么 A 和 B 到达接收端的时间差应该等于发送端收到两个 ACK 的时间差,那么加上其他流对链路的占用,收到 ACK 的时间差就会大于接收端收到包的时间差。

其中时间窗口 通常是 6 到 10 个 RTT。

TCP 必须记录每个数据包的出发时间,以计算 RTT。BBR 还会记录总的传输数据量,因此每次到达的 ack 都会产生一个 RTT 和一个传输速率测量值,过滤器会将其转换为 RTprop 和 BtlBw 估计值。

注意这些值是互相独立的:RTprop 会变化,但是瓶颈是一样的(例如,改变路由),或者 BltBw 可以在路径不变的情况下改变(例如,当无线链路的速率改变时)。这种独立性就是为什么必须知道这两个约束条件,才能将发送行为与传送路径相匹配的原因。由于 RTprop 只在图中 BDP 的左侧可见,而 BtlBw 只在图的右侧可见,因此它们遵循不确定性原理:只要其中一个可以测量,另一个就不能测量。直观地说,这是因为管道必须过满才能测出其容量,而过满会产生一个队列,从而掩盖了管道的长度。

看到不确定性原理蚌埠住了,意思是那个意思,但不是物理学上的那个。抛开这点,这段的表述是十分正确的。想要测量管道容量必须要把它灌到溢出,你才能知道它是满的,这是显而易见的道理。

例如,运行请求响应协议的应用程序可能永远不会发送足够的数据来填满管道并观察 BtlBw。长时间批量数据传输可能在带宽受限区域内度过整个生命周期,并且只有来自第一个数据包 RTT 的单个 RTprop 样本。

带宽受限区域是中间的部分。带宽达到最大,但是 RTT 也随 inflight 增大而增大。

这种内在的不确定性意味着,除了恢复两个路径参数的估算器之外,还必须有一些状态可以跟踪当前运行点可以学习到什么,以及当信息变得陈旧时,如何到达可以重新学习的运行点。

不知道这段他想说啥。

Matching The Packet Flow To The Delivery Path

BBR 算法的核心有两部分。

当收到一个 ACK 时

每次 ACK 都会提供新的 RTT 和传输速率测量值,从而更新 RTprop 和 BtlBw 估计值:

1
2
3
4
5
6
7
8
9
10
11
12
13
function onAck(packet)
rtt = now - packet.sendtime
update_min_filter(RTpropFilter, rtt)
delivered += packet.size
delivered_time = now
deliveryRate = (delivered - packet.delivered)
/ (now - packet.delivered_time)
if (deliveryRate > BtlBwFilter.currentMax
|| ! packet.app_limited)
update_max_filter(BtlBwFilter,
deliveryRate)
if (app_limited_until > 0)
app_limited_until -= packet.size

if 检查解决了上一段中描述的不确定性问题:发送方可能是应用限制的,这意味着应用无法让数据填满网络。由于请求响应流量的存在,这种情况很常见。当有发送机会但没有数据要发送时,BBR 会将相应的带宽样本标记为应用限制(见后面的 send() 的伪代码)。这里的代码会决定将哪些样本纳入带宽模型,以便反映网络而非应用程序的限制。BtlBw 是传输速率的硬性上限,因此测量到的传输速率如果大于当前的 BtlBw 估计值,就意味着估计值过低,无论样本是否受应用程序限制。否则,应用受限的样本将被丢弃。图中显示,在应用受限区域,deliveryRate 会低估 BtlBw。这些检查可防止 BtlBw 过滤器被低估的数据填满,从而导致数据发送过慢。

这段伪代码首先计算 rtt 并更新 filter,没啥问题都这么算的,delivered 是已 ACK 的数据量,这个统计也没啥问题,delivered_time 是接到 ACK 的时间,packet.deliveredpacket.delivered_time 是包发出的时候的两个变量的值,可以看 send() 部分,这里两个 delivered_time 的差就是两次 ACK 之间的时间差。就是说我们要用 ACK 的数据来推断目前链路情况,两次 ACK 之间统计的传输数据量是准确的。

当数据包被发送时

为了使数据包到达速率与瓶颈链路的出发速率相匹配,BBR 对每个数据包都进行速度调整。BBR 必须与瓶颈速率相匹配,这意味着 pace 是设计的组成部分,也是运行的基础——pacing_rate 是 BBR 的主要控制参数。一个次要参数 cwnd_gain,将 inflight 限制在 BDP 的一个小倍数内,以处理常见的网络和接收器异常现象(参见后面关于 Delayed ACK 和 Stretched ACK 的章节)。从概念上讲,TCP 发送例程类似于下面的代码。(在 Linux 中,发送使用高效的公平队列 pacing 规则,它能在千兆链路上提供 BBR 线速的单连接性能,并能以可忽略不计的 CPU 开销处理数千个较低速率连接)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function send(packet)
bdp = BtlBwFilter.currentMax
* RTpropFilter.currentMin
if (inflight >= cwnd_gain * bdp)
// wait for ack or timeout
return
if (now >= nextSendTime)
packet = nextPacketToSend()
if (! packet)
app_limited_until = inflight
return
packet.app_limited =
(app_limited_until > 0)
packet.sendtime = now
packet.delivered = delivered
packet.delivered_time = delivered_time
ship(packet)
nextSendTime = now + packet.size /
(pacing_gain *
BtlBwFilter.currentMax)
timerCallbackAt(send, nextSendTime)

cwnd_gainpacing_gain 是可调参数,nextSendTime 中根据增加后的瓶颈带宽算下一次发送包的时间,也就是说我们把这个包传完了再传下一个。

稳态行为

BBR 发送的速率和总量完全是估计的 BtlBw 和 RTprop 的函数,因此 filter 除了估计瓶颈限制外,还控制自适应。这就形成了下图所示的新型控制回路,图中显示了 10Mbps 40 毫秒流量在 700 毫秒内的 RTT(蓝色)、inflight(绿色)和传输速率(红色)细节。

Figure 2

传输速率数据上方的灰色粗线是 BtlBw 最大值 filter 的状态。三角形结构是 BBR 循环 pacing_gain 以确定 BtlBw 是否增加的结果。循环的每个部分所使用的 gain 与其影响的数据在时间上保持一致。gain 在数据发送时提前一个 RTT 应用。事件序列描述中左上方的水平点动表示了这一点。

BBR 将大部分时间用于在 BltBw 估计中 pace 的一个 inflight BDP,从而将延迟降到最低。

没明白他想说啥。

这就把瓶颈转移到了发送端,使其无法观察到 BtlBw 的增加。因此,BBR 会周期性地以 pacing_gain > 1 的速度花费一个 RTprop 时间间隔,从而提高发送速率和 inflight。如果 BtlBw 没有变化,则会在瓶颈处出现一个队列,增加 RTT,从而保持 deliveryRate 不变。(在下一次 RTprop 中,通过补偿 pacing_gain < 1 发送,队列将消失)。如果 BtlBw 增加,则 deliveryRate 增加,新的最大值会立即增加 BtlBw filter 的输出,从而提高 pacing rate。因此,BBR 会以指数级的速度收敛到新的瓶颈速率。下图显示了 BtlBw 在稳定运行 20 秒后突然翻倍至 20 Mbps,然后在以 20 Mbps 稳定运行 20 秒后又降至 10 Mbps 对 10 Mbps,40 毫秒流量的影响。

Figure 3

BBR 是 Max-plus 控制系统的一个简单实例,这是一种基于非标准代数的新控制方法。这种方法允许适应率(由 gain 的最大值控制)与队列增长(由 gain 的平均值控制)无关。应用于这一问题时,它能产生一个简单的隐式控制回路,由代表这些约束条件的 filter 自动处理对物理约束条件变化的适应。而传统的控制系统则需要由复杂的状态机连接多个回路,才能达到同样的效果。

Max plus algebra 是一种特殊的代数,定义 。这种控制方式网上感觉没一点资料,就是这种方式有啥特性啥的完全不懂。

BBR 流量的绝大部分时间都处于 ProbeBW 状态,使用一种称为 gain 循环的方法探测带宽,这有助于 BBR 流量达到高吞吐量、低队列延迟,并收敛到公平的带宽份额。BBR 会循环使用一系列 pacing_gain 值。它采用八阶段循环,步长增益值如下:。每个阶段通常持续估计的 RTprop 时间。这种设计允许 gain 的时候首先以高于 1.0 的 pacing_gain 探测更多带宽,然后以低于 1.0 的且和高 gain 相等距离的 pacing_gain 排空产生的队列,最后以 1.0 的 pacing_gain 在短队列的情况下保持状态。所有阶段的平均 gain 是 1.0,因为 ProbeBW 的目标是使其平均 pacing rate 等于可用带宽,从而保持较高的利用率,同时维持一个小的、上界确定的队列。请注意,虽然 gain cycle 会改变 pacing_gain 的值,但 cwnd_gain 却始终保持为 ,因为 Delayed ACK 和 Stretched ACK 随时可能发生(请参阅「Delayed ACK 和 Stretched ACK」部分)。

稳态里仍然保持 cwnd_gain 吗,感觉有点类似于拥塞避免阶段让 cwnd 增大,是一种 AI。

此外,为了提高混合性和公平性,并在多个 BBR 流量共享一个瓶颈时减少队列长度,BBR 对 ProbeBW gain cycle 的阶段进行了随机化,即在进入 ProbeBW 时,从所有阶段(3/4 阶段除外)中随机选择一个初始阶段。为什么不从 3/4 开始循环?3/4 步进增益的主要优势在于,当管道已满时,可以通过 3/4 gain 来排空 5/4 gain 时产生的队列。在退出 Drain 或 ProbeRTT 并进入 ProbeBW 时,没有队列需要排空,因此 3/4 的 gain 不具备这种优势。在这些情况下使用 3/4 需要付出代价:该轮的链路利用率为 3/4,而不是 1。由于从 3/4 开始会有代价,但没有好处,而且进入 ProbeBW 发生在任何连接的开始阶段,连接时间长到足以有一个 Drain,因此 BBR 使用了这个小优化。

也就是说永远不提前 Drain,一定是先 5/4 再 3/4,然后随机可以保证连接大概率不会同时 5/4 把链路塞爆。下面会说明 BBR 是对 BBR 流多流公平的。

BBR 流量通过合作,使用一种称为 ProbeRTT 的状态定期排空瓶颈队列。在除 ProbeRTT 本身之外的任何状态下,如果 RTProp 估计值没有更新(即通过获得更低的 RTT 测量值)的时间超过 10 秒,那么 BBR 就会进入 ProbeRTT 并将 cwnd 减少到很小的值(4 个数据包)。在保持这一最小 inflight 至少 200 毫秒并往返一次后,BBR 将离开 ProbeRTT,并根据其估计管道是否已被填满,过渡到 Startup 或 ProbeBW。

基于一系列权衡,BBR 被设计为将绝大部分时间(约 98%)用于 ProbeBW,其余时间用于 ProbeRTT。ProbeRTT 持续时间足够长(至少 200 毫秒),允许不同 RTT 的流量有重叠的 ProbeRTT 状态,同时又足够短,可将 ProbeRTT 的 cwnd 上限性能损失控制在大约 2%(200 毫秒/10 秒)。RTprop 过滤器的窗口(10 秒)足够短,以便在流量水平或路由发生变化时快速收敛,但也足够长,以便交互式应用(如网页、RPC、视频块)在窗口内经常出现自然静默或低速率时段,此时流量速率足够低或足够长,足以耗尽瓶颈中的队列。然后,RTprop 过滤器就会适时地采集这些 RTprop 测量值,RTProp 就会刷新,而不需要 ProbeRTT。这样,如果有多个大流量在整个 RTProp 窗口内忙于发送,流量通常只在 2% 的时间带宽受影响。

Appendix - A State Machine For Sequential Probing

pacing_gain 控制着相对于 BtlBw 发送数据包的速度,是 BBR 学习能力的关键。pacing_gain > 1 会增加 inflight,减少数据包到达时间,使连接向图 1 右侧移动。pacing_gain < 1 则效果相反,连接会向左移动。

BBR 利用 pacing_gain 实现了一个简单的顺序探测状态机,它交替测试较高的带宽,然后测试较低的往返时间。由于 BtlBw 最大值 filter 会自动处理带宽减少的问题,因此没有必要探测带宽减少的情况:新的测量结果反映了带宽减少的情况,因此只要最后一次旧的测量结果超出过滤器的时间,BtlBw 就会进行自我修正。RTprop 最小值 filter 也会自动处理路径长度的增加(类似情况)。

因为 filter 全都是带过期时间的,所以可以自动处理。

如果 BtlBw 增加,BBR 必须加快发送速度才能发现这一点。同样,如果实际 RTprop 发生变化,BDP 也会随之变化,因此,BBR 必须降低发送速率,使 inflight 低于 BDP,以便测量新的 RTprop。因此,发现这些变化的唯一方法就是进行实验,加快发送以检查 BtlBw 是否增加,或减慢发送以检查 RTprop 是否减少。这些实验的频率、幅度、持续时间和结构因已知情况(启动或稳态)和发送应用程序行为(间歇或连续)而异。

也就是说,如何确定变化,是通过 pacing 来确定的。但是这样实验不会导致尖刺和抖动吗?倒是改变路由路径和 BltBw 是可能次数很少的,这种测试可能大部分时间是不值得的,但是为了适应改变,就需要每隔一段时间进行测试,产生尖刺。感觉就十分没有必要。然后这一节最后都没说这个状态机是啥。

Single BBR Flow Startup Behavior

现有的实现方法通过特定事件算法和多行代码来处理启动、关闭和丢包恢复等事件。BBR 使用前面(上一节)详述的代码来处理所有事件,通过一组「状态」序列来处理事件,这些「状态」由包含一个或多个固定 gain 和退出标准的表来定义。大部分时间处于「稳态行为」一节所述的 ProbeBW 状态。Startup 和 Drain 状态用于连接启动。为了处理跨越 12 个数量级的互联网链路带宽,Startup 通过使用 的 gain 来实现 BtlBw 的二分搜索,从而在传输速率增加的同时将发送速率提高一倍。

这将在 的 RTT 内发现 BtlBw,但在此过程中会产生 2BDP 大小的队列。一旦 Startup 发现了 BtlBw,BBR 就会转换到 Drain,后者使用 Startup gain 的倒数来处理多余队列,一旦 inflight 的队列下降到 BDP,BBR 就会转换到 ProbeBW。

突然就开始上强度了, 这个 gain 没有解释原因。如果使用最基础的二分方法的话确实是对数的,但是它用了一个不是 的 gain,后面产生的 2BDP 的队列也没有证明,不太清楚为啥是这样的。

下图显示了 10Mbps 40 毫秒 BBR 流量的第一秒,展示了发送方(绿色)和接收方(蓝色)的进度与时间的关系。红线显示的是相同条件下的 CUBIC 发送端。灰色垂直线表示 BBR 状态转换。图的下方显示了两个连接的 RTT 与时间的关系。请注意,这些数据的时间参考是 ACK 到达时间(蓝色),因此,虽然它们看起来有时间偏移,但事件显示的时间点是 BBR 了解到这些事件并采取行动的时间点。

Figure 4

上图下半部分是 BBR 和 CUBIC 的对比图。它们的初始行为相似,但 BBR 能完全耗尽 Startup 过程中产生的队列,而 CUBIC 却不能。由于没有路径模型来告诉 CUBIC 多少 inflight 是多余的,CUBIC 会降低 inflight 增长的强度,但增长会一直持续,直到瓶颈缓冲区填满并丢弃一个数据包,或者达到接收方的 inflight 限制(TCP 的接收窗口)。

下图显示了上图所示连接的前八秒内的 RTT 行为。CUBIC(红色)填满了可用缓冲区,然后每隔几秒从 70% 满到 100% 满循环一次。启动后,BBR(绿色)运行时基本上没有队列。

Figure 5

这里似乎体现了 BBR 重点解决的是时延问题,因为 CUBIC 的这个时延实属有点看不下去,从操作点位置看,BBR 和 CUBIC 操作点的区别就在于 RTprop 的位置。

当 BBR 流量启动时,它会执行第一个(也是最快速的)顺序 Probe/Drain 过程。网络链路带宽的范围从几 bps 到 100Gbps 不等,跨越 12 个数量级。为了学习 BtlBw,BBR 在这一巨大的探索范围内对速率空间进行二分搜索。这样可以很快找到 BtlBw(在 次往返时间下),但代价是在搜索的最后一步会创建一个长 2BDP 的队列。BBR 的 Startup 状态会进行这样的搜索,然后 Drain 会将搜索产生的队列排空。

首先,Startup 会以指数速率提高发送速率,每轮加倍。为了以最平滑的方式实现这种快速探测,在启动过程中,pacing_gaincwnd_gain 被设置为 ,即允许发送速率每轮翻倍的最小值。一旦管道已满,cwnd_gain 会将队列限制为 (cwnd_gain - 1) x BDP。

也就是说队列长度上限是 (cwnd_gain - 1) x BDP,但是仍然没给解释,并且用 的 gain 算应该和 2BDP 不同的,大概是 1.8BDP 左右,可能是个估计值?

在启动过程中,BBR 通过寻找 BtlBw 估计值中的高点来估计管道是否已满。如果发现有几轮(三轮)尝试将发送速率增加一倍的结果实际上增加很少(少于 25%),那么它就会估计已达到 BtlBw,并退出 Startup,进入 Drain。BBR 会等待三轮,以便有确凿证据证明发送方没有检测到接收窗口临时强加导致的传输速率高点。通过三轮等待,接收方的接收窗口自动调整算法有时间打开接收窗口,BBR 发送方也有时间意识到 BtlBw 应该更高:在第一轮等待中,接收窗口自动调整算法扩大了接收窗口;在第二轮等待中,发送方填满了更高的接收窗口;在第三轮等待中,发送方获得了更高的传输速率样本。YouTube 的实验数据验证了这三轮阈值。

在 Drain 阶段,BBR 的目标是快速耗尽在 Startup 中创建的队列,方法是切换到与启动过程中使用的值相反的 pacing_gain,在一轮内耗尽队列。当 inflight 与估计的 BDP 相匹配时,即 BBR 估计队列已完全耗尽,但管道仍然是满的,这时 BBR 就会离开 Drain,进入 ProbeBW 阶段。

请注意,BBR 的 Startup 和 CUBIC 的慢启动都是以指数方式探索瓶颈容量,每一轮的发送速率都会翻倍。然而它们在主要手段上不同。首先,BBR 在发现可用带宽方面更稳健,因为它不会在数据包丢失或(如 CUBIC 的 Hystart)延迟增加时退出搜索。其次,BBR 能平滑地加快发送速率,而 CUBIC(即使有 pacing)在每一轮中都会发送一阵数据包,然后再设置一个静默间隙。图 4 显示了 BBR 和 CUBIC 的 inflight 和每次确认时的 RTT。

确实能感觉 BBR 比 Vegas 多装了很多反馈,不是硬滑翔,还是有调整姿态的阶段的。但是这些数怎么算出来的完全没有个过程,只能看他们调参了。

Deployment Experience

这部分是原文中两个部署经验合起来了

Google B4 WAN 的部署从 CUBIC 过渡到了 BBR,吞吐量提升了 2 到 25 倍。但却发现 75% 的 BBR 连接受限于内核的 TCP 接收缓冲区,网络运营团队特意将该缓冲区设置得较低(8 MB),以防止 CUBIC 让过多 inflight(8-MB/200ms 洲际 RTT ⇒ 335Mbps 最大吞吐量)涌入网络。 手动提高一条美国到欧洲路径上的接收缓冲区可使 BBR 立即达到 2 Gbps,而 CUBIC 则保持在 15 Mbps,这是 Mathis 等人预测的 133 倍相对改进。要实现带宽占满,现有的基于丢包的拥塞控制要求丢包率小于 BDP 平方的倒数,对于 1Gbps,100ms 链路要求丢包率小于 ,但是 BBR 不是基于丢包的,所以在丢包率有 5% 的情况下效果和满带宽差不多,比 CUBIC 好很多。CUBIC 的丢包忍受是算法的结构属性,而 BBR 的丢包忍受则是配置参数。当 BBR 的丢包率接近 ProbeBW 的最大 gain 时,测量到真实 BtlBw 的概率就会急剧下降,从而导致 max filter 估计不足。这时候就要调参了。

研究人员在 YouTube 也部署了 BBR,发现在所有 QoE 指标上 BBR 都好于 CUBIC,可能因为 BBR 的行为一致性更高,更可预测。这种情况下 BBR 只能略微提高连接吞吐量,因为 YouTube 已经将服务器的流媒体传输速率调整到远低于 BtlBw 的水平,以尽量减少 bufferbloat 和 rebuffer 事件。即便如此,BBR 在全球范围内平均将 RTT 中值降低了 53%,在发展中国家降低了 80% 以上。

BBR 在移动网络上运行得也很好,但是我不是研究移动网络的,不看了。因为 BBR 是一个通用的 TCP CC 方案,所以应该在各种网络环境下都 work,移动网络就是一个比较好的例子。

Delayed And Stretched ACKS

蜂窝、Wi-Fi 和有线宽带网络经常会延迟和聚合 ACK。当 inflight 仅限于一个 BDP 时,这将导致吞吐量降低的停滞。将 ProbeBW 的 cwnd_gain 提高到 2,即使 ACK 延迟有一个 RTT,BBR 也能以估计的传输速率继续顺利发送。这在很大程度上避免了停滞。

Token-Bucket Policers

BBR 在 YouTube 上的初步部署表明,世界上大多数互联网服务提供商都在使用令牌桶策略器篡改流量。在连接启动时,这个 bucket 通常是满的,因此 BBR 可以学习底层网络的 BtlBw,但一旦 bucket 空了,所有发送速度快于 bucket 填充率(远低于 BtlBw)的数据包都会被丢弃。BBR 最终会学习这种新的传输速率,但 ProbeBW gain cycle 会导致持续的中度损失。为了尽量减少这些损失造成的上游带宽浪费和应用延迟增加,我们在 BBR 中添加了策略器检测和显式策略器模型。此外,我们还在积极研究更好的方法来减轻策略器造成的损失。

就是被 ISP QoS 了怎么办,现在感觉没啥好办法。

Competition With Loss-Based Congestion Control

不管是与其他 BBR 流量竞争,还是与基于丢包的拥塞控制竞争,BBR 都会向公平共享瓶颈带宽的方向收敛。即使基于丢包的拥塞控制已将可用缓冲区填满,ProbeBW 仍能稳健地将 BtlBw 估计值向流量的公平份额靠拢,而 ProbeRTT 则能找到足够高的 RTProp 估计值,从而相对地收敛到公平份额。然而,无管理的路由器缓冲区超过了几个 BDP,会导致基于丢包的长期竞争者让队列变长,抢占超过其公平份额的流量。缓解这一问题是另一个正在积极研究的领域。

无管理的路由器感觉类似于一种即插即用的路由器,不用配置直接就工作了。

Comment

感觉很多关键问题都没细说,比如参数选择,状态机也没给,就是平铺了一些技术和测试,说了自己怎么做的,这么做效果怎么样,但是没说为什么要这么做。文章写得挺没意思的,看着十分折磨,大可直接看网上教程。