BBR处理网络拥塞的原理
Google推出的BBR可以用于解决长胖网络(具有高速率时延积BDP的传输网络)的性能,他与传统的Reno或者CUBIC的TCP拥塞控制算法相比有了一些突破性的思维。在2016年,谷歌将其所有Youtube服务器集群等全面部署了BBR算法,并在之后一段时间的应用成果后将其开源。
传统拥塞控制算法
传统的 TCP 拥塞控制主要是基于 Van Jacobson 博士提出的慢启动算法、拥塞避免算法和用于估计周转 RTT(Round Trip Time)的算法。
慢启动算法通过观察进入网络的速率应该与另一端返回确认的速率相同进行工作。慢启动为发送方增加了一个称为“拥塞窗口”(英文中称为 cwnd )的窗口,当与接收方建立 TCP 连接时,拥塞窗口被初始化为 1 个报文段(即另一端通告的报文段大小)。接收方每收到一个 ACK ,拥塞窗口就增加 1 个报文段( cwnd 以字节为单位,而慢启动以报文段大小为单位进行增加),发送方取拥塞窗口与通告窗口中的最小值作为发送上限,即拥塞窗口是发送方的流量控制,通告窗口是接收方的流量控制。发送方开始时发送一个报文段,然后等待 ACK ,收到 ACK 后拥塞窗口从 1 增加到 2 ,即可以发送两个报文段。当收到这两个报文段的 ACK 后拥塞窗口则增加为 4 ,拥塞窗口呈指数式增长。
然后,在某些传输段可能会达到容量上限,中间路由便开始丢弃分组。拥塞避免算法就是用于处理丢失分组的算法。
拥塞避免算法假定分组丢失是非常少的(远小于 1% ),因此认为分组丢失就意味着在发送方和接收方之间的某个传输段上发生了拥塞。
慢启动和拥塞避免算法是两个目的不同、独立的算法,但在实际中这两个算法通常在一起实现。当拥塞发生时,我们希望降低分组进入拥堵段的速率,于是设计了慢启动方式来做到这一点。
之后出现的 TCP Reno 版本逐渐取代了 TCP Tahoe 版本,增加了快速重传算法(基于冗余ACK和快速恢复算法,避免了当网络拥塞不够严重却因采用慢启动算法而造成过大地减小发送窗口尺寸的现象。
一、BBR的特性
1、相对于传统拥塞控制算法,BBR不再去关注拥塞的发生,传统拥塞控制算法都是监测拥塞状态机,只有当拥塞发生的时候才会去采取修改拥塞窗口cwnd
的方式,传统的RENO或者后面出现的CUBIC方式也有许多问题。Cubic同之前的拥塞控制算法一样,无法区分拥塞丢包和传输错误丢包,只要发现丢包,就会减小拥塞窗口,降低发送速率,而事实上传输错误丢包时网络不一定发生了拥塞,但是传输错误丢包的概率很低,所以对Cubic算法的性能影响不是很大。但是在远距离高延时网络之下,由于错误丢包的现象就大大提升了,CUBIC算法无法判断错误丢包从而导致了许多不必要的降速,不能充分的提高带宽利用率。
Cubic算法的另一个不足之处是过于激进,在没有出现丢包时会不停地增加拥塞窗口的大小,向网络注入流量,将网络设备的缓冲区填满,出现Bufferbloat(缓冲区膨胀)。由于缓冲区长期趋于饱和状态,新进入网络的的数据包会在缓冲区里排队,增加无谓的排队时延,缓冲区越大,时延就越高。另外Cubic算法在高带宽利用率的同时依然在增加拥塞窗口,间接增加了丢包率,造成网络抖动加剧。
适用场景:适用于高带宽、低丢包率网络,能够有效利用带宽。
2、BBR算法关注的只是他自己定义的四个状态,分别是startup
、drain
、probeRTT
、probeBW
3、BBR的算法在网络传输过程中会持续关注两个参数,一个是RTT
,也就是往返传输时延,另一个是即时宽带bw,在BBR运行过程中,它会持续关注这两个参数的最大值,而他的目标也是达到这两个参数的最大值。
4、BBR算法的输出参数:传统算法的输出参数一般就是cwnd
,操作系统通过提取cwnd窗口值以及远程端主机提供的rwnd值得出发送窗口的上限值。而BBR算法不仅提供cwnd,并且还提供了pacing rate
参数。Linux系统对于一个发送窗口的数据的发送方式是洪泛发送,也就是同时发送所有内容,这个参数的意义代表相邻时间发送的一个窗口内容所取的时间间隔。
5、BBR算法还利用了一些其他外部机制,比如-fq,RACK等
二、BBR算法对于四种状态的处理方式
start-up状态
在startup状态下,BBR通过初始的增益系数(默认为2/ln(2))不断地增加cwnd与pacing rate,从而找到最大的带宽值,并且一直记录着RTT最小值。如何找到最大的宽带呢?BBR的思想就是在保持RTT不变的情况下不断地增加BDP,直到连续三次探测(此时进入drain状态)宽带不再增加的时候,在此基础上测量RTT所得的就是最小RTT。(因为后续若继续增加发送速率,数据包的排队现象就产生了,此时传输速率并不会增加,多余的BDP将会被路由器缓存下来,从而导致RTT增加。因此,当带宽不再增加之时BBR可以同时得到最大宽带与最小RTT。
drain状态
在从startup状态,由于是基于连续三次探测的,因此此时已经发送了三次多余的数据在链路上了,drain的目的就是排空之前的多余发送量。在drain状态下,增益系数将会被调整至小于1的状态,然后继续减小速率进行发送,同时继续采集信息。通过采样得到的最小RTT值与最大带宽值进行乘积运算,得到的时延带宽积就为链路最大宽带BDP。若inflight(BBR发送的数据)<= BDP 则BBR进入probe_bw匀速阶段。若infight> BDP,则BBR继续保持drain排空阶段直至infight不高过BDP为止。
probe_bw状态
在此状态之下,BBR将会分成8个阶段来发送数据,其中一个阶段的增益系数为5/4,这个阶段的时间可以保持久一点,目的是为了填满空闲的宽带。紧接着的阶段增益系数为3/4,这个时间可以设置得短一点,为了不错过填充带宽的时间。其余的增益系数都为1,目的是为了进行温和的探测是否有空闲的宽带可用。在此过程中不断地采集最新的RTT与bw信息,不断地计算时延带宽积。当采集到连续10秒钟没有探测到新的最小RTT之后,此时判断链路中出现了拥塞(路由器开始启用缓存队列机制),此时将会从probe_bw状态跳转至probe_rtt阶段。
probe_rtt状态
这个状态进入后,cwnd会被设置成一个很小的值,为了防止丢包而采集最小的RTT值,持续200ms(BBR默认),之后过了这个值重新探测是否带宽已经被占满(增加发送速率后RTT是否增加),若已经占满了则回到probe_bw状态,此时发送速率已经减小了很多。若没有占满则回到start_up状态开始按照高增益持续增加数据发送量。
三、BBR对于宽带的计算细节与其处理方式
BBR对于计算宽带时,只用到了两个值,其一为delivered
,表示应答了多少数据,其二为interval_us
,表示应答上面delivered数据所用的时间。
将上述两者相除即可得到即时宽带bw(bw = delivered / interval_us)
上面的计算为完全标量计算,只关注数据的大小而不去关注其含义。例如在delivered中BBR不去关注应答的数据是重传的AKC确认、正常AKC确认或者累计ACK确认的,它只关心应答的值。
当然关于乱序、重传等情况会由SACK机制等来关注。
带宽的计算
即时带宽 BW 的计算
BBR 完全忽略系统层面的 TCP 状态,计算带宽时它仅需要两个值:
应答了多少数据,记为 delivered
应答 delivered
所用的时间,记为 interval_us
将上述二者相除,就能得到带宽: BW
= delivered
/interval_us
以上的计算完全是标量计算,只关注数据的大小,不关注数据的含义。例如 delivered 的采集中,BBR 根本不关心某个应答是重传后的 ACK 确认的、正常 ACK 确认的、还是 SACK 确认的。BBR 只关心被应答了多少。
这和 TCP/IP 网络模型是一致的,因为在中间链路上,路由交换机也不会去管这些数据包是重传的还是乱序的,然而拥塞也是在这些地方发生的,既然拥塞点都不关心数据的意义,TCP 为什么要关注呢?拥塞发生的原因,即数据量超过了路由带宽限制,利用这一点,只需要精心地控制发送数据量就好了,完全不用管什么乱序、重传之类的。
pacing rate 以及 cwnd 的计算
pacing rate 怎么计算?就是即时带宽 BW 。而即时带宽 BW 是上一次采样计算的结论,这次能否按照这个 BW 发送数据呢?这要看当前的增益系数的值,设为 G ,那么 G×BW 就是 pacing rate 的值。
至于 cwnd 的计算,cwnd 其实描述了一条网络管道(而 rwnd 描述了接收端缓冲区),因此 cwnd 其实就是这个管道的容量即 BDP 。BW 已经有了,还缺少 RTT 。BBR 一直在持续搜集最小 RTT ,BBR 并没有采用类似移动指数平均算法来猜测 RTT ,而是直接采集最小 RTT(这个 RTT 是 TCP 系统层面移动指数平均的结果,即 SRTT ,但 BBR 并不会对此结果再次做平均。最小 RTT 表示一个曾经达到的最佳 RTT ,既然曾经达到过,说明可以再次达到该 RTT ,这样有益于网络管道利用率最大化。通过 G’×BDP 就算出了 cwnd ,这里的 G’是 cwnd 的增益系数,与带宽增益系数 G 含义一样,根据 BBR 的状态机获取。
状态机
不断地基于当前带宽以及当前的增益系数计算 pacing rate 以及 cwnd ,以这 2 个结果作为拥塞控制算法的输出。在 TCP 连接的持续过程中,每收到一个 ACK ,都会计算即时带宽,然后将结果反馈给 BBR 的 pipe 状态机,不断地调节增益系数。
可以发现,它是一个典型的封闭反馈系统,与 TCP 当前处于什么拥塞状态完全无关。这非常不同于之前的所有拥塞控制算法,在之前的算法中,算法内部是受外部的拥塞状态影响的,比方说在 Recovery 状态下,甚至都不会进入拥塞控制算法。
在 BBR 问世前,Linux 使用 PRR 算法控制 Recovery 状态的窗口调整,即使这个时候网络已经恢复,TCP 也无法发现, 因为 TCP 的 Recovery 状态还未恢复到 Open 。
四、BBR对于异常情况的处理
RTT突变情况
因为BBR算法是基于bw与RTT的闭环控制方式,如果发生了RTT突变等异常情况会不会影响BBR的效果呢?答案是不会。经dog250博主实验测试,在BBR与CUBIC与Reno同时工作的环境中,他制造了RTT突变的情况,以查看BBR对于异常情况的处理。然而发现BBR并没有降速,甚至对RTT的突变不会有反应,而对RTT的变化的剧烈反应是基于时延的拥塞算法的基本特征,任何情况下BBR的表现都优于其他算法,是不是很奇怪呢。
这是因为BBR是在一个不随时间滑动的大概10秒的时间窗口中采集最小RTT,BBR只使用这个最小RTT计算Pacing Rate和拥塞窗口。BBR不会对RTT变大进行反应。但是如果整的发生了拥塞,RTT确实会变大,BBR怎么发现这种情况呢?答案就在于这个时间窗口的超期滑动,如果在一个时间窗口内持续没有采集到更小的RTT,那么就会将当前的RTT赋值个最小RTT。BBR就是这样抵抗假拥塞的。毕竟秒级的窗口内,什么都是瞒不住的。