前言

Windows11开启BBR2拥塞控制算法 中,将Windows的TCP拥塞控制算法从CUBIC换成了BBR2。TCP 拥塞控制到底在控制什么?这些算法有什么不同?

事实上,TCP一直在问自己:我应该同时在网络里“放多少数据”才不会把路堵死?一个名叫“拥塞窗口”(Congestion Window)的参数被设定,用于限制发送端能发送的最大数据量。大的cwnd会使得吞吐更高,但也更容易造成排队和丢包;更小的cwnd则会使得延迟更低,但是链路可能跑不满。

拥塞控制算法的本质,其实就是如何动态调整cwnd。如果看起来网络很空,那就增大一些cwnd,使得吞吐量提高;网络如果太“拥堵”,那就减小一点cwnd,使得延迟降低一点。

CUBIC:很平庸,也很稳

CUBIC像是“先加速、再减速、再加速”,目标是让 cwnd 在大窗口区间增长更快

CUBIC 用一个三次函数来控制 cwnd 变化:

  • 丢包后,cwnd 会降到某个值
  • 然后增长会逐渐加速
  • 接近上一次发生丢包时的窗口(Wmax)附近,增长会变慢(更谨慎)
  • 超过 Wmax 后,再加速探测更高带宽

那么代价是什么呢?CUBIC是Loss-Based,换句话说,就是将“丢包”作为最主要的拥塞信号来处理。在没有丢包发生时,会默认认为网络还能承受更多流量;一旦出现丢包,就会重新探测增长。如果网络有深缓冲,即暂存数据包的队列容量特别大时,可能把队列顶得很高,导致RTT 上升,交互体验变差。

NewReno:边缘控制与试探

Reno家族的策略都是“试探与克制”:

  • 加性增(Additive Increase):没看到拥塞就慢慢涨
  • 乘性减(Multiplicative Decrease):一旦丢包就狠狠降

这是一种非常经典、稳定的控制方式(AIMD),能在共享链路上自动形成“公平分配”。而NewReno最大的特点便在其 New 上。当一个窗口里丢了不止一个包,Reno 可能恢复效率很差,而 NewReno 利用 partial ACK(部分确认) ,可以更好地推断“后面还有包丢了”,减少超时与反复降窗。因此,丢包发生后,恢复阶段更聪明一点,但整体仍是典型的 Loss-Based 控制。因此,NewReno的优点是实现成熟、行为可预测、兼容性强,但高带宽与高时延环境下,带宽利用率容易上不去。

BBRv2:猜拳不如理性分析

CUBIC/Reno 家族有个共同点:把丢包当成拥塞信号,但丢包不总是拥塞导致的——无线干扰、链路误码也会丢。

BBR家族换了一种策略:根据丢包来估计丢包不可靠,直接估计“瓶颈带宽”和“传播时延”,用模型算出合适的发送速率更为可靠。它使用BtlBw(瓶颈带宽)和RTprop(最小传播 RTT)进行估算。对于BBRv1,在部分场景会与 Loss-Based 流共存时出现公平性问题。BBRv2针对这一点进行了改进,在“模型驱动”的基础上,更认真地把loss/ECN作为拥塞信号纳入控制,变得更温和了(上限也没BBRv1高)。可见,BBRv2是典型的 Model-Based 控制。

没有拥塞控制的UDP?

UDP 不是“没有拥塞控制”,而是“协议层不自带拥塞控制”。为什么呢?

UDP 的设计目标就是“简单、无连接、无状态” 。它只做很少的事,如端口复用、简单校验、狂发包等。拥塞控制本质是控制系统:要测量、要记忆、要调参,而UDP不建立连接、不维护发送窗口、不追踪 ACK/RTT,也就没有 TCP 那套“反馈回路”来推断拥塞、动态调速。

此外,UDP的应用场景太多了:视频、实时语音、游戏……都用一种拥塞控制会很麻烦。

总的来说,UDP 不带拥塞控制,是为了保持协议极简和通用;真正需要的拥塞控制,被放到了 UDP 之上的协议或应用层来实现。比如HTTP3时代的QUIC,虽然底层是 UDP,但 QUIC 自己实现了连接管理、可靠性与拥塞控制。

如Hysteria2协议,官方将BBR协议魔改后移植到QUIC上,会根据 RTT 变化估算瓶颈带宽,并动态调整速率。此外,还有自研的Brutal算法,核心是固定速率模型:丢包、RTT 变大 不会像传统算法那样自动降速,甚至在达不到目标速率的时候会根据计算的丢包率“补偿”发更多包。