CDN 边缘节点的带宽竞争远比单机 TCP 复杂。一个回源连接、若干节点间同步连接和大量用户下行连接共享同一块网卡,如果没有合理的拥塞控制,瞬时流量峰值会引发连锁超时。AIMD 即加性增乘性减,它把复杂链路简化为一个窗口控制问题:每经过一个往返时间没有丢包,发送窗口线性增加;一旦检测到丢包,窗口乘以一个小于 1 的系数。这个规则构成了 TCP Reno 等经典拥塞控制算法的基础,也被大量 CDN 调度与传输模块借鉴。理解 AIMD 的收敛与公平性,是调优 CDN 出口带宽利用率的前提。

一、AIMD 的窗口演化与公平性
在拥塞避免阶段,加性增的典型实现是每收到一个 ACK,把拥塞窗口增加大约 MSS * MSS / cwnd 字节。这样折算到一个往返时间内,窗口大约净增一个 MSS。比如当前窗口是 10 个 MSS,经过一个 RTT 后窗口变成 11;窗口是 100 时,经过一个 RTT 后变成 101。加性增保证了窗口不会在没有拥塞时增长过快,避免瞬时压垮瓶颈队列。
一旦发送端通过重复 ACK 或超时检测到丢包,就进入乘性减阶段。以常见的 Reno 算法为例,窗口会直接乘以 0.5。假设丢包前窗口是 20 个 MSS,减半后变为 10,之后的线性增长又从 10 开始。这种锯齿状的窗口曲线,在长期稳定运行的连接中会不断重复。它的妙处在于多条连接共享同一瓶颈链路时,每条连接都会经历加性增和乘性减,最终收敛到接近均等的带宽份额。因为窗口越大,遇到丢包的概率越高,乘性减的惩罚也越大,形成负反馈闭环。
但这套公平性是建立在所有连接具有相近 RTT 和丢包率的前提下的。CDN 中不同链路 RTT 可能相差数十倍,长 RTT 连接每次加性增的周期更长,带宽恢复更慢,容易出现短 RTT 连接抢占更多资源的情况。这就是为什么单机 TCP 的默认 AIMD 参数不能直接照搬到 CDN 链路的原因之一。
二、CDN 场景下 AIMD 的落地与挑战
CDN 的流量大致可以分成三层:用户到边缘节点的下行、边缘节点到父节点的回源、节点之间的内容同步。三层链路的 RTT、丢包率和链路容量差异很大。用户下行通常经过接入网,丢包率最高,但 RTT 较低;回源链路经过公网骨干网,RTT 高、抖动大;节点间同步链路如果是内网专线,则带宽高、丢包极低。若三条链路使用同一套 AIMD 参数,长 RTT 的回源连接很容易被边缘短连接挤压,导致回源吞吐不足,缓存命中率下降。
实际工程中,CDN 的传输层通常会在不同虚拟通道上设置不同的初始窗口和慢启动阈值。对于回源大文件传输,可以适当提高加性增量 α,让窗口在每次 RTT 中增加多个 MSS,以抵消高 RTT 带来的慢恢复问题;对于用户下行小文件连接,则更依赖慢启动快速打开窗口,而不是在拥塞避免阶段长期停留。因为很多小文件在慢启动结束前就已经传完了,拥塞避免阶段根本没有机会被触发。
另一个挑战是并发连接数极大。AIMD 的公平性需要所有连接长期共存才能体现,但 CDN 边缘节点的短连接可能只持续几百毫秒。这时单个连接的窗口曲线往往只出现一次加性增,甚至没来得及乘性减就被关闭。因此,边缘节点的全局出口带宽更多依赖慢启动阈值和初始窗口的配置,而不是 AIMD 的稳态行为。这也是边缘传输程序常常在用户态实现自定义速率控制,而不是完全依赖内核 TCP 的原因。
三、参数调优与模拟实现
调优 AIMD 的核心参数是加性增步长 α 和乘性减因子 β。β 越大,丢包后窗口下降越少,带宽波动小,但网络拥塞后的恢复时间更长;β 越小,拥塞反应越剧烈,链路利用率可能震荡。在数据中心内网,β 有时可以设置为 0.8,因为内网丢包通常来自交换机缓存瞬时溢出,不需要过于激进的惩罚。而在公网回源链路,β 保持 0.5 更合适,以快速让出带宽、避免长时间排队。
下面的 Python 代码模拟了一个简化版 AIMD 窗口演化过程。它把慢启动、拥塞避免和乘性减组合在一起,便于观察不同丢包率下窗口的锯齿形态。实际 CDN 传输模块的窗口单位可能是 MSS,也可以根据业务需要改成字节数。
import random
def simulate_aimd(rtt_count=50, alpha=1, beta=0.5, ssthresh=8, loss_rate=0.03):
cwnd = 1.0 # 初始窗口,单位 MSS
cwnd_history = []
for rtt in range(rtt_count):
cwnd_history.append(cwnd)
# 模拟本 RTT 是否发生丢包
if random.random() < loss_rate:
# 乘性减:丢包后窗口减半,并降低慢启动阈值
cwnd = max(1.0, cwnd * beta)
ssthresh = max(2.0, cwnd)
else:
if cwnd < ssthresh:
# 慢启动:指数增长
cwnd = min(cwnd * 2, ssthresh)
else:
# 拥塞避免:加性增,每 RTT 增加 alpha
cwnd += alpha
return cwnd_history
trace = simulate_aimd()
for i, w in enumerate(trace):
print(f"RTT {i}: cwnd={w:.2f} MSS")
运行这段代码可以看到,在丢包率较低时,窗口会快速爬升到慢启动阈值,随后进入线性增长,偶发丢包后出现一次明显的垂直下降。这个垂直下降就是乘性减的直观体现。将 β 改成 0.8 后,下降幅度明显变小,曲线更加平滑。CDN 工程师可以根据这种模拟结果,为不同链路提前确定一组基线参数,再通过线上灰度观测首包延迟、重传率和出口队列深度来微调。