1986年10月,Lawrence Berkeley Laboratory(LBL)与加州大学伯克利分校之间的网络连接出现了一个令人困惑的现象:两地相隔仅400码,中间只经过两个IMP(Interface Message Processor)跳转,正常情况下数据吞吐量应维持在32 Kbps左右,但在这段时间里,吞吐量骤降至40 bps——下降了近1000倍。

这不是设备故障,也不是链路中断。Van Jacobson在后来的分析中指出,这是互联网历史上第一次"拥塞崩溃"(congestion collapse)。TCP协议在设计之初并没有考虑网络拥塞问题,当多个连接同时向已饱和的网络倾注数据时,会触发一种正反馈循环:数据包丢失导致重传,重传进一步加剧拥塞,更多的数据包丢失,更多的重传……系统陷入恶性循环。

这个事件催生了TCP拥塞控制机制。此后的三十八年间,从Tahoe到Reno,从Vegas到CUBIC,再到BBR,拥塞控制算法经历了多次范式转移。每一次演进都不是简单的性能优化,而是对网络本质理解的深化。

拥塞的本质:瓶颈与管道模型

要理解拥塞控制算法的演进,首先需要理解拥塞的物理本质。

在任何时刻,一条全双工TCP连接在每一方向上都有且只有一个最慢的链路——这就是瓶颈(bottleneck)。瓶颈的重要性体现在两个方面:它决定了连接的最大数据传输速率;它是持久队列形成的地方。

如果将网络路径比作一根管道,那么往返传播时延(RTprop)是管道的长度,瓶颈带宽(BtlBw)是管道的最小直径。管道的容量——带宽时延积(BDP = BtlBw × RTprop)——决定了需要多少数据"在途"才能充分利用这条路径。

Kleinrock在1979年证明了一个重要结论:当发送速率等于瓶颈带宽,且在途数据量等于BDP时,系统达到最优操作点——既最大化了带宽利用率,又最小化了延迟和丢包。但Jaffe在1980年证明了一个令人沮丧的"不可能性定理":无法设计出一个分布式算法,让网络收敛到这个最优操作点。

原因在于测量模糊性:当你观测到RTT增加时,无法判断这是因为路径变长了、瓶颈带宽下降了,还是其他连接的流量增加了排队延迟。单次测量无法区分这些因素。

这个不可能性定理主导了拥塞控制研究的方向长达三十年——直到BBR的出现。

Tahoe与Reno:丢包作为信号

1988年,Van Jacobson发表了具有里程碑意义的论文《Congestion Avoidance and Control》。这篇论文提出了三个核心算法:慢启动(Slow Start)、拥塞避免(Congestion Avoidance)和快速重传(Fast Retransmit)。这些算法被实现在4.3BSD Tahoe版本中,因此被称为TCP Tahoe。

Tahoe的核心思想是"包守恒"原则:在稳定状态下,只有当一个旧包离开网络时,才应该向网络注入一个新包。ACK就是那个"时钟"——每当收到一个ACK,就意味着有一个包离开了网络,可以安全地发送一个新包。

但如何确定应该发送多少数据?Tahoe引入了拥塞窗口(cwnd)的概念。慢启动阶段,cwnd从一个包开始,每收到一个ACK就翻倍(指数增长)。当cwnd超过慢启动阈值(ssthresh)或发生丢包时,进入拥塞避免阶段,cwnd每个RTT只增加一个包(线性增长)。

Tahoe的致命问题是:一旦检测到丢包(通过三个重复ACK或超时),它就将cwnd重置为1,重新开始慢启动。这过于激进,导致大量带宽浪费。

1990年出现的TCP Reno改进了这一点。Reno引入了快速恢复(Fast Recovery)算法:当收到三个重复ACK时,不是将cwnd降为1,而是减半,然后直接进入拥塞避免阶段,跳过慢启动。这大大提高了从轻度拥塞中恢复的效率。

// Reno的核心逻辑简化
if (收到3个重复ACK) {
    ssthresh = cwnd / 2;
    cwnd = ssthresh + 3;  // 快速恢复
    重传丢失的包;
}
// 当收到新的ACK时
if (处于快速恢复) {
    cwnd = ssthresh;  // 恢复正常
    退出快速恢复;
}

但Reno也有缺陷:当同一窗口内有多个包丢失时,Reno只能恢复第一个丢失的包,对后续丢包需要等待超时。这就是NewReno(RFC 6582)要解决的问题——它能识别"部分ACK"(partial ACK),在快速恢复阶段继续重传丢失的包,直到所有丢包都被恢复。

AIMD(加性增乘性减)策略是Reno系列算法的核心:无拥塞时线性增加窗口探测更多带宽,检测到拥塞时将窗口减半。这个策略被证明能保证系统的稳定性和公平性。但其代价是:长期运行在"管道填满+队列部分填满"的状态,导致延迟偏高。

Vegas:延迟作为信号

1994年,Brakmo等人提出了TCP Vegas——一种基于延迟而非丢包的拥塞控制算法。

Vegas的核心洞察是:如果网络没有拥塞,实际吞吐量应该等于预期的瓶颈带宽。当实际吞吐量低于预期时,说明队列正在积压,网络已经开始拥塞。

具体来说,Vegas持续测量RTT和实际吞吐量。设BaseRTT为观测到的最小RTT(代表无排队时的传播延迟),Expected = cwnd / BaseRTT是预期吞吐量,Actual = cwnd / RTT是实际吞吐量。两者的差值Diff = Expected - Actual反映了当前队列的积压程度。

如果 Diff < α:说明网络利用不足,增加cwnd
如果 Diff > β:说明队列积压过多,减少cwnd
否则:保持cwnd不变

Vegas在实验中表现出色:吞吐量提高37-71%,丢包减少50-80%。但它从未被广泛部署。原因是公平性问题:当Vegas与Reno共享瓶颈时,Reno会填满队列,导致RTT增加;Vegas检测到RTT增加后会降低发送速率,最终被Reno"饿死"。

这是基于延迟的算法面临的普遍困境:在以丢包为信号的主导算法面前,它们往往表现得过于"谦让"。

高速网络的挑战:BIC与CUBIC

进入21世纪,网络带宽从Mbps提升到Gbps,延迟从数百毫秒降到数十毫秒。BDP的量级发生了巨大变化,AIMD的线性增长策略暴露出了问题。

假设一条10 Gbps/100 ms的路径,BDP约为125 MB(83333个1500字节的包)。如果cwnd从1开始,以每个RTT增加1个包的速度增长,填满这个管道需要83333个RTT——超过2小时!即便从BDP的一半开始恢复,也需要一个多小时才能重新利用全部带宽。

BIC TCP(Binary Increase Congestion control)就是为了解决这个问题而设计的。它的核心思想是将拥塞窗口的调整视为一个搜索问题:已知上一次没有发生拥塞的窗口大小Wmax,和发生拥塞时的窗口大小,那么最优窗口应该在两者之间。BIC使用二分搜索来快速逼近最优值。

当检测到丢包时,BIC记录Wmax = cwnd,然后执行乘性减(cwnd减半)。在恢复阶段,如果cwnd与Wmax差距较大,BIC进行"快速增长"(直接跳到中点);当接近Wmax时,切换到"最大值探测"(线性增加)。这使得BIC在高速网络中能快速恢复到满带宽状态。

CUBIC是BIC的改进版,由Sangtae Ha等人于2008年提出,从Linux内核2.6.19开始成为默认拥塞控制算法。CUBIC用三次函数替代了BIC的二分搜索逻辑:

W(t) = C(t - K)³ + Wmax

其中 K = ∛(Wmax × β / C)

CUBIC的窗口增长曲线呈现一个"S"形:在Wmax附近增长缓慢(保持稳定性),远离Wmax时增长迅速(快速恢复)。增长函数仅依赖于时间,与RTT无关——这解决了BIC存在的RTT公平性问题(短RTT连接增长更快)。

参数C(默认0.4)控制增长的激进程度,β(默认0.7)是乘性减因子。这些参数经过精心调校,确保CUBIC在与Reno共存时保持TCP友好性。

Bufferbloat:被忽视的问题

当拥塞控制算法不断优化吞吐量时,另一个问题被忽视了:延迟。

Jim Gettys在2010年首次系统描述了"Bufferbloat"现象。随着内存价格下降,网络设备的缓冲区变得越来越大。在传统观点中,缓冲区越大越好——能吸收突发流量,减少丢包。

但问题在于,基于丢包的拥塞控制算法(Reno/CUBIC)会持续增加窗口,直到缓冲区填满并开始丢包。如果缓冲区有几秒的容量,那么RTT会增加几秒。对于交互式应用(语音、视频、游戏),这是灾难性的。

Bufferbloat的根本原因在于:丢包作为拥塞信号太"迟钝"了——它意味着队列已经溢出。理想情况下,拥塞信号应该在队列开始积压时就发出,而不是等到溢出。

Active Queue Management(AQM)机制如RED(Random Early Detection)和CoDel试图在路由器端解决这个问题:在队列满之前就主动丢弃或标记数据包,提前通知发送端减速。ECN(Explicit Congestion Notification)更进一步:不在队列满时丢包,而是在IP头部设置标记,让接收端在ACK中反馈这个信息,发送端据此调整速率而不必承受丢包的代价。

但路由器端的改进需要全网部署,进展缓慢。真正改变游戏规则的,是发送端的革新。

BBR:重新定义拥塞控制

2016年,Google的Neal Cardwell、Yuchung Cheng等人在ACM Queue上发表了《BBR: Congestion-Based Congestion Control》。这篇文章提出了一个根本性的问题:为什么我们要用丢包来判断拥塞?

丢包和拥塞的关系在1980年代是强相关的——那时缓冲区很小,丢包几乎总是意味着拥塞。但随着缓冲区变大,丢包可能只是缓冲区溢出的副作用;而在浅缓冲区网络(如Google的B4 WAN)中,丢包可能只是突发流量导致的随机事件,与真正的拥塞无关。

BBR(Bottleneck Bandwidth and RTT)的核心思想是:直接测量路径的两个关键参数——瓶颈带宽(BtlBw)和往返传播时延(RTprop)——并据此调整发送行为。

这正是绕过Jaffe不可能性定理的方法:虽然单次测量无法同时确定带宽和延迟,但可以通过时间上的交替测量来估计它们。

BBR的状态机

BBR维护两个估计器:

  • BtlBw:使用滑动窗口最大值过滤器,跟踪过去6-10个RTT内的最大发送速率
  • RTprop:使用滑动窗口最小值过滤器,跟踪过去几十秒内的最小RTT

BBR的发送行为由两个参数控制:

  • pacing_rate:发送速率,设为BtlBw × pacing_gain
  • cwnd:拥塞窗口,设为BDP × cwnd_gain

BBR通过一个状态机来交替探测带宽和延迟:

STARTUP阶段:使用增益2/ln(2) ≈ 2.89快速增加发送速率,直到测量到的发送速率不再增长——说明已达到瓶颈带宽。此时最多会产生2BDP的队列。

DRAIN阶段:使用增益ln(2)/2 ≈ 0.35排空STARTUP阶段积累的队列,直到在途数据量降至BDP。

PROBE_BW阶段:稳态运行阶段,使用循环的增益值[1.25, 0.75, 1, 1, 1, 1, 1, 1]来周期性探测带宽变化。平均增益为1.0,保持稳定利用。

PROBE_RTT阶段:当RTprop估计长时间未更新时,将cwnd降至4个包,持续至少一个RTT,以测量真实的传播延迟。这会让瓶颈队列排空,获得准确的RTprop测量。

状态转换逻辑简化:
if (STARTUP阶段 && 发送速率不再增长) -> DRAIN
if (DRAIN阶段 && 队列排空) -> PROBE_BW
if (RTprop估计过期) -> PROBE_RTT
if (PROBE_RTT完成) -> PROBE_BW 或 STARTUP(取决于是否有带宽增长)

与传统算法的本质区别

传统算法(Reno/CUBIC)维护的是窗口大小的估计,而BBR维护的是网络路径的物理模型。这个区别决定了它们的行为:

  • 传统算法将丢包视为拥塞信号,BBR将丢包视为网络事件——只有在影响到BtlBw估计时才响应
  • 传统算法通过填满缓冲区来探测带宽,BBR通过主动调节发送速率来测量带宽
  • 传统算法的窗口大小隐式决定了在途数据量,BBR显式控制在途数据量

部署效果

Google在B4 WAN(其全球数据中心互联网络)上的部署结果表明,BBR的吞吐量比CUBIC高2-25倍。这不是魔法——B4使用浅缓冲区交换机,CUBIC频繁触发丢包并大幅降速,而BBR正确识别了瓶颈带宽。

在YouTube边缘服务器上,BBR将全球中位RTT降低了53%,在发展中国家降低超过80%。这是因为BBR不会无节制地填满缓冲区——它大部分时间运行在接近最优操作点的位置。

BBR的争议与演进

BBR并非完美。学术界和工业界对其提出了多项批评,主要集中在公平性问题上。

RTT不公平性

研究发现,当不同RTT的BBR流共享瓶颈时,长RTT流会获得不成比例的带宽份额。这与传统TCP(短RTT流获得更多带宽)的偏好相反。

原因在于BBR的设计:BtlBw估计基于发送速率,而发送速率 = 数据量 / 时间。在相同的发送速率下,长RTT流的在途数据量更大。当它们竞争相同的瓶颈带宽时,长RTT流的在途数据"占用"了更多空间,挤压了短RTT流。

与CUBIC共存问题

当BBR与CUBIC共享瓶颈时,公平性取决于缓冲区大小:

  • 小缓冲区(10KB):BBR可能获得超过90%的带宽,因为CUBIC频繁触发丢包而降速
  • 大缓冲区(几倍BDP):CUBIC可能获得更多带宽,因为它会填满缓冲区

这种不一致的行为给网络运营带来了不确定性——你不知道部署BBR后会发生什么。

BBRv2的改进

Google在2019年推出了BBRv2,主要改进包括:

  1. 集成ECN和丢包信号:v1完全忽略丢包,v2将ECN标记和丢包率纳入考量,避免过度激进
  2. 改进的RTT公平性:通过调整PROBE_RTT的行为和增益值,缓解长RTT流的带宽优势
  3. 更好的CUBIC友好性:v2主动探测共存流的存在,在必要时收敛到公平份额

实验表明,BBRv2在与CUBIC共存时的带宽份额更加均衡,同时保留了v1的低延迟特性。

拥塞控制算法的选择

没有放之四海而皆准的算法。选择取决于场景:

数据中心内部:时延敏感,队列浅,可以考虑BBR或专门的数据中心算法(如DC-TCP、TIMELY)

广域网大文件传输:带宽优先,CUBIC仍然可靠,BBR在浅缓冲区网络中更有优势

交互式应用:延迟敏感,BBR显著优于传统算法

高丢包环境:无线网络、卫星链路,BBR的丢包容忍度远高于CUBIC(实验显示BBR在5%丢包率下仍能维持接近链路容量的吞吐量,而CUBIC在0.1%丢包时吞吐量下降10倍)

混合流量:需要考虑与现有流量的公平性共存问题

在Linux上切换拥塞控制算法很简单:

# 查看可用算法
sysctl net.ipv4.tcp_available_congestion_control

# 切换为BBR
sysctl -w net.ipv4.tcp_congestion_control=bbr

# 永久生效(写入/etc/sysctl.conf)
net.ipv4.tcp_congestion_control = bbr

尾声:持续演进的技术

TCP拥塞控制的故事远未结束。QUIC协议使用UDP作为底层传输,可以更灵活地实现拥塞控制;BBRv3正在开发中;学术界不断提出新的变体来解决RTT公平性、多路径拥塞控制等挑战。

回顾这三十八年的演进,每一步都是对前人假设的重新审视:

  • 从"丢包即拥塞"到"丢包只是丢包"
  • 从"填满缓冲区"到"保持小队列"
  • 从"窗口大小控制"到"速率-延迟模型控制"

Van Jacobson在1988年拯救了互联网;今天的拥塞控制研究者们,正在让这个拯救持续下去。


参考文献

  1. Jacobson, V., & Karels, M. J. (1988). Congestion Avoidance and Control. SIGCOMM ‘88.
  2. Cardwell, N., Cheng, Y., Gunn, C. S., Yeganeh, S. H., & Jacobson, V. (2016). BBR: Congestion-Based Congestion Control. ACM Queue, 14(5).
  3. Ha, S., & Rhee, I. (2011). Taming the Elephants: New TCP Slow Start. Computer Networks, 55(9).
  4. Brakmo, L. S., O’Malley, S. W., & Peterson, L. L. (1994). TCP Vegas: New Techniques for Congestion Detection and Avoidance. SIGCOMM ‘94.
  5. Gettys, J., & Nichols, K. (2011). Bufferbloat: Dark Buffers in the Internet. Communications of the ACM, 55(1).
  6. RFC 5681: TCP Congestion Control.
  7. RFC 8312: CUBIC for Fast Long-Distance Networks.
  8. Cardwell, N., et al. (2025). BBR Congestion Control Algorithms: Evolution, Challenges and Optimization Strategies. ACM Computing Surveys.