TCP 拥塞控制核心机制:从慢启动到 BBR
TCP 拥塞控制核心机制:从慢启动到 BBR
本文拆解 TCP 如何"猜"出可用带宽:经典四阶段算法的演进逻辑、丢包作为信号的局限,以及 BBR 为什么会把规则重写一遍。
为什么需要拥塞控制?
没有拥塞控制的世界:
所有发送端都拼命往网络灌 → 路由器队列爆满 → 大面积丢包
→ everyone 重传 → 更堵 → 拥塞崩溃(1986 年 Internet 就真实发生过全局吞吐骤降)
TCP 的任务:在"不知道链路容量"的前提下,自己试探出能跑多快,
并且不比邻居贪心(公平性),也不把中间设备压垮。
两个约束合起来决定发送窗口:
cwnd(拥塞窗口)—— 网络还能吃多少
rwnd(接收窗口)—— 对方缓冲还剩多少
实际发送量 = min(cwnd, rwnd)
经典四阶段(Reno 视角)
经典 Reno 的发送速率随时间大致是一条"先指数、后线性、丢了减半"的曲线。刚开始是慢启动,cwnd 从初始值(一般 10 个 MSS,约 14KB)起步,每收到一个 ACK 就加 1 个 MSS,效果是每个 RTT 翻一倍,也就是指数增长。它不是"慢慢传",而是先小步快探,因为此时并不知道链路能承受多少。
当 cwnd 达到 ssthresh 后进入拥塞避免,改成每个 RTT 只加 1 个 MSS,从指数增长转为线性增长,放慢试探速度。一旦发现丢包:收到 3 个重复 ACK,就立刻快速重传丢失的那一段(不用等超时);随后进入快速恢复,把 cwnd 减半、ssthresh 也设为 cwnd/2,从一半继续线性增长。如果等不到重复 ACK 而是超时,Reno 会把 cwnd 打回 1,重新慢启动,代价非常大。
核心哲学:AIMD(加法增、乘法减)——线性增长保证公平收敛,
乘性下降保证大家"一起退让",这是 Reno/CUBIC 家族的公共底座。
队列视角:Bufferbloat 与丢包信号
经典算法把"丢包"当网络拥塞的唯一信号,但现代网络有两个干扰项:
1. 随机丢包(无线误码、光路抖动)
→ 算法误判为拥塞,无谓降速
2. 缓冲膨胀(Bufferbloat)
路由器队列很深:不丢包,但 RTT 从 10ms 涨到 300ms
→ cwnd 一路飙,延迟爆炸,吞吐反而变差(视频通话卡死的主因)
问题本质:靠"等爆队"来探测带宽 = 用高延迟换吞吐
于是出现两条改良路线:
A. 更早、更细地看拥塞征兆(ECN、延迟梯度)
B. 直接建模测带宽(BBR)
各算法一览
| 算法 | 关键改进 | 问题/定位 |
|---|---|---|
| Tahoe | 超时回到慢启动起点 | 太保守 |
| Reno | 3 dupACK → 快速重传/恢复 | 超时惩罚重;高带宽长链路吃不满 |
| Vegas | 用 RTT 差值预判拥塞(而非等丢包) | 与 Reno 共存时吃亏(退让过早),未普及 |
| NewReno | 一个 RTT 内多段丢失的恢复修正 | Linux 默认家族基础 |
| CUBIC | cwnd 按三次函数增长,靠 W_max 记忆探针 | 与 RTT 无关,高带宽友好;Linux 默认 |
| Copa | 以"延迟梯度"判断自己在瓶颈队列的哪一侧 | 公平性博弈强,研究热点 |
| BBR | 估计带宽×RTprop,主动不填队列 | 见下文 |
CUBIC 直觉
丢包后从 W_max 按 cubic 曲线回升:
离 W_max 远 → 增得快(快速恢复)
接近 W_max → 平台期微增(探测是否还能更高)
超过后 → 缓慢回落稳定
好处:增长节奏与 RTT 解耦(长肥管道不再吃亏),
高带宽下吞吐恢复极快,因此成为 Linux 默认。
BBR:把"猜丢包"换成"量带宽"
BBR(Google,2016;BBRv2 2019 改公平性)不再等丢包,而是持续估计两个物理量:
BtlBw = 链路瓶颈带宽(近几个 RTT 交付速率的最大值)
RTprop = 最小往返时延(近 200s 内 RTT 最小值,≈ 纯传播延迟)
目标:发送量 = BDP = BtlBw × RTprop
在"刚好填满管道、不填队列"的点全速跑
pacing gain 控制每个时片的发送速率;
cwnd 上限 = BDP × 2(只防超大队列)
状态机
START 慢启动(用 2.88 的高增益快速拉满,按丢包或
"增长<2%"判据退出,而非等队满丢包)
↓
DRAIN 把慢启动灌多的排队清掉(gain 1/2.88),降到 RTT 回落
↓
PROBE_BW 稳态:以 1 倍带宽巡航,周期内一轮 gain=1.25 超发探测
(4 个 RTT 维持 + 1 个 RTT 探测)
↓
PROBE_RTT 长期没看到更低的 RTT → 限时压到 4 个 packet 的极小量,
排空队列重新量一次"干净" RTprop,然后回 PROBE_BW
BBR 的强项与争议
强项:
- 有损/随机丢包链路(WiFi、跨洋)不再被误判降速
- 低延迟下高吞吐(不把队列当缓存用)
- 对 shallow buffer 友好,缓解 bufferbloat
争议(v1 为主):
- 与丢包型算法共存时会"抢占"队列(公平性吃亏/占便宜双面)
- 对整形器(token bucket 限速)可能测不满突发而低估带宽
- 与 ECN/QoS 设备行为交互有坑
v2 改进:降 gain 1.25→1.13、对 ECN 标记更敏感、引入损失上限与
主动 fairness 信号,收敛更接近传统算法。
工程落地备忘
# Linux 启用
net.ipv4.tcp_congestion_control = bbr
net.core.default_qdisc = fq # 必须 fq/fq_codel(pacing 靠它)
ss -ti -p # 观察 delivery rate / rtt_min
常见误区:只开 bbr 不配 fq;容器里 sysctl 没生效;
短连接场景收益有限(还没到 PROBE_BW 就结束了)
实战:怎么判断该用哪种
| 链路特征 | 推荐 | 理由 |
|---|---|---|
| 数据中心内网、浅队列 | CUBIC / DCQCN | 丢包率极低,传统算法表现稳定 |
| 跨洋/高 RTT 大文件 | BBR 或 CUBIC+large init window | 慢启动窗口与带宽探测都关键 |
| 移动网络、随机丢包 | BBRv2 | 不把误码当拥塞 |
| 音视频实时通话 | BBR/GCC(RTP 层的拥塞控制) | 延迟优先于吞吐 |
| CDN 静态分发 | BBR 普及中 | 长尾链路质量参差 |
总结
| 维度 | 经典派(Reno/CUBIC) | BBR |
|---|---|---|
| 拥塞信号 | 丢包(队满的代理指标) | 带宽/时延实测 |
| 控制目标 | 填满队列且不溢出 | 填满管道且不填队列 |
| 队列长度 | 天然偏深 | 偏浅、低延迟 |
| 公平性 | AIMD 自收敛 | v1 吃亏点,v2 补丁 |
| 部署现实 | Linux 默认 CUBIC | 云厂商/CDN 逐步铺 |
理解 TCP 拥塞控制的关键在于:传统算法是"以丢包为唯一反馈的分布式猜测游戏",优点是简单公平、缺点是拿延迟和队列当筹码;BBR 换了范式——把网络当可测量的物理系统,用交付速率和最小 RTT 建模出 BDP 并贴着上限跑,代价是需要 fq 这类精准 pacing 与更复杂的公平性补丁。
- 点赞
- 收藏
- 关注作者
评论(0)