引
BBR中有很多诸如1.25,0.75,0.89,0.77之类的魔数字,它们是调教出来的经验值呢,还是可以用数学推导发出来呢?
这些问题在结果导向的当今非常无聊,但也勉强仅图一乐吧。对我自己而言,除了兴趣还有一些执念。
我不相信背后没有数学解释的东西,一开始我看不上BBR,就是因为它没有数学模型,类似AIMD,response function那样优美的数学模型,没有Reno/CUBIC中
t
=
α
β
p
t=\alpha \sqrt {\dfrac{\beta}{p}}
t=αpβ这样的公式。
后来经neal在内的BBR Development groups大佬们的指点,加入一些自己的思考,发现BBR的确没有统一的数学模型,但在零散的每个细节背后都有严谨的数学,这让人感到舒服且兴奋。国庆节的清早,就把这些写写。
Startup状态gain的确定
Startup状态的gain在历史上有两个值,分别为2.89和2.77,它们的差别在于:
- 从cwnd算出pacing rate,则为2.89,这也是原始的理想结论。
- 从pacing rate算出cwnd,则为2.77,这是更加合理的最新结论。
关于该话题,引述2018年和neal在BBR邮件讨论组里的交流:
下面挨个看。
2.77的推导
设
f
(
x
)
f(x)
f(x)和
g
(
x
)
g(x)
g(x)分别为BBR在Startup状态的pacing rate函数和cwnd函数,
P
i
n
i
t
P_{init}
Pinit和
W
i
n
i
t
W_{init}
Winit分别为初始pacing rate和初始cwnd,根据慢启动原理:
f
(
x
)
=
P
i
n
i
t
×
2
x
f(x)=P_{init}\times 2^x
f(x)=Pinit×2x
g
(
x
)
=
W
i
n
i
t
×
2
x
g(x)=W_{init}\times 2^x
g(x)=Winit×2x
计算简化起见,设
P
i
n
i
t
=
W
i
n
i
t
=
1
P_{init}=W_{init}=1
Pinit=Winit=1.
cwnd与BDP是等计量的,BDP显然是pacing rate在一个区间的积分:
如上图,显然有:
B D P x − 2 x − 1 = ∫ x − 2 x − 1 f ( x ) = ∫ x − 2 x − 1 2 x d x = 2 x 4 ln 2 BDP_{x-2}^{x-1}=\int_{x-2}^{x-1}f(x)=\int_{x-2}^{x-1}2^xdx=\dfrac{2^x}{4\ln2} BDPx−2x−1=∫x−2x−1f(x)=∫x−2x−12xdx=4ln22x
根据cwnd函数,则有:
W x = g ( x ) = 2 x W_{x}=g(x)=2^x Wx=g(x)=2x
若 W x W_{x} Wx是在 B D P x − 2 x − 1 BDP_{x-2}^{x-1} BDPx−2x−1的基础上通过乘以一个gain而来,设gain为 g g g,则:
W x = g × B D P x − 2 x − 1 = g × 2 x 4 ln 2 = 2 x W_x=g\times BDP_{x-2}^{x-1}=g\times \dfrac{2^x}{4\ln 2}=2^x Wx=g×BDPx−2x−1=g×4ln22x=2x
解出 g g g:
g = 4 ln 2 ≈ 2.77 g=4\ln 2\approx 2.77 g=4ln2≈2.77
2.89的推导
预设和2.77的推导一致,图示不一样:
如上图,显然有:
B D P x − 1 x = ∫ x − 1 x f ( x ) d x = 2 x 2 ln 2 BDP_{x-1}^{x}=\int_{x-1}^xf(x)dx=\dfrac{2^x}{2\ln 2} BDPx−1x=∫x−1xf(x)dx=2ln22x
若 B D P x − 1 x BDP_{x-1}^x BDPx−1x是在 W x − 2 W_{x-2} Wx−2的基础上通过乘以一个gain而来,设gain为 g g g,则:
B D P x − 1 x = 2 x 2 ln 2 = g × g ( x − 2 ) = g × 2 x 4 BDP_{x-1}^x=\dfrac{2^x}{2\ln 2}=g\times g(x-2)=g\times \dfrac{2^x}{4} BDPx−1x=2ln22x=g×g(x−2)=g×42x
解出 g g g:
g = 2 ln 2 ≈ 2.89 g=\dfrac{2}{\ln 2}\approx2.89 g=ln22≈2.89
Drain状态gain的确定
详见下面“ProbeBW状态drain阶段的另一种可能(关于drain-to-target)”小节。
ProbeBW状态使用minRTT界定cycle phase的原因
当需要计时,有minRTT和packet-timed RTT可供使用,而BBR在ProbeBW状态采用了minRTT而不是packet-timed RTT,原因如下:
-
probe同步问题
感谢neal的解释:For what it’s worth, the motivation for using wall clock time rather than packet-timed round trips for triggering the end of a cycle phase was to avoid entrainment. We originally tried using packet-timed round trips, but noticed that this caused entrainment: flows aligned their phases based on the dynamics of the bottleneck queue; in turn this caused some flows to repeatedly probe for bandwidth at the same time as other flows, causing each flow in the synchronized ensemble to fail to realize that its fair share was higher. We found better bandwidth convergence by avoiding entrainment by explicitly randomizing the initial phase of the flows and then using wall clock time to trigger the progress through the bandwidth probing cycle.
使用packet-timed RTT的问题在于会发生所谓的Entrainment,这将导致几个流同时进入probe阶段,之后又同时进行drain,从而破坏BBR在ProbeBW状态固有的公平收敛性。同步probe会让BBR造成误判,事实上并没有发生拥塞,只是大家一起probe导致的暂时假性拥塞。
和简谐振动同步一样,probe同步也是有害的,详细解释为什么多个流会对齐它们的phase start时间不是本文的目的,只能直观上感受一下。
多条流显然是在同一个buffer中相互作用的系统,它们之间的交互是probe同步的原因:和荡秋千时为了让秋千快速停下来需要随机摇晃一样,随机化是破解同步的利器。我前段时间尝试以固定时间执行ProbeBW,也是有probe同步的风险的,解除这个风险有好几个方案:
- 随机化每一个cycle phase的顺序。
- 每一个cycle phase的时间在小时间范围随机化。
- 小范围随机化cruise phase的数量。
以上的随机方案可以叠加实施。
-
高丢包问题
显然,packet-timed RTT大于等于minRTT的,使用更久的packet-timed RTT坚持probe可能会造成很高的丢包,比如在已经开始排队的系统中。下面是一个描述该场景的tcptrace图示:
凡事要分析两面性,纵然使用packet-timed round可能会出现probe同步以及高丢包,但它的好处是,packet-timed RTT可以天然识别排队。比如,通过packet-timed RTT和minRTT的比较,在识别出排队时,便可以跳过probe阶段了,因此此时的probe将会加剧拥塞。所以说,ProbeBW状态完全可以根据实际情况进行触发式自适应:
- 触发式probe:在采集到minRTT或者RTT小到一定程度是马上进行probe。
- 周期性probe:在时间段 T T T内,至少保证要probe一次,以防饿死
- probe和cruise阶段使用minRTT周期计数。
- drain阶段使用packet-timed RTT周期计数(见最后一节)。
ProbeBW状态probe阶段BBR如何公平收敛
设一条流的带宽为
B
B
B,probe增益为
g
g
g,理想情况下,在probe之前和其它流完全共享整个带宽,设为1,带宽利用率为100%,则其它流的带宽为
1
−
B
1-B
1−B.
当该流probe时,分两种情况:
-
有空余带宽加进来,该流理所当然获得 g × B g\times B g×B的带宽。
-
无空余带宽,probe导致了排队,队列出口按照buffer中各流包量比例分配带宽。
无论如何probe之后,该流带宽在当前总带宽的占比为:g × B g × B + 1 − B \dfrac{g\times B}{g\times B+1-B} g×B+1−Bg×B
该流在probe前后的带宽占比的变化为:
p ( B ) = g × B g × B + 1 − B B B + 1 − B p(B)=\dfrac{\frac{g\times B}{g\times B+1-B}}{\frac{B}{B+1-B}} p(B)=B+1−BBg×B+1−Bg×B
如果吧 G × B G\times B G×B看作是在 B B B的基础上增加了一个增量 α \alpha α,即 g = 1 + α g=1+\alpha g=1+α,则有:
g × B g × B + 1 − B = B + α B + α + 1 − B \dfrac{g\times B}{g\times B+1-B}=\dfrac{B+\alpha}{B+\alpha+1-B} g×B+1−Bg×B=B+α+1−BB+α
相比于probe之前的带宽占比,分子分母同时加上一个值,其占比是扩大的,因此 p ( B ) > 1 p(B)>1 p(B)>1,化简为:
p ( B ) = g ( g − 1 ) B + 1 p(B)=\dfrac{g}{(g-1)B+1} p(B)=(g−1)B+1g
g g g大于1,因此$ p ( B ) p(B) p(B)是一个大于1的减函数,初始带宽越小获得的加速比就越大。这趋向于收敛到公平。
ProbeBW状态的RTT不公平性
设 λ ( t ) \lambda(t) λ(t)为时间 t t t时刻一条BBR流的测量速率,根据Bottleneck通量原理:
λ ( t ) = B D P R T T \lambda(t)=\dfrac{BDP}{RTT} λ(t)=RTTBDP
记传播时延 R T p r o p RTprop RTprop为 T T T,排队时延 D e l a y q u e u i n g Delay_{queuing} Delayqueuing为 D D D,从而有:
λ ( t ) = B D P T + D \lambda(t)=\dfrac{BDP}{T+D} λ(t)=T+DBDP
注意 D D D对于排入同一个buffer的所有流而言是一致的,所以不区分流。
在ProbeBW状态 λ ( t ) \lambda(t) λ(t)是上一轮测量带宽 λ ( t − 8 T ) \lambda(t-8T) λ(t−8T)基础上以增益 g g g进行probe的结果,该过程发送的数据量即BDP:
λ ( t ) = g × T × λ ( t − 8 T ) T + D = λ ( t ) = g × T T + D λ ( t − 8 T ) \lambda(t)=\dfrac{g\times T\times \lambda(t-8T)}{T+D}=\lambda(t)=\dfrac{g\times T}{T+D}\lambda(t-8T) λ(t)=T+Dg×T×λ(t−8T)=λ(t)=T+Dg×Tλ(t−8T)
其中 g × T T + D \dfrac{g\times T}{T+D} T+Dg×T为有效增益系数。
可以从测量带宽 λ ( t ) \lambda(t) λ(t)的有效增益系数 g × T T + D \dfrac{g\times T}{T+D} T+Dg×T看出BBR的RTT不公平性:
- RTT越大,有效增益系数越大,抢占能力越强。
ProbeBW状态drain阶段如何在minRTT时间将队列排掉
在drain阶段刚开始的时候,根据BBR的probe算法描述,在进入drain的那一刻,端到端的inflight为(at least BBR.RTprop and either inflight has reached 5/4 * estimated_BDP):
g p r o b e × B l t B W × R T p r o p g_{probe}\times BltBW\times RTprop gprobe×BltBW×RTprop
假设drain持续的时间为 T d r a i n T_{drain} Tdrain,则整个drain阶段发出的数据包量为:
g d r a i n × B l t B W × T d r a i n g_{drain}\times BltBW\times T_{drain} gdrain×BltBW×Tdrain
在整个drain阶段,由于已经排队,带宽持续保持 B l t B W BltBW BltBW,因此被确认的数据包量为:
B l t B W × T d r a i n BltBW\times T_{drain} BltBW×Tdrain
根据数据包守恒原则,drain阶段发出的数据包和被确认的数据包之差正好等于一个不排队时的BDP,有下列等式:
g p r o b e × B l t B W × R T p r o p + g d r a i n × B l t B W × T d r a i n − B l t B W × T d r a i n = B D P m i n = B l t B W × R T p r o p g_{probe}\times BltBW\times RTprop + g_{drain}\times BltBW\times T_{drain}-BltBW\times T_{drain}=BDP_{min}=BltBW\times RTprop gprobe×BltBW×RTprop+gdrain×BltBW×Tdrain−BltBW×Tdrain=BDPmin=BltBW×RTprop
一步步化简为:
g
p
r
o
b
e
×
R
T
p
r
o
p
+
g
d
r
a
i
n
×
T
d
r
a
i
n
−
T
d
r
a
i
n
=
R
T
p
r
o
p
g_{probe}\times RTprop+g_{drain}\times T_{drain}-T_{drain}=RTprop
gprobe×RTprop+gdrain×Tdrain−Tdrain=RTprop
(
g
p
r
o
b
e
−
1
)
×
R
T
p
r
o
p
=
(
1
−
g
d
r
a
i
n
)
×
T
d
r
a
i
n
(g_{probe}-1)\times RTprop = (1-g_{drain})\times T_{drain}
(gprobe−1)×RTprop=(1−gdrain)×Tdrain
我们希望在最多一个 R T p r o p RTprop RTprop的时间内完成drain,那么 R T p r o p > T d r a i n RTprop>T_{drain} RTprop>Tdrain,则有:
g p r o b e − 1 > 1 − g d r a i n g_{probe}-1 > 1-g_{drain} gprobe−1>1−gdrain
解上述不等式:
g d r a i n > 2 − g p r o b e g_{drain}>2-g_{probe} gdrain>2−gprobe
当前BBR中 g p r o b e = 1.25 g_{probe}=1.25 gprobe=1.25代入,得到:
g d r a i n > 0.75 g_{drain}>0.75 gdrain>0.75
理想情况下,当 g d r a i n = 0.75 g_{drain}=0.75 gdrain=0.75时,BBR可以在一个 R T p r o p RTprop RTprop中完成drain阶段的收敛。
这就是BBR算法ProbeBW中probe和drain两个阶段的Gain的关系:
phase 1 of the gain cycle is designed to drain any queue at the bottleneck (the likely outcome of phase 0 if the pipe was full) by using a pacing_gain of 3/4, chosen to be the same distance below 1 that 5/4 is above 1.
https://datatracker.ietf.org/doc/html/draft-cardwell-iccrg-bbr-congestion-control-00#section-4.3.4.1
ProbeBW状态drain阶段的另一种可能(关于drain-to-target)
如上节描述,如果队列仅是同一个流在probe阶段堆积,接下来的drain阶段,预期可在一个minRTT之内排空该堆积。如果队列并非由自己所造,便不能保证在一个minRTT内排空自己的堆积了。
此时需要一个修正,即drain-to-target patch的逻辑:
- 一直坚持到将inflight下降到BDP,否则不离开drain阶段。
事实上,如果不使用minRTT而使用packet-timed round来triggering the end of drain cycle phase,便可以保证可以在一个round内drain掉堆积。然而如本文第一小节所描述,使用packet-timed round是有弊端的,因此还是要使用minRTT。
那么,能否仅针对drain阶段使用packet-timed round呢?我想也未尝不可,只是如此一来,drain阶段的gain就不再是 2 − 1.25 = 0.75 2-1.25=0.75 2−1.25=0.75,而是 1 1.75 = 4 5 \dfrac{1}{1.75}=\dfrac{4}{5} 1.751=54。
这是为什么?按照上节推导方式,只需要将 T d r a i n T_{drain} Tdrain换为 T p a c k e t r o u n d T_{packet round} Tpacketround即可:
g p r o b e × B l t B W × R T p r o p + g d r a i n × B l t B W × T p a c k e t r o u n d − B l t B W × T p a c k e t r o u n d = B D P m i n = B l t B W × R T p r o p g_{probe}\times BltBW\times RTprop + g_{drain}\times BltBW\times T_{packet round}-BltBW\times T_{packet round}=BDP_{min}=BltBW\times RTprop gprobe×BltBW×RTprop+gdrain×BltBW×Tpacketround−BltBW×Tpacketround=BDPmin=BltBW×RTprop
而 T p a c k e t r o u n d T_{packetround} Tpacketround包括 R T p r o p RTprop RTprop和排队时延,进一步可以将排队时延替换成
g p r o b e × B l t B W × R T p r o p − B l t B W × R T p r o p B l t B W \dfrac{g_{probe}\times BltBW\times RTprop-BltBW\times RTprop }{BltBW} BltBWgprobe×BltBW×RTprop−BltBW×RTprop
代入上面的等式,化简为:
g p r o b e + g d r a i n × g p r o b e − g p r o b e = 1 g_{probe} + g_{drain}\times g_{probe}-g_{probe}=1 gprobe+gdrain×gprobe−gprobe=1
最终结果:
g d r a i n = 1 g p r o b e g_{drain}=\dfrac{1}{g_{probe}} gdrain=gprobe1
二者互为倒数。而这个结果也正是BBR在Startup状态和Drain状态时其gain的关系。
ProbeBW的cwnd上界
BBR采用pacing rate作为其primary control parameter,而cwnd只是secondary control parameter,之所以仍然需要一个cwnd作为一个control parameter,很大程度上是为了规定一个发送量的上界。
根据BBR的模型,在理想情况下,BBR是无排队,不丢包的。但在被动排队的场景下,BBR无法兑现上面的承诺,为了最大限度降低被动排队对BBR造成的影响,在被动排队场景下,一个cwnd的上界是必要的。
假设一个buffer queue长度为 C C C,BltBW为 B B B,队列中已经被占据的比例为 p p p,那么留给BBR排队的空间为 C × ( 1 − p ) C\times (1-p) C×(1−p)。
若想让BBR获得最大带宽,则需要将 C × ( 1 − p ) C\times (1-p) C×(1−p)的空间排满,而此时采集到的其minRTT则为排入的第一个数据包的RTT,因此我们可以得到:
B
W
m
a
x
=
B
×
(
1
−
p
)
BW_{max}=B\times (1-p)
BWmax=B×(1−p)
R
T
T
m
i
n
=
C
×
p
B
RTT_{min}=\dfrac{C\times p}{B}
RTTmin=BC×p (注意,BBR被动排在后面,所以前面的数据包以
B
B
B输出)
因此,我们可以计算得到一个带有增益系数 g g g的cwnd:
C ( p ) = g × ( 1 − p ) × p × C C(p)=g\times (1-p)\times p\times C C(p)=g×(1−p)×p×C
很明显,这是一个抛物线,其最大值在 p = 0.5 p=0.5 p=0.5的时候,即:
C ( 0.5 ) = 0.25 × g × C C(0.5)=0.25\times g\times C C(0.5)=0.25×g×C
BBR标准版本选择了 g = 2 g=2 g=2,那么cwnd的上界则是 0.5 × C 0.5\times C 0.5×C,也就是说,BBR最多只能够占据一半的buffer,而这个比例是比较合理的:
- 在已经堆积了一半的场景下,BBR激进容忍到极限,可以撑到AQM丢包的极限。
- 在尚未堆积到一半的场景下,BBR偏保守,等待缓解。
- 在堆积超过一半的场景下,BBR偏保守,避免了丢包。
无论如何,只要发生了被动排队,BBR均不会采取激进措施,以最大限度维持BBR的模型。
如果不选择2作为cwnd_gain会怎样?
- cwnd_gain大于2,当队列已经堆积了一半时,BBR发送量将会超过队列容量的一半,造成丢包。
- cwnd_gain小于2,当队列堆积尚未达到一半时,BBR发送量就到达上界,如果被其它流饿死。
不多不少,2作为cwnd_gain刚刚好。
probe挤占导致的排队问题以及解法
设流的带宽为 B 1 B_1 B1,probe之后测量其带宽为 B 2 B_2 B2
B 2 × ( T m i n + Δ t ) = g × B 1 × T m i n B_2\times (T_{min}+\Delta t)=g\times B_1\times T_{min} B2×(Tmin+Δt)=g×B1×Tmin
B 2 > B 1 B_2>B_1 B2>B1的情况下,真的意味着该流probe到新的带宽 B 2 − B 1 B_2-B_1 B2−B1了吗?
分两种情况:
- 如果 Δ t = 0 \Delta t=0 Δt=0,说明真的是probe到了新的空闲带宽。
- 如果 Δ t > 0 \Delta t>0 Δt>0,说明是挤占了其它流的带宽。
无论哪种情况,更大的 B 2 B_2 B2均将进入windowed-max-filter中并在至少10个packet-timed rounds中以 B 2 B_2 B2作为send pacing rate,同时其它BBR流由于windowed-max-filter会坚持10个packet-timed round,并不会降低send pacing rate。
在 Δ t > 0 \Delta t>0 Δt>0的情况下,Bottleneck将因此而过载,从而造成buffer拥塞。这显然违背了BBR的初衷。那么,如何修正这个问题呢?
在判断新采集的带宽 B 2 B_2 B2是否要进入windowed-max-filter的时候,增加一个条件。设 T c u r r T_{curr} Tcurr为最新采集到的RTT,如果满足下面的条件,则不将新的更大的带宽 B 2 B_2 B2放入windowed-max-filter:
T c u r r − T m i n = Δ t T_{curr}-T_{min}=\Delta t Tcurr−Tmin=Δt
由于完全理想情况难遇,在实际操作中,需要在计算时给予一定的宽容,允许一定的偏差。
后记
终于写完了,公式排版太难搞,不像文字可以一气呵成,不过总算还是写完了。
在调研BBR以及看它的源码的时候,总是可以看到范雅各布森的大名,他总是被列入作者的行列,但我一直不清楚范大师在BBR具体做了哪些工作,是真的做了一些工作还是说只是为了尊敬,希望知道的朋友告诉我,我一直都对历史和科技考古非常感兴趣。
我了解到的,范雅各布森对bufferbloat是有过很深的研究的,除此之外,范雅各布森1988年的成果更是奠定了后来关于TCP拥塞控制的基调。
另一方面,QUIC方面无论从源码,IETF draft,还是各类文案,均找不到范雅各布森的名字,即便是QUIC版的CUBIC,BBR也都没有提他,哈哈,是不是有点不尊师重道了呢 (─‿‿─)
参考:
- BBR开发团队的分析
https://github.com/google/bbr/tree/master/Documentation/startup/gain/analysis - 之前写过的一些文章
https://blog.csdn.net/dog250/article/details/80660091
https://blog.csdn.net/dog250/article/details/80754825
浙江温州皮鞋湿,下雨进水不会胖。