mptcp 推出很久了,先看 rfc6356 三原则:
- 对自己,mptcp 的吞吐不能比用 sp(single path)tcp 时更差;
- 对它者,mptcp 子流对资源的占用不能侵害其它 sptcp 流量;
- 负载分担,要将孬 subflow 流量分担到好 subflow 上。
若精心实现这三者,过犹不及:
- 负载均匀分担到所有 subflow 需精确计算,但 tcp 的信息精度不支持;
- 目标执着于 mp,有时吞吐还不如 sp,且乱序,重传导致时延增加;
- 挑选最优路径,意味着次优等其它路径被放弃;
- …
这些问题本质原因还是 tcp 乃至所有传输层协议信息精度不足以支撑 mp 传输,我认可 mp,但我不认可 mptcp。
说到底就是 tcp/ip 网络传输缺乏一种模糊逻辑和概率路由的机制,这一点我在前面谈到图论最大流最小割时有说过,若 ip 路由不以最短路径为基,而以排序最小割最大流为概率进行传输,网络天然就 mp,其上的 tcp 天然就 mp,若有一日有人发现最短路径可解放保序业务主机算力,那他会提出 sptcp,即单路径 tcp 协议,也能有一篇论文当经理。事情正反都合理,事情正着做,逻辑要反着想。
说说 mpbbr。
昨天有朋友问我 bbr 多路径问题,如果每条 subflow 都跑 bbr,n 条子流就是 n 倍嗨,这违反 rfc6356 原则二。我就着这问题简单推演了一个 bbr 的多路径模型。
先提出问题,然后推演模型,再然后调参,最后仿真,最最后上线灰度,但显然最最后也没得机会。
mptcp 拥塞控制核心是耦合,即所有子流不再独立进行拥塞控制,而彼此影响,比如在控制 cwnd 总量的前提下针对性调整某条子流的 cwnd。这在 aimd 算法中很容易实现,当前主流 mptcp 拥塞控制的耦合算法基本都是 aimd,不再赘述。但对 bbr 而言,由于它是 rate-based,而 rate 作为矢量不存在耦合,便无法采用 aimd 的方法,便需要一个新方法来保证 mpbbr 不侵害其它 sptcp 流量。
何谈侵害?有交互才能侵害,如果两个流量走不同路径便井水不犯河水谈不上侵害,因此只有经过相同瓶颈的流量才需要确保公平性,mptcp 也不例外。
bbr subflow 共享瓶颈识别非常简单,同步 probertt 意味着同时进入 probertt 的 subflow 共享瓶颈,同时还可辅助一些别的手段加以确认减少误判,比如单独 subflow probe 时 rtt 同时升高的 subflow 共享瓶颈,将共享瓶颈的 subflow 们加入集合 S,目标是 “集合 S 内的所有 subflow 不能联合起来侵害共享同一瓶颈的其它 sptcp 流”
由于 bbr 独立测量 maxbw 和 minrtt,若不对这个过程加以干涉,bbr 默认会独自与其它流共享资源,将它们耦合在一起的方式就是提供一个作用力,让集合 S 中的 subflow 们一起往里收,最终每个 subflow 收到 1 / n 的力道。
先看标准 bbr 的动力学方程,以 3 条流为例:
d x d t = C ⋅ g ⋅ x ⋅ R g ⋅ x ⋅ R + y ⋅ R + z ⋅ R − x \dfrac{dx}{dt}=C\cdot \dfrac{g\cdot x\cdot R}{g\cdot x\cdot R+y\cdot R+z\cdot R}-x dtdx=C⋅g⋅x⋅R+y⋅R+z⋅Rg⋅x⋅R−x
y d t = C ⋅ g ⋅ y ⋅ R g ⋅ y ⋅ R + x ⋅ R + z ⋅ R − y \dfrac{y}{dt}=C\cdot \dfrac{g\cdot y\cdot R}{g\cdot y\cdot R+x\cdot R+z\cdot R}-y dty=C⋅g⋅y⋅R+x⋅R+z⋅Rg⋅y⋅R−y
z d t = C ⋅ g ⋅ z ⋅ R g ⋅ z ⋅ R + x ⋅ R + y ⋅ R − z \dfrac{z}{dt}=C\cdot \dfrac{g\cdot z\cdot R}{g\cdot z\cdot R+x\cdot R+y\cdot R}-z dtz=C⋅g⋅z⋅R+x⋅R+y⋅Rg⋅z⋅R−z
我以 probe 之后为收力点,若 probe 到带宽 c,我将其缩放为 c = c * (1/c),缩放系数为 d = 1 / n,即在上面方程组右边第一项后面再乘以一个系数 d 即可。
若设计独立的拥塞控制算法,公平性最大的难点在于共享瓶颈的流量彼此独立互不知情,我经常卡顿于不知道 n 的尴尬,若知道了 n,一切就迎刃而解。幸运的是 mpbbr 中,sender 知道 n,知道集合 S 的一切信息。
但事情并没有这么简单。如果某条流直接缩放 1 / n,腾出的资源会被同在 S 中的其它 subflow 拿去,公平性动力会浪费在 subflow 彼此交互之上,因此需要走 “绝对值越小加速比越大” 的路子,即选择系数 d > 1 / n。或另一个路子,分批次慢缩放。
微分方程改为下:
d x d t = C ⋅ g ⋅ x ⋅ R g ⋅ x ⋅ R + y ⋅ R + z ⋅ R ⋅ D − x \dfrac{dx}{dt}=C\cdot \dfrac{g\cdot x\cdot R}{g\cdot x\cdot R+y\cdot R+z\cdot R}\cdot D-x dtdx=C⋅g⋅x⋅R+y⋅R+z⋅Rg⋅x⋅R⋅D−x【D > 1 / n】
接下来模拟调参。先给出下面的数值解:
a, b = 1.25, 0
C, R = 20, 0.1
x0, y0, z0, w0, q0, k0 = 1, 3, 5, 8, 9, 2
u0 = 0
T, dt = 380, 0.01
...
# 调节 b
b = ?
...
for n in range(1, len(times)):
x[n] = x[n-1] + dt * (C*a*R*x[n-1]/(R*(k[n-1] + a*x[n-1] + y[n-1] + z[n-1] + w[n-1] + q[n-1]))*b - x[n-1])
y[n] = y[n-1] + dt * (C*a*R*y[n-1]/(R*(k[n-1] + a*y[n-1] + x[n-1] + z[n-1] + w[n-1] + q[n-1]))*b - y[n-1])
z[n] = z[n-1] + dt * (C*a*R*z[n-1]/(R*(k[n-1] + a*z[n-1] + x[n-1] + y[n-1] + w[n-1] + q[n-1]))*b - z[n-1])
w[n] = w[n-1] + dt * (C*a*R*w[n-1]/(R*(k[n-1] + a*w[n-1] + x[n-1] + y[n-1] + z[n-1] + q[n-1]))*b - w[n-1])
q[n] = q[n-1] + dt * (C*a*R*q[n-1]/(R*(k[n-1] + a*q[n-1] + x[n-1] + y[n-1] + z[n-1] + w[n-1]))*b - q[n-1])
k[n] = k[n-1] + dt * (C*a*R*k[n-1]/(R*(q[n-1] + a*k[n-1] + x[n-1] + y[n-1] + z[n-1] + w[n-1])) - k[n-1])
u[n] = (x[n] + y[n] + z[n] + w[n] + q[n])
集合 S 中有 x,y,z,w,q 一共 5 条 subflow,与共享瓶颈的另一条标准 bbr 流 k 参与公平收敛,目标是 k 线和 u 线重合。为简化调参过程,我用另一种方式模拟 k,即让其固定 inflight-in-buffer。
既然 C * R = 2,若公平共享,k 的 inflight-in-buffer 应为 1,于是用 B = 1 替换 k[…] * R:
x[n] = x[n-1] + dt * (C*a*R*x[n-1]/(B + R*(a*x[n-1] + y[n-1] + z[n-1] + w[n-1] + q[n-1]))*b-x[n-1])
...
k[n] = C*B / (R*(x[n] + y[n] + z[n] + w[n]) + B)
n = 2 时,b = 0.998,n = 5 时,b = 0.911,效果如下:
这意味着 D 随着 n 的增大在减小,需要设计一个减函数 0 < D(n) <= 1,但这是另一件事,方法也简单,先给出 n = 1,2,3,4,…,m 时得到的最佳 D,然后用一个函数拟合这些点即可,如果要空间换时间,就存储为静态表,以 n 为键查表。
具体到代码的修改,只需要修改一下以下片段:
static const int bbr_pacing_gain[] = {
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... */
};
...
... D(int n, int idx)
{
if (idx < 2) return 1;
...
}
...
case BBR_PROBE_BW:
bbr->pacing_gain = (bbr->lt_use_bw ?
BBR_UNIT :
D(n, bbr->cycle_idx) * bbr_pacing_gain[bbr->cycle_idx]);
bbr->cwnd_gain = bbr_cwnd_gain;
break;
如何将 n 传入 bbr不谈,核心难点是内核中如何处理 0.998,0.827 这种数字。所以我只做了仿真测试,没有真去修改内核的 bbr.c,因为搞不定。至于 bbr3 如何,原理一样。
浙江温州皮鞋湿,下雨进水不会胖。