终于说到这个话题了。
bbr 不好吗,我自己也做过很多关于 bbr 的仿真验证,现网数据分析以及数学建模,结论均指向 bbr 是一个公平高效且天然不会引发 bufferbloat 的算法,但细节值得商榷:
- 非理想哑铃拓扑下测量误差的叠加效应:停车场拓扑中的 bbr;
- 流数量增多时,同时 probe 流数增多且持续,小 buffer 占量的承诺不可扩展。
特别在汇聚或骨干网络,就算 phase 全部打散,至多有 6 个完全独立的 phase slot,而在叠加 phase,同时 probe 将导致 buffer 占量增加,这问题类似 vegas 分析中,如果每条流保持 α < diff < β,n 条流的 diff 下界就是 n * α,当 n 很大时,排队时间将不可避免增加。
算法的 buffer 占量可扩展的意思是,无论多少条流,占据 buffer 的量是一定的,因此在 inflight 守恒算法中,我需要 inflt_remain 是一个携带负反馈的向内收缩的量:
- bw 越大,inflt_remain 越小,确保多流公平收敛而不是发散;
- srtt 越大,inflt_remain 越小,确保 buffer 占量不因流而增加。
先解释第一点,如果 flow 1 的 bw1 大于 flow 2 的 bw2,且 flow 1 的 inflt_remain 大于 flow 2 的 inflt_remain,那么两条流将发散,最终 flow 1 将 flow 2 挤出,独占所有带宽资源,inflight 守恒算法算一个 additive increase additive decrease 算法,理论分析表明,只有 increase 线与公平线平行或趋向,才能确保公平收敛,这就要求一条流的 inflt_remain 与其 bandwidth 负相关,这就是第一个负反馈。
再看第二点,为了确保 buffer 占量不会因流的增加而增加,以 srtt 作为收缩依据是高尚的,因为 buffer 占量越大,srtt 越大,二者存在一个简单线性关系,这一点很容易被利用。
为构造这个 inflt_remain 的表达式,我从 inflight 的基础构成 bdp 开始。
设瓶颈带宽为 C,传播时延为 R,只有 1 条流时,inflight 为 C * R,若存在 n 条流,稳定状态下每条流的基础 inflt 为 k = (1 / n) * C * R = bw * R,为构造关于 bw 的负反馈,k 要么做多项式的一项被减去,要么做分母,而为了构造关于 srtt 的负反馈,注意到下面简单的事实:
∑ n 1 B W i ⋅ R = ∑ n 1 C ⋅ 1 n ⋅ R = n 2 C ⋅ R = n ⋅ 1 B W i ⋅ R \sum^n\dfrac{1}{BW_i\cdot R}=\sum^n\dfrac{1}{C\cdot \dfrac{1}{n}\cdot R}=\dfrac{n^2}{C\cdot R}=n\cdot\dfrac{1}{BW_i\cdot R} ∑nBWi⋅R1=∑nC⋅n1⋅R1=C⋅Rn2=n⋅BWi⋅R1
如果能消掉 n,就能构造 srtt 的负反馈了,这里利用 srtt - R 和 buffer 占量之间的线性关系:
T 单独一条流排队时间 T 所有 n 条流排队时间 = B 一条流 B n 条流 = 1 n \dfrac{T_{单独一条流排队时间}}{T_{所有n条流排队时间}}=\dfrac{B_{一条流}}{B_{n条流}}=\dfrac{1}{n} T所有n条流排队时间T单独一条流排队时间=Bn条流B一条流=n1
单独一条流的排队时间和所有共存流的排队时间可以用以下代码近似:
if (rtt < RTPROP) rtt = RTPROP;
f->srtt = 0.7 * f->srtt + 0.3 * rtt;
#define EPSILON 0.001
...
if (fabs(rtt - f->min_rtt) < EPSILON || rtt < f->min_rtt) {
f->min_rtt = rtt;
} else {
double d = rtt - f->min_rtt;
if (fabs(d) > 100 * EPSILON && d < f->qmin)
f->qmin = d;
if (d > f->qmax)
f->qmax = d;
}
取 f->qmin / f->qmax 即可近似 1 / n,但既然都是近似,就没有必要精确,只要表达出负反馈的意思,然后调参数,获得一个最佳的解即可,因此我直接用 pow(f->min_rtt / f->srtt, γ) 来近似,剩下的调节 γ 参数。
于是就得到了 inflt_remain 的表达式:
I = α ⋅ ( R T T m i n R T T s m o o t h ) γ − β ⋅ B W i ⋅ R T T m i n I=\alpha\cdot (\dfrac{RTT_{min}}{RTT_{smooth}})^\gamma-\beta\cdot BW_i\cdot RTT_{min} I=α⋅(RTTsmoothRTTmin)γ−β⋅BWi⋅RTTmin
代码表达如下:
double s = f->min_rtt / f->srtt;
double p = pow(s, 20);
f->I = 0.7 * f->I + 0.3 * (alpha * p - 0.01 * f->max_e.bw * f->min_rtt);
这个减法还有第三个负反馈作用:
- 如果 srtt 过大,或者 bw 过大,f->I 更容易变为负数,从而 bdp 余量变成了 bdp 欠量,清除 queue。
实际效果如下:
理想效果如下:
相差不大。delivery rate 和 srtt 效果分别如下:
以上这个负反馈 inflt_remain 的问题在于需要仔细调整 α,β,γ 参数以避免 I 恒为负,为避免陷入数学,我倾向于设置比较大的 α 和比较小的 β,调整 γ。比如 α = 300 / 2000,β = 0.1 / 2000,分母分别展现相对大小,分子 2000 缩放总量到一个不大的范围。
除此之外,还有另一种负反馈 inflt_remain:
I = α ⋅ ( B W i β ⋅ R T T m i n + B W i ⋅ R T T m i n ) ⋅ ( R T T m i n R T T s m o o t h ) γ I=\alpha\cdot (\dfrac{BW_i}{\beta\cdot RTT_{min}+BW_i\cdot RTT_{min}})\cdot (\dfrac{RTT_{min}}{RTT_{smooth}})^\gamma I=α⋅(β⋅RTTmin+BWi⋅RTTminBWi)⋅(RTTsmoothRTTmin)γ
总结一下。
设某个时刻 t,flow 1 和 flow 2 的 bw 分别为 x,y,那么如果它们占据了 buffer(我是说 if,最理想情况下是不占任何 buffer,但这不可能),它们在 buffer 中的数据量之比为 p1 = xR : yR,在下一个 round trip 发送中,分别为它们加上一个余量 I,p2 = (x*R + I)R : (yR + I)*R,我们知道 p2 比 p1 更接近 1,以此类推,x,y 收敛到公平。
实际上如果把余量设置为常量 I = 2,系统依然可以收敛,但慢!
可见,除了保持 buffer 占量在多流场景下算法可扩展,我引入负反馈的另一个意义在于,让收敛更快,设 x > y,p1 = xR : yR,Ix < Iy,那么 p2 = (x*R + Ix)R : (yR + Iy)*R 将更快趋向 1。
评价一下。
我将 bbr 作为事件的 probebw 状态的 probe phase 均匀平摊在了所有时间,于是就取消了状态机(但 probertt 似乎还是需要),就可以用连续的微分方程组描述连续的行为,这意味着对网络状态的感受力更加灵敏了,反应也更及时。带宽利用率为 100 %,且始终保持很少且固定(取决于参数)的 buffer 占用,天然无拥塞。
此前那双鞋👟鞋底子掉了,扔掉了,鞋垫一个月前就折了扔了,今天穿着拖鞋跑步,还是很不错的,速度稳定,也不磨脚,也没滑跌。
浙江温州皮鞋湿,下雨进水不会胖。