昨天下班路上又有人咨询拥塞控制的理论问题,每当此事我就重新思考,写篇自己文章 排队系统拥塞控制的位置 的读后感吧。
网络传输,不能只孤立地看吞吐(goodput),时延(以及更重要的抖动,jitter),更应该关注网络传输效率,传输效率是一个综合概念,它表示的是一种 ROI,即用多大代价获得当前的收益,具体表现为:
网络传输效率 = 吞吐 时延 网络传输效率=\dfrac{吞吐}{时延} 网络传输效率=时延吞吐
基于此公式,结论是:
- 要获得最大吞吐,你得支付时延;
- 要获得最小时延,你得支付带宽;
- 要获得最佳的 E(比如雇佣我),你得支付金钱(皮鞋我也收);
哪有免费的午餐,所以,你怎么选?
评价网络传输效率,用动态地看,从无负载到拥塞崩溃的过程,依次要经过 “膝点”,“崖点” 两个转折点:

上图含义如下:
- 膝点之前:网络传输效率逐步提高,包括常规 “慢启动” 阶段,但会过充;
- 膝点:网络传输效率达到最佳,这是最理想的拥塞控制操作点,即 Kleinrock’s optimal point。
- 膝点到崖点:网络传输效率不增反降,吞吐会继续缓慢提升,即常规 “拥塞避免” 阶段;
- 崖点:网络最后的可用点,负载继续增加会越过此点,有效吞吐跌 0,拥塞崩溃;
- 崖点之后:再无有效吞吐,网络传输效率为 0;
膝点说的比较多了,BBR 的操作点就是膝点,它依赖主动测量,因此它是一个不稳定点,BBR 都到 v3 了还在为此犯难,它无法守住 optimal point 从而不影响 “崖点流” 的公平性,所谓 “崖点流” 就是对崖点进行被动反应的流,比如 Reno/CUBIC 这种 AIMD 流。
根据排队模型,以任意流为例,很容易通过合并 Delay-t,Bandwidth-t 图像,从 Delay-Bandwidth 图像上确定膝点:

但崖点并不能用排队模型导出作图法,因为排队模型是连续模型,崖点顾名思义是一个 “悬崖”,吞吐由此点跌落,显然这里是不连续的,无法用连续可微的曲线去描述。
模仿 1986 年拥塞崩溃场景,构建一个 TCP 拥塞崩溃模型,看看崖点到底长什么样。
设到达速率为 λ,瓶颈带宽为 C,buffer 大小为 B,则 buffer 填充速率为 λ − C \lambda-C λ−C,填满 buffer 的时间为 t full = B λ − C t_{\text{full}}=\dfrac{B}{\lambda-C} tfull=λ−CB,此时由于尚未丢包,吞吐保持为 C,可交付吞吐(与吞吐的区别时忽略了交付时间)为 λ。
当时间经过了 t full t_{\text{full}} tfull,开始丢包,可求得丢包率 R(t):
p ( t ) = λ + R ( t ) − C λ + R ( t ) p(t)=\dfrac{\lambda+R(t)-C}{\lambda+R(t)} p(t)=λ+R(t)λ+R(t)−C
那么重传率的变化率就是:
d R ( t ) d t \dfrac{\text{d}R(t)}{\text{d}t} dtdR(t)= λ ⋅ p ( t ) = λ ⋅ ( 1 − C λ + R ( t ) ) \lambda\cdot p(t)=\lambda\cdot(1-\dfrac{C}{\lambda+R(t)}) λ⋅p(t)=λ⋅(1−λ+R(t)C)
而可交付吞吐可表示为:
T ( t ) = λ ⋅ ( 1 − p ( t ) ) = λ ⋅ ⋅ C λ + R ( t ) T(t)=\lambda\cdot (1-p(t))=\lambda\cdot\cdot\dfrac{C}{\lambda+R(t)} T(t)=λ⋅(1−p(t))=λ⋅⋅λ+R(t)C
OK,得到两个微分方程,解之就是重传率和可交付吞吐的表达式 R(t) 和 T(t)。有 Python matplotlib 数值解谁还会去硬解微分方程:
import numpy as np
import matplotlib.pyplot as plt
def dRdt(R, t, lambda_val, C):
return lambda_val - (lambda_val * C) / (lambda_val + R)
def corrected_euler_method(R0, t, lambda_val, C, B):
t_full = B / (lambda_val - C)
R = np.zeros_like(t)
R[t < t_full] = 0
idx = np.argmax(t >= t_full)
for i in range(idx, len(t)):
dt = t[i] - t[i-1] if i > 0 else 0
R[i] = R[i-1] + dt * dRdt(R[i-1], t[i-1], lambda_val, C)
return R, t_full
lambda_val1 = 1.5
C = 1.0
R0 = 0.0
t = np.linspace(0, 50, 5000)
B_values = [2, 5, 10]
colors = ['b', 'g', 'r']
lambda_val2 = 3.5
B_additional = 5
additional_color = 'm'
plt.figure(figsize=(15, 6))
plt.subplot(1, 2, 1)
for B, color in zip(B_values, colors):
R, t_full = corrected_euler_method(R0, t, lambda_val1, C, B)
plt.plot(t, R, color, linewidth=2, label=f'λ={lambda_val1}, B={B}')
plt.axvline(t_full, color=color, linestyle='--', alpha=0.5)
R_additional, t_full_additional = corrected_euler_method(R0, t, lambda_val2, C, B_additional)
plt.plot(t, R_additional, additional_color, linewidth=2,
label=f'λ={lambda_val2}, B={B_additional}')
plt.axvline(t_full_additional, color=additional_color, linestyle='--', alpha=0.5)
plt.xlabel('Time (t)', fontsize=12)
plt.ylabel('$R(t)$', fontsize=12)
plt.title('$\\frac{dR}{dt}=\\lambda-\\frac{\\lambda C}{\\lambda+R(t)}$', fontsize=14)
plt.grid(True)
plt.legend()
plt.subplot(1, 2, 2)
for B, color in zip(B_values, colors):
R, t_full = corrected_euler_method(R0, t, lambda_val1, C, B)
T = np.where(t < t_full, C, lambda_val1 * C / (lambda_val1 + R))
plt.plot(t, T, color, linewidth=2, label=f'λ={lambda_val1}, B={B}')
plt.axvline(t_full, color=color, linestyle='--', alpha=0.5)
R_additional, t_full_additional = corrected_euler_method(R0, t, lambda_val2, C, B_additional)
T_additional = np.where(t < t_full_additional, C, lambda_val2 * C / (lambda_val2 + R_additional))
plt.plot(t, T_additional, additional_color, linewidth=2,
label=f'λ={lambda_val2}, B={B_additional}')
plt.axvline(t_full_additional, color=additional_color, linestyle='--', alpha=0.5)
plt.xlabel('Time (t)', fontsize=12)
plt.ylabel('$T(t)$', fontsize=12)
plt.title(r'$T(t) = C\ \text{if}\ t<t_{full},\ \frac{\lambda C}{\lambda+R(t)}\ \text{if}\ t\geq t_{full}$', fontsize=14)
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()
结局如下:

可见,更大的 λ,更小的 B 都会加快崖点的到达,一旦越过崖点,跌落是如此决绝,巧妇难为无米之炊。
这就展示了崖点的成因和作图,值得注意的是,标准 M/M/1,M/G/1 模型是崖点不可达的,因为不计 buffer,ρ 无限接近 1,λ 就渐近 μ,时延渐近无穷大但不会出现吞吐跌落。
所谓崖点是工程意义上的交付吞吐跌落点,曲线上一定是不连续的,buffer 的有限和 TCP 的可靠性构建这种不连续的工程场景,崖点的表达是一个动态过程。
1986 年拥塞崩溃之后,VJ 等人的工作事后看来很明确,检测到崖点即可,而据 VJ 本身所说,任何手段都会骗人,唯有丢包不会骗人,丢了就是丢了。
于是 “对丢包进行反应” 就成了拥塞控制的基调,至于如何反应,由于考虑公平性,这就需要控制论背景,而不只是一句话的事了,我们都知道,这就是 AIMD。不过 VJ 确实说对了,BBR 不再对丢包被动反应,而采用了 ”主动测量“ 的手段,稍微疏漏,哪怕是主机时钟错乱,都会造成不符合预期的异常,正因为如此,BBRv2 开始再次对丢包做出反应,唯有丢包不会骗人。
拥塞控制应该在膝点和崖点之间工作,然而膝点的滑落不可检测,而崖点的滑落则可检测,我们知道,Reno/CUBIC 等 AIMD 算法均以崖点为操作点,膝点和崖点之间就叫拥塞避免,对应 Additive Increase,而对崖点的反应就是 Multiplicative Decrease,而 BBR 则以膝点为理想操作点,但也只是理想。是故,膝点不易达,崖点最真实,针对崖点的拥塞控制才是网络传输的真实边界。
浙江温州皮鞋湿,下雨进水不会胖。
336

被折叠的 条评论
为什么被折叠?



