根据上图,BBR的BtlBw和RTprop无法同时测量的,测量BtlBw需要塞满buffer,测量RTprop不能使用buffer。
ProbeBW状态:BBR通过延续10s的up/down稳态来试图塞满带宽,由此可以测得BtlBw。
不管这10s中是自己还是其它流塞满了带宽,但几乎10s肯定能测量出最大的带宽。
ProbeRTT状态:BBR在延续10s的up/down之后突然排空所有的buffer,由此可以测得RTprop。
通过将自己cwnd限制为4看起来不能保证整个buffer被排空,但这里有一个自动全局同步,下面说.
注意上面的ProbeRTT状态,很多人会误认为在这个阶段pacing rate会跌零然后重新开始,事实上这是错误的。
ProbeRTT状态相比ProbeBW状态而言持续非常短,而BBR计算pacing rate所使用的BtlBw是windowed_max的,因此离开ProbeRTT再次进入ProbeBW状态时,绝大多数情况下,其BtlBw是没有变化的。
当我们把BBR简化,去除了非核心的杂项之后,BBR其实就是一个在两个状态之间周期转换的状态机
是不是和Reno家族的AI状态和MD状态周期转换的状态机很类似:
我们可以在BtlBw/RTprop图示上将两者统一起来:
Reno确实是面向buffer的,它是bufferbloat的初始.没有buffer的overflow便无法指示Reno的收敛点westwood像是一个经过BBR调制的Reno…
OK,现在我们确定了BBR的收敛点,即离开ProbeRTT阶段进入ProbeBW的那一刻.
在那一刻,BtlBw保持,而RTprop最新测得,为了保证公平性,需要在这个收敛点做点什么,就像Reno家族在buffer overflow做的MD减窗操作那样。
遗憾的是,BBR在这里除了随机选择一个phase进入ProbeBW阶段之外,什么也没有做。我想象不出BBR论文中下图的理由:
下面这个图倒是像真的:
我的测试结果如下:
我想象不出除了靠运气,还有什么潜在的动力学可以保证这些流最终收敛到公平。
假设塞满整个pipe时两条流的BtlBw之比是m/n,离开ProbeRTT状态进入ProbeBW状态时,除非二者分别一个up phase一个down phase,否则它们的BtlBw还将保持这个比例。
5/4和3/4的均值是1,相当于什么也没有做。除非真的有空余带宽被加进来(有流退出或者路由重新收敛到更大带宽的路径)才会避免3/4down
那么如何解决BBR的这个公平性问题?
按照控制论的观点, 效率和公平是不可兼得的。 为了增强公平性,就不得不损失点效率。
我们知道,离开ProbeRTT状态进入ProbeBW状态时,所有BBR流的BtlBw之和等于网络pipe的瓶颈带宽,没有任何空余的带宽空间可以用来执行公平性的工作,这个时候如果有流主动出让带宽,会被认为是 降低了带宽利用率 。
我们可以从BBR论文里的一段话里看出,它涉及到一个小优化(但我并没有在代码里看到它的实现):但是,若想保证公平,必须有流出让带宽,这样才能实现重分配。
问题是如何出让,出让多少呢?在我看来,在ProbeRTT状态和ProbeBW状态中间加一个Converge状态即可:
和ProbeRTT一样的思路,用一个比较短但又足够长的时间来range这个Converge状态的持续时间,比方说400ms(虽不优雅,但再来个800ms又何妨),在这个Converge状态,对pacing执行AIMD替代ProbeBW状态的5/4,4/3 固定增益的up,down操作:
ai_up()
{
if (sub_state != up)
return;
pacing_rate += 10/RTprop;
if (is_full_length &&
(rs->losses || /* perhaps pacing_gain*BDP won't fit */
inflight >= bbr_inflight(sk, bw, bbr->pacing_gain))) {
sub_state = down;
bbr->pacing_gain = BBR_UNIT * 3/4;
}
}
md_down()
{
if (sub_state != down)
return;
bbr->pacing_gain = BBR_UNIT;
if (is_full_length ||
inflight <= bbr_inflight(sk, bw, BBR_UNIT)
sub_state = up;
}
到头来,其实Reno家族和BBR是一样的:
Reno家族通过丢包被动发现收敛点。
BBR通过ProbeRTT主动发现收敛点。
都一样,到达收敛点之后执行AIMD即可。
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
TCP BBR算法的带宽敏感性以及高丢包率下的优化
bbr算法最终会稳定在Steady-state,即其PROBE_BW的状态,该状态内置了一个小循环,bbr在这个循环中不停运转:
注意其中的速率增益:1|1|1|1|1|1|1.25|0.75|1|1|1|1|1|1|1.25|0.75|1|...这个循环增益完全是bbr自主的循环增益,不受任何外界控制,是否进入下一个增益取决于以下的判断:
1.正增益周期,是否在不丢包情况下已经填满了可用带宽,这个旨在提高资源利用率(效率因素);
2.减损周期内,是否已经腾出了部分可以资源,这个旨在满足公平性(公平因素);
3.平稳反馈周期是否足够久,使得共享资源的TCP连接有时间收敛到一个公平的位置(公平因素)。
这是个典型的效率优先兼顾公平的实例,但是我们可以看到,如果上述的第三点,即平稳反馈周期太久会怎样?
很显然,这样的话会降低bbr的抢占性和灵活性,bbr发现带宽富余的最短时间就是6个RTT,这对于某些场景而言显然是不足的。
因此,我们可以得出第一个结论:
PROBE_BW状态中平稳反馈周期的长度决定了bbr抢占敏感性。
一个最简单且极端的例子,如果我们将bbr在PROBE_BW的pacing gain数组改成下面这个样子,其将会第一时间发现带宽富余,然则代价就是发送速率的颠簸,这对下载业务是好的,但是并不利于直播点播业务:
// 将8个元素的数组改成2个元素的数组,是的bbr在Steady-state处于颠簸徘徊状态。
static const int bbr_pacing_gain[2] = {
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... */
};
那么bbr是不是也类似呢?非也!因为bbr不会被接管,bbr自己全程负责所有的拥塞处理!事实上,bbr根本不管什么拥塞不拥塞,它只是单纯的根据自己收到的带宽反馈来计算下面发送的带宽,即便真的发生了拥塞,不也还是可以发送数据的么,只是发送的可能是重传数据而不是新数据而已.
我们来看一张来自Google的bbr测试结果图示:
问题我已经标在上面了,为什么bbr会面临一个崖点,此后完全不可恢复!虽然CUBIC最终也是归于同一结局,但是它是按比例滑落的,而不像bbr那样跌落一般。这个怎么解释呢?
我直接给出答案吧,崖点的存在,正是因为bbr_pacing_gain数组的固定配置。
我们看到在bbr_pacing_gain数组中,其增益元素的增益比是1/4,也就是25%,可以简单理解为,在增益周期,bbr可以多发送25%的数据!
注意看上面的图的横轴丢包率,当丢包率接近25%的时候,曲线从崖点跌落,这并不是偶然的!过程很容易理解,首先,在增益期,x%的丢包率是否抵消了增益比25%?也就是说,x是否大于25.
我们假设x就是25!那么可想而知,25%的收益完全被25%的丢包所抵消,相当于没有收益,接下来的减损周期,又减少了25%的发送数据,同时丢包率依然是25%...再接下来的6个RTT,持续保持25%的丢包率,而发送率却仅仅基于反馈,即每次递减25%,我们可以看到,在bbr_pacing_gain标识的所有8周期,数据的发送量是只减不增的,并且会一直持续下去,这就是崖点.
以上我的假设是x就是25,事实上,丢包率在不足25%的时候,以上的减损现象就已经开始呈现了,在Google的测试中,我们发现从5%的丢包率开始,持续到10%左右的丢包率,然后到15%~20%丢包率的时候,吞吐率持续下跌,一直跌到底。我们可以看到,在bbr_pacing_gain数组中,除了bbr_pacing_gain[0]表示增益之外,在有丢包的情况下,其它的都是减损,特别在高丢包率(我一向觉得5%以上就是很高丢包率了)下更是明显。
好了,我们把上述的论述总结在一个图示上:
然后我们得到的新结论是:
1.bbr_pacing_gain数组的bbr_pacing_gain[0]系数(当前是5/4),决定了抗丢包能力;
2.bbr_pacing_gain数组中系数为1(即平稳反馈周期)的元素的多少,决定了抗丢包能力(太久,则会持续衰减,太短,则比较颠簸)。
其中为什么会”causing the max filter to underestimate“呢?
因为max filter是基于时间窗口的,随着时间的流逝,bbr会忘掉之前的大带宽,只记着现在由于丢包持续减少的小带宽。
你要注意,其实只要有丢包,在6个平稳反馈周期内,带宽就是持续减少的,然而,问题是,这些减少的量能不能通过增益周期,即bbr_pacing_gain[0]的收益一次性补偿,如果不能,那么跌入崖点就是必然的。
因此,拿什么来抵抗丢包呢?答案是损失一点公平性,用增益系数去换!我把5/4换成了3/2,效果良好,大概到丢包率30%以后才跌入崖点,要知道30%的丢包率,可用性几乎已经可以说是零了!另外,我为什么不减少bbr_pacing_gain数组中增益系数为1的元素的数量呢?因为这会严重影响公平性!所以我保持其8个元素。
这是bbr不如CUBIC的地方,bbr事实上完全基于反馈,正常来讲按照VJ的数据守恒规则,一个包离开另一个方可进入的情况下,丢包率本身就会持续拉低带宽,bbr有一个5/4的增益,这可能会弥补丢包带来的反馈减损,然而增益系数5/4却是一个配置参数,当丢包率大于25%的时候,就会资不抵债!甚至丢包率在10%~15%的时候,bbr的表现就开始不佳。因此,如果能动态计算丢包率,然后将丢包率反馈给增益系数,动态计算增益系数,我想效果应该会不错。
比如丢包率达到25%的时候,增益系数就变成50%,这样就可以避免由于丢包带来的反馈减损,然而,你又如何判断这些丢包是噪声丢包还是拥塞丢包呢?
答案在于RTT!只要时间窗口内的RTT不增加,那么丢包就不是拥塞导致的,感谢Gail & Kleinrock给出的模型,感谢Yuchung Cheng & Neal Cardwell证明了这是对的。
顺便说一句,由于CUBIC的表现就是填满所有可以填满的缓存,它们的行为是可怕的,要不是CUBIC还遵循着乘性减窗(只是减到了原来的0.7而不是0.5),网络估计早就崩溃了,即便如此,如果不是因为大量的TCP接收端限制了接收窗口,网络也是要被CUBIC搞崩溃的。现如今,有将近30%~50%的接收端通告了一个比CUBIC计算出来的更小的窗口,感谢这些接收端,使得我们免受CUBIC Flood的危害!
,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,