嘿,想象一下早高峰的北京三环。你是司机,前面的车是你的“数据包”,红绿灯和收费站就是“路由器”。如果每个司机都以为自己能无限加速,结果就是全线瘫痪——这就是网络拥塞。
TCP(传输控制协议)作为互联网的基石,它的核心使命之一就是在不撞车的状态下把货送得越快越好。这听起来简单,但实际操作中,TCP不知道前方路况,只能靠“猜”。过去几十年,工程师们为了猜得更准,演化出了一套又一套算法。今天咱们就顺着时间线,把这出“网络交通管理史”聊透。
一、 最初的摸索:慢启动与拥塞避免(1988-1990s)
最早的TCP拥塞控制出自Van Jacobson之手,他在1988年提出的方案奠定了现代互联网传输的基础。这套机制的核心思想非常朴素:先试探,再线性增长,出事了就激进退让。
1. 慢启动(Slow Start):谨慎的起步
想象你刚坐进一辆新车,不知道刹车灵不灵、油门响应如何。你会一脚地板油吗?当然不会。你会慢慢踩,试探车的性能。
TCP的发送窗口(cwnd,Congestion Window)就是这辆车的油门开度。在连接建立初期,cwnd被设为一个较小的值(通常是1个MSS,即最大分段长度)。每收到一个ACK(确认包),cwnd就加1。
这意味着什么?意味着指数增长。
- 第1轮:发送1个包
- 第2轮:收到ACK,cwnd=2,发送2个包
- 第3轮:收到ACK,cwnd=4,发送4个包
- 第4轮:cwnd=8,发送8个包…
这种指数级的增长是为了迅速找到带宽的“底限”,但也不能无限增长,否则瞬间就会撑爆路由器缓冲区。所以,当cwnd达到一个阈值(ssthresh,slow start threshold)时,TCP就切换模式。
2. 拥塞避免(Congestion Avoidance):线性爬坡
一旦进入拥塞避免阶段,TCP变得谨慎多了。它的策略是:每经历一个RTT(往返时延),cwnd就增加1个MSS。
这是线性增长,目的是小心翼翼地逼近网络带宽的极限。只要不丢包,就慢慢加;一旦检测到丢包(通过超时或重复ACK),就认为网络拥堵了。
3. 丢包即信号:TCP的“痛觉”
在早期算法中,丢包是判断拥塞的唯一信号。
- 如果发生超时(Timeout),TCP认为网络非常拥堵,将ssthresh设为当前cwnd的一半,cwnd重置为1,重新慢启动。
- 如果收到3个重复ACK(Triple Duplicate ACK),TCP认为只是轻微拥塞,执行TCP Fast Reconvergence,将ssthresh减半,cwnd设置为ssthresh+3,然后直接进入拥塞避免。
这套机制被称为AIMD(Additive Increase, Multiplicative Decrease,加性增乘性减)。它在低负载时表现尚可,但在高带宽、高延迟的链路(BDP,带宽时延积很大)上,线性增长太慢了,等待时间过长,带宽利用率低下。
4. Cubic:Google时代的优化(2009-2016)
随着互联网视频、云存储的爆发,网络带宽越来越大,RTT也越来越复杂。传统的TCP Reno(AIMD)在高速长距离链路上显得力不从心:它需要很长时间才能恢复到高带宽状态,而且对丢包过于敏感,稍微有点抖动就降速。
2009年,韩国电信(KT)和Georgia Tech的宋章奎(Sangtae Ha)和Raghupathy Sivakumar提出了Cubic算法,随后被Linux内核广泛采用,成为长期的主流。
Cubic的核心改进:三次方函数
Cubic不再使用线性的AIMD,而是引入了一个基于时间的三次方函数来调控cwnd的增长。
\[ W(t) = C \cdot (t - K)^3 + W_{max} \]
听起来很数学?没关系,我们换个说法。
想象你在调节淋浴喷头的水温。Reno的做法是:拧一点点,等一会儿,再拧一点点。如果水太烫(丢包),就把水温放掉一半,重新从冷开始慢慢调。
Cubic的做法是:它记得上次丢包时的最大窗口大小(\(W_{max}\))和达到那个大小所花的时间。它用一条平滑的曲线来预测什么时候可以安全地增加发送量。这条曲线在接近\(W_{max}\)时增长缓慢(避免再次撞车),在远离\(W_{max}\)时增长较快(快速恢复带宽)。
为什么Cubic更好?
- 更快收敛:在丢包后,Cubic能比Reno更快地恢复到合理的带宽水平,尤其是在高BDP链路上。
- 更平滑的波动:三次方曲线比直线更平滑,减少了窗口大小的剧烈震荡,从而降低了队列抖动(Jitter)。
- 公平性:Cubic设计时考虑了与Reno、TCP NewReno等其他算法的共存问题,尽量保证公平共享带宽。
Cubic统治了Linux内核很多年,从2.6.19版本开始成为默认算法,直到现在仍是许多服务器的标准配置。
5. BBR:Google的颠覆性思路(2016至今)
尽管Cubic改进了很多,但它始终有一个根本缺陷:它仍然依赖丢包作为拥塞的信号。然而,现代网络中的丢包并不总是因为“太满了”。
- 有时候丢包是因为无线信号干扰(WiFi抖动)。
- 有时候是因为路由器缓冲区太小(Bufferbloat问题)。
- 有时候是因为运营商的QoS策略主动丢弃了某些包。
在这些情况下,把丢包当成拥塞信号来降速,简直是“反应过度”。你明明路很宽,只是前面有一块石头绊了一下,你却以为整条路都塌了,于是把车扔在路边。
2016年,Google发布了BBR(Bottleneck Bandwidth and Round-trip propagation time)拥塞控制算法。BBR彻底抛弃了“丢包=拥塞”的假设,转而直接测量网络的带宽和RTT。
BBR的两个核心指标
BBR的目标很简单:找到网络的BtlBw(瓶颈带宽)和Rttprop(-propagation时延),然后用这两个值来发送数据,既不填满缓冲区,也不浪费带宽。
- BtlBw:单位时间内能发送的最大数据量。BBR通过持续发送Probe带宽探针(Bursty Packets)来探测当前可用的带宽。
- Rttprop:信号在网络中传播所需的最小时间(排除队列延迟)。BBR会跟踪RTT的最小值,因为这个最小值最接近纯粹的传播时延。
BBR的工作模式
BBR不像传统算法那样线性增长或三次方增长,而是像一个聪明的调音师,动态调整发送速率。
阶段1:Startup(启动期)
BBR开始时假设带宽未知,会以较快的速率发送数据包,目的是尽快探测到瓶颈带宽。它会持续增加发送速率,直到发现RTT开始增加(意味着缓冲区开始填满)。
阶段2:Drain(排水期)
一旦检测到RTT增加,BBR知道缓冲区可能已经满了。它会迅速降低发送速率,把之前积压在缓冲区里的数据包“排空”,让网络恢复到没有队列延迟的状态。这一步非常关键,它解决了传统的Bufferbloat问题。
阶段3:ProbeBw(带宽探测期)
这是BBR最精妙的部分。排空缓冲区后,BBR会进入一个循环,不断用不同的速率发送数据包,以重新探测当前的瓶颈带宽。它会使用一种叫“ pacing rate ”(整形速率)的技术,确保数据包均匀地发送,避免突发流量造成新的拥塞。
在这个阶段,BBR会交替使用高于和低于估计带宽的速率发送,以验证估计的准确性。如果RTT没有增加,说明还有带宽空间;如果RTT增加了,说明估计过高,需要调整。
BBR代码示例(Linux内核实现)
BBR在Linux内核中的实现非常复杂,但核心逻辑可以通过以下简化代码理解:
// 简化的BBR算法核心逻辑伪代码
struct bbr {
u32 cw_max; // 最大窗口大小
u32 rtt_prop; // 传播时延(最小RTT)
u32 btl_bw; // 瓶颈带宽估计
enum bbr_mode {
BBR_STARTUP, // 启动期
BBR_DRAIN, // 排水期
BBR_PROBE_BW, // 带宽探测期
BBR_PROBE_RTT // 探测RTT
} mode;
struct ack_sample last_ack_sample;
// ... 其他状态变量
};
// 当收到ACK时更新状态
void bbr_update_from_ack(struct bbr *bbr, struct ack_sample *sample)
{
// 1. 更新RTT传播时间(取最小值)
if (sample->rtt_us < bbr->rtt_prop || sample->rtt_us == 0)
bbr->rtt_prop = sample->rtt_us;
// 2. 更新瓶颈带宽估计(取最近window_size/rtt的最大值)
u32 bw = div64_u64((u64)sample->acked_sacked * 1000000,
sample->rtt_us);
if (bw > bbr->btl_bw)
bbr->btl_bw = bw;
// 3. 模式切换逻辑
switch (bbr->mode) {
case BBR_STARTUP:
// 如果RTT开始增加,说明缓冲区满了,切换到Drain
if (sample->rtt_us > bbr->rtt_prop + BBR_RTT_THRESHOLD)
bbr->mode = BBR_DRAIN;
break;
case BBR_DRAIN:
// 如果发送速率降到很低,说明缓冲区已排空,切换到ProbeBw
if (tcp_sndWND(sk) < bbr->btl_bw * bbr->rtt_prop / 2)
bbr->mode = BBR_PROBE_BW;
break;
case BBR_PROBE_BW:
// 周期性调整发送速率,探测带宽
bbr_probe_bw_update(bbr, sample);
// 如果RTT最小值已经保持了一段时间,切换到ProbeRTT
if (bbr->rtt_prop_is_recent)
bbr->mode = BBR_PROBE_RTT;
break;
case BBR_PROBE_RTT:
// 短暂暂停发送,测量真实的传播时延
bbr_probe_rtt_update(bbr, sample);
// 暂停结束后,回到ProbeBw继续探测带宽
if (time_after_eq(jiffies, bbr->probe_rtt_stamp + BBR_PROBE_RTT_MIN_DURATION))
bbr->mode = BBR_PROBE_BW;
break;
}
}
// 计算发送速率
static u64 bbr_rate_bytes_per_sec(struct bbr *bbr, u32 pacing_rate)
{
// pacing_rate = btl_bw * gain
// gain是一个系数,在1.0到2.0之间变化,用于探测带宽上限
return bbr->btl_bw * bbr->gain;
}
BBR vs 传统算法:直观对比
| 特性 | Cubic (AIMD) | BBR |
|---|---|---|
| 拥塞信号 | 丢包、延迟抖动 | 直接测量带宽和RTT |
| 缓冲区使用 | 倾向于填满缓冲区 | 主动避免填满缓冲区 |
| 高BDP链路表现 | 较慢恢复,带宽利用率高但延迟高 | 快速收敛,低延迟,高带宽 |
| 无线/不稳定网络 | 性能差,频繁降速 | 稳健,不易受丢包影响 |
| 公平性 | 与Reno等算法共存良好 | 在某些场景下可能抢占更多带宽 |
实际效果
在Google的内部测试和外部网络中,BBR通常能将吞吐率提高2-3倍,同时将延迟降低50%以上。特别是在跨国专线、卫星链路和移动网络中,BBR的优势更为明显。
2017年,BBR被整合进Linux内核4.9版本。现在,Android手机、Chrome浏览器、以及越来越多的Linux服务器都默认使用BBR。
6. 未来展望:从BBRv1到BBRv3
虽然BBRv1已经非常优秀,但它也有一些局限性。例如,它在某些情况下可能表现得“过于激进”,挤压了传统Cubic流量的空间。此外,BBRv1在处理大规模网络中的公平性方面还有争议。
因此,Google在2021年发布了BBRv3,主要改进包括:
- 更好的公平性:BBRv3调整了带宽探测策略,使其更容易与Cubic等现有算法共存。
- 丢包恢复更快:即使发生丢包,BBRv3也能更快地恢复发送速率,而不是像BBRv1那样可能需要较长时间重新探测。
- 更稳定的RTT估计:改进了对传播时延的估计,减少了对队列延迟的误判。
7. 如何启用BBR?
如果你使用的是Linux系统,启用BBR非常简单。以Ubuntu为例:
# 1. 检查当前使用的拥塞控制算法
sysctl net.ipv4.tcp_congestion_control
# 2. 修改配置为BBR
echo 'net.core.default_qdisc=fq' | sudo tee -a /etc/sysctl.conf
echo 'net.ipv4.tcp_congestion_control=bbr' | sudo tee -a /etc/sysctl.conf
# 3. 应用配置
sudo sysctl -p
# 4. 验证是否生效
sysctl net.ipv4.tcp_congestion_control
输出应该是:
net.ipv4.tcp_congestion_control = bbr
对于其他操作系统,如Windows 10/11和macOS,也陆续支持了BBR。Windows 10 Build 17063及以上版本,可以通过PowerShell命令启用:
netsh interface tcp set global congestioncontrol=bbbr
结语:从“撞了才知道疼”到“提前感知路况”
回顾TCP拥塞控制的演进,我们能看到一个清晰的趋势:从被动响应到主动预测,从依赖单一信号到多指标融合。
- 慢启动/拥塞避免:像新手司机,靠撞车(丢包)来学习道路规则。
- Cubic:像经验老道的司机,能用平滑的曲线控制车速,尽量减少急刹急停。
- BBR:像装了雷达和导航的智能汽车,不靠撞车来判断路况,而是实时测量道路宽度和畅通程度,主动避开拥堵。
这套演进不仅仅是算法的优化,更是工程思维的提升。每一代算法都在解决上一代算法遗留的问题,同时引入新的挑战。未来,随着5G、卫星互联网、QUIC协议的普及,拥塞控制算法还会继续进化。但无论如何变化,核心目标始终不变:在有限的网络资源下,让数据流动得更高效、更稳定。
所以,下次当你刷短视频流畅无比,或者下载大型文件速度爆表时,别忘了背后有BBR这样的智能算法在默默调控着每一次数据包的发送。它是互联网隐形的基础设施,让数字世界得以顺畅运行。
