土法炼钢兴趣小组的算法知识备份

【网络工程】BBR 深度剖析:基于带宽的拥塞控制革命

文章导航

分类入口
network
标签入口
#tcp#bbr#congestion-control#bandwidth#pacing

目录

你在数据中心到用户之间的链路上部署了 CUBIC,一切看起来正常。然后用户开始投诉:页面加载慢。你用 ping 测了一下——RTT 从 20ms 涨到了 200ms。但丢包率几乎为零。

问题出在哪里?路径上某个路由器有一个很大的缓冲区。CUBIC 作为基于丢包的算法,不断增大 cwnd,直到缓冲区溢出才降速。在溢出之前,缓冲区被填满了——这就是 Bufferbloat。数据包没丢,但都在路由器队列里排队等着。延迟飙升,吞吐量反而没变。

2016 年,Google 发布了 BBR(Bottleneck Bandwidth and Round-trip propagation time)。它从根本上改变了思路:不看丢包,直接估算瓶颈带宽最小 RTT,然后按照这两个值控制发送速率。

一、为什么需要 BBR

Kleinrock 的最优操作点

Leonard Kleinrock 在 1979 年的研究指出,网络存在一个最优操作点——在这个点上,吞吐量最大且延迟最低:

# Kleinrock 的网络状态模型:
#
# 吞吐量 ↑          ← BtlBw(瓶颈带宽)
#         |─────────────────────────────
#         |        /
#         |       /  ← 最优操作点在这里
#         |      /      (拥塞开始排队之前的转折点)
#         |     /
#         |    /
#         |   /
#         |  /
#         | /
#         |/
#         +──────────────────────────→ 飞行中数据量
#
# 延迟   ↑
#         |                           /
#         |                          /
#         |                         /
#         |                        /
#         |                       /  ← 丢包开始(CUBIC 在这里反应)
#         |──────────────────────/
#         |← RTprop(传播延迟) →|
#         +──────────────────────────→ 飞行中数据量
#
# 最优操作点 = BDP(带宽延迟积)
# inflight = BtlBw × RTprop 时:
#   - 吞吐量 = 最大(管道被填满)
#   - 延迟 = 最小(没有排队延迟)
#
# 基于丢包的算法(CUBIC/Reno)在最优点的右边运行:
#   它们填满队列直到丢包,然后回退
#   → 永远不会在最优点运行
#
# BBR 的目标:直接在最优操作点运行
#   通过估算 BtlBw 和 RTprop,控制 inflight ≈ BDP

CUBIC 在 Bufferbloat 环境中的问题

# Bufferbloat 场景下的 CUBIC vs BBR

# 假设:瓶颈链路带宽 50Mbps, 传播延迟 20ms
# 路由器缓冲区:500ms 等效(很常见的家用路由器)

# CUBIC 的行为:
# 1. cwnd 增长 → 填满缓冲区 → RTT 从 20ms 升到 520ms
# 2. 缓冲区终于溢出 → 丢包 → cwnd 减少到 70%
# 3. cwnd 恢复增长 → 再次填满缓冲区
# 结果:
#   平均吞吐量 ≈ 50Mbps(不错)
#   平均延迟 ≈ 300ms(灾难性的!比传播延迟高 15 倍)

# BBR 的行为:
# 1. 估算 BtlBw = 50Mbps, RTprop = 20ms
# 2. 控制 inflight ≈ BDP = 50Mbps × 20ms = 125KB
# 3. 不多发,不填满缓冲区
# 结果:
#   平均吞吐量 ≈ 50Mbps(同样好)
#   平均延迟 ≈ 20-25ms(接近传播延迟)

# Google 的实测数据(2016, BBR 论文):
# Google.com:
#   CUBIC → BBR:中位吞吐量提升 2-25%
#   发展中国家(高 Bufferbloat):吞吐量提升 > 100%
# YouTube:
#   RTT 降低 33%(p95)
#   重缓冲率降低 18%

二、BBR v1 的核心模型

BtlBw 和 RTprop 的估算

BBR 的核心任务是持续估算两个参数:

# BtlBw(Bottleneck Bandwidth,瓶颈带宽):
# 路径上最慢链路的带宽
# 估算方法:滑动窗口内最大交付速率
#   BtlBw = max(delivery_rate) over last 10 RTTs
#
# delivery_rate 的计算:
#   对每个被确认的数据包:
#   delivery_rate = delivered_bytes / delivery_interval
#   其中 delivered_bytes = 从发送这个包到收到它的 ACK 之间成功交付的总字节数
#   delivery_interval = 对应的时间间隔

# RTprop(Round-trip propagation time,往返传播延迟):
# 没有排队延迟时的最小 RTT
# 估算方法:10 秒窗口内最小 RTT
#   RTprop = min(RTT) over last 10 seconds
#
# 为什么用 max(delivery_rate) 和 min(RTT)?
# 在排队时:delivery_rate 可能被低估(包在队列中等待)
# 在排队时:RTT 被高估(包含了排队延迟)
# 取最大 delivery_rate → 最接近真实瓶颈带宽(排队最少时的采样)
# 取最小 RTT → 最接近真实传播延迟(排队最少时的采样)

# 在 ss 中观察 BBR 的估算值
ss -ti dst 10.0.0.2 | grep bbr
# bbr wscale:7,7 rto:204 rtt:20.5/0.3
#   bbr:(bw:50.0Mbps,mrtt:20.1,pacing_gain:1,cwnd_gain:2)
#
# bw:50.0Mbps — BBR 估算的瓶颈带宽 (BtlBw)
# mrtt:20.1   — BBR 估算的最小 RTT (RTprop),单位 ms

BBR 的四个状态

BBR v1 有四个状态,构成一个状态机:

stateDiagram-v2
    [*] --> Startup: 连接建立
    Startup --> Drain: BtlBw 不再增长<br>(3 个 RTT 连续不增)
    Drain --> ProbeBW: inflight ≤ BDP
    ProbeBW --> ProbeBW: 持续循环<br>(8 个阶段)
    ProbeBW --> ProbeRTT: RTprop 过期<br>(>10s 未更新)
    ProbeRTT --> ProbeBW: 200ms 后恢复
    
    note right of Startup: pacing_gain = 2/ln2 ≈ 2.89\ncwnd_gain = 2/ln2\n指数增长探测带宽
    note right of Drain: pacing_gain = ln2/2 ≈ 0.35\n排空多余的队列
    note left of ProbeBW: pacing_gain 循环:\n[1.25, 0.75, 1, 1, 1, 1, 1, 1]\n稳态运行
    note left of ProbeRTT: cwnd = 4\n排空队列测量真实 RTprop
# === 状态 1: Startup(启动) ===
# 类似慢启动,但用 pacing 控制发送速率
# pacing_rate = 2/ln(2) × BtlBw ≈ 2.89 × BtlBw
# 目标:快速发现瓶颈带宽
# 退出条件:连续 3 个 RTT,BtlBw 估算值不再增长
#
# 与慢启动的区别:
# 慢启动用丢包作为退出信号 → 已经导致了排队溢出
# BBR Startup 用带宽停止增长作为退出信号 → 更早发现瓶颈

# === 状态 2: Drain(排空) ===
# Startup 阶段多发的数据会填充队列
# Drain 阶段减速排空这些队列
# pacing_rate = ln(2)/2 × BtlBw ≈ 0.35 × BtlBw
# 退出条件:inflight ≤ BDP(队列排空)

# === 状态 3: ProbeBW(探测带宽) ===
# 稳态运行阶段,占大部分时间
# 使用 8 个阶段的 pacing_gain 循环:
# [1.25, 0.75, 1, 1, 1, 1, 1, 1]
#
# Phase 0 (gain=1.25): 加速,探测是否有更多带宽
# Phase 1 (gain=0.75): 减速,排空 Phase 0 可能导致的排队
# Phase 2-7 (gain=1.0): 巡航,以 BDP 速率发送
#
# 每个 Phase 持续一个 RTT
# 一个完整周期 = 8 × RTT

# === 状态 4: ProbeRTT(探测 RTT) ===
# RTprop 有 10 秒的过期时间
# 如果 10 秒内没有观察到更小的 RTT → 进入 ProbeRTT
# 将 cwnd 降到 4 个 MSS,持续 200ms
# 目的:排空所有队列,测量真正的传播延迟
# 问题:cwnd=4 意味着吞吐量骤降 200ms

# 在 ss 中观察当前状态
ss -ti dst 10.0.0.2
# bbr:(bw:50.0Mbps,mrtt:20.1,pacing_gain:1.25,cwnd_gain:2)
# pacing_gain:1.25 → 当前在 ProbeBW 的 Phase 0

BBR 的 Pacing 机制

BBR 与传统拥塞控制的一个根本区别是它主要通过 pacing(节奏控制) 而不是 cwnd 来控制发送速率:

# 传统算法(Reno/CUBIC):
# 控制变量是 cwnd
# 收到 ACK → 发送 cwnd 允许的所有数据 → 突发
#
# BBR:
# 控制变量是 pacing_rate
# pacing_rate = pacing_gain × BtlBw
# 数据包按照 pacing_rate 均匀发送
# cwnd 只是一个安全上限(inflight cap),不是主要控制手段

# BBR 的 pacing 需要内核的 FQ (Fair Queue) 调度器支持
# 检查当前队列调度器
tc qdisc show dev eth0
# qdisc fq 8001: root refcnt 2 limit 10000p flow_limit 100p
#   buckets 1024 orphan_mask 1023 quantum 3028b
#   initial_quantum 15140 low_rate_threshold 550Kbit

# 如果不是 fq, 配置 fq:
tc qdisc replace dev eth0 root fq

# 为什么 fq 对 BBR 很重要?
# BBR 通过 sk->sk_pacing_rate 告诉内核"以这个速率发送"
# fq 调度器读取这个值,均匀地调度每个包的发送时间
# 没有 fq, BBR 的 pacing 就无法生效
# → BBR 退化为纯 cwnd 控制 → 效果大打折扣

# 在高性能场景中,建议用 fq_codel 或 fq:
tc qdisc replace dev eth0 root fq_codel
# fq_codel 结合了 Fair Queue 和 CoDel(主动队列管理)

三、BBR v1 的已知问题

与 CUBIC 的公平性问题

BBR v1 在与基于丢包的算法(如 CUBIC)共享瓶颈链路时存在严重的公平性问题:

# 实测场景:一条 100Mbps 链路,一条 BBR 流 + 一条 CUBIC 流

# 预期:各占 50Mbps
# 实际(BBR v1):
#   BBR:   ~70-80 Mbps
#   CUBIC: ~20-30 Mbps
# BBR 抢占了过多带宽!

# 原因分析:
# 1. BBR 的 ProbeBW 阶段用 gain=1.25 探测
#    → 每 8 个 RTT 有 1 个 RTT 多发 25%
#    → 这些额外的数据导致路由器队列增长
# 2. CUBIC 看到丢包(被 BBR 的多余数据挤出来的)
#    → CUBIC 降低 cwnd
# 3. BBR 看到的 delivery_rate 没变(因为 BBR 自己的包还在正常交付)
#    → BBR 不降速
# 4. CUBIC 反复被挤压 → BBR 占据大部分带宽

# 更极端的场景:多条 BBR 流 vs 少量 CUBIC 流
# CUBIC 流可能几乎被饿死

# 这个问题在 BBR v2 和 v3 中有了显著改善

ProbeRTT 的吞吐量下降

# ProbeRTT 每 10 秒触发一次, cwnd 降到 4
# 在高带宽链路上这意味着:

# 假设:BtlBw = 1Gbps, RTprop = 10ms
# 正常 inflight = 1Gbps × 10ms = 1.25MB
# ProbeRTT inflight = 4 × 1460 = 5.84KB
# 吞吐量骤降:从 1Gbps 降到 ~4.7Mbps
# 持续 200ms

# 对于长 RTT 链路问题更严重:
# RTprop = 200ms, 200ms 的 ProbeRTT 等于 1 个 RTT
# 在这个 RTT 内几乎没有有效传输

# 多条 BBR 流的 ProbeRTT 不同步时:
# 随机的吞吐量抖动 → 对延迟敏感的应用不友好

高丢包率环境的过度激进

# BBR v1 不把丢包当作拥塞信号
# 在真正的拥塞丢包场景中:
# BBR 不降速 → 发更多包 → 更多丢包 → 恶性循环

# 在随机丢包率 > 5% 的环境中:
# BBR v1 的重传率明显高于 CUBIC
# 因为 BBR 不响应丢包,继续按估算的 BtlBw 发送
# 而这些重传包进一步加重了网络负担

# 这在 WiFi 和移动网络中尤其成问题
# 无线链路的丢包不一定是拥塞,但 BBR 也不处理它
# → 大量无意义的重传

四、BBR v2 的改进

BBR v2 是一个重大更新,解决了 v1 的多个工程问题:

# BBR v2 的核心改进:

# 1. 引入丢包和 ECN 信号
# v1 完全忽略丢包 → v2 将丢包和 ECN 作为辅助信号
# 当丢包率超过阈值时,BBR v2 会主动降低 inflight_hi
# 这解决了与 CUBIC 共存的公平性问题

# 2. 改进的 inflight 控制
# v2 引入了 inflight_hi 和 inflight_lo 两个边界:
# inflight_hi: 不导致丢包的最大 inflight(带宽上限)
# inflight_lo: 最近观察到的最小有效 inflight
# BBR v2 在这两个边界之间探测

# 3. 更温和的 ProbeBW
# v2 的 ProbeBW 分为两个子阶段:
# ProbeBW_UP:   增加 inflight 探测更多带宽(类似 v1 的 gain=1.25)
# ProbeBW_DOWN: 减少 inflight 排空队列(类似 v1 的 gain=0.75)
# ProbeBW_CRUISE: 稳态(gain=1.0)
# ProbeBW_REFILL: 恢复 inflight 到 inflight_hi
#
# 关键区别:ProbeBW_UP 在检测到丢包时立即退出
# → 不像 v1 那样无视丢包继续探测

# 4. 更好的 ProbeRTT
# v2 的 ProbeRTT 不再将 cwnd 降到 4
# 而是降到 BDP 的一半
# → 吞吐量下降更温和

# BBR v2 的公平性实测:
# 一条 100Mbps 链路,BBR v2 + CUBIC:
# BBR v2: ~55 Mbps(略多,但接近公平)
# CUBIC:  ~45 Mbps
# 比 v1 的 80:20 分配好得多

五、BBR v3:当前最新版本

BBR v3 是 BBR 的最新迭代,主要增强了 ECN 支持和公平性:

# BBR v3 的主要改进(相比 v2):

# 1. ECN(显式拥塞通知)的深度集成
# v3 使用 ECN 标记作为主要的拥塞信号之一
# 当收到 ECN CE(Congestion Experienced)标记时:
# → 降低 inflight_hi
# → 效果类似丢包信号,但更早(在丢包发生之前)
# 这使得 BBR v3 在 DCTCP 等 ECN 环境中表现更好

# 2. 改进的 Startup 退出
# v3 在 Startup 阶段检测到 ECN 标记时提前退出
# → 减少 Startup 阶段对其他流的影响

# 3. 更精确的模型参数
# v3 对 BtlBw 和 RTprop 的估算做了微调
# 减少了对暂态波动的过度反应

# BBR v3 的内核状态(Linux 6.x)
# BBR v3 在 Linux 内核中是默认的 BBR 版本
# 使用 modprobe tcp_bbr 即可加载
# 没有单独的 "bbr2" 或 "bbr3" 模块名
# 内核版本决定了实际使用的 BBR 版本

# 检查 BBR 版本
# 通过内核版本间接判断:
uname -r
# 5.15.x → BBR v1 (tcp_bbr 模块)
# 6.1+   → BBR v2/v3 (取决于具体版本和补丁)

# 各版本对比表见下文

BBR 版本对比

特性 BBR v1 BBR v2 BBR v3
首次发布 2016 (Linux 4.9) 2019 (实验) 2023 (Linux 6.x)
丢包响应 忽略 作为辅助信号 作为辅助信号
ECN 支持 基础支持 深度集成
与 CUBIC 公平性 差(抢占) 改善
ProbeRTT cwnd 4 MSS BDP/2 BDP/2
Startup 退出 带宽不增长 带宽不增长 + 丢包 带宽不增长 + 丢包 + ECN
ProbeBW 子状态 UP/DOWN/CRUISE/REFILL UP/DOWN/CRUISE/REFILL
inflight 上限 2 × BDP inflight_hi inflight_hi

从 CUBIC 迁移到 BBR 的检查清单

# 迁移前检查:
# 1. 内核版本是否支持 BBR
uname -r
# 至少 4.9,推荐 6.1+ (BBR v3)

# 2. BBR 模块是否可用
modprobe tcp_bbr 2>&1
# 无报错即可

# 3. FQ 队列调度器是否可用
tc qdisc add dev lo root fq 2>&1
tc qdisc del dev lo root 2>/dev/null
# 无报错即可

# 迁移步骤(灰度):
# Phase 1: 在 1% 的服务器上启用 BBR,对比指标 24 小时
# Phase 2: 扩大到 10%,确认无回退
# Phase 3: 全量推广

# 需要监控的指标:
# - 吞吐量(应该持平或提升)
# - p50/p95/p99 延迟(应该降低,尤其 p95+)
# - 重传率(BBR v1 可能上升,BBR v3 应持平)
# - CPU 使用率(BBR 的计算开销略高于 CUBIC,但通常 < 1%)

# 回滚方案:
sysctl -w net.ipv4.tcp_congestion_control=cubic
# 只影响新连接,旧连接需等待断开

六、BBR 的部署与配置

Linux 上启用 BBR

# === 步骤 1: 加载 BBR 模块 ===
modprobe tcp_bbr
# 验证
lsmod | grep tcp_bbr
# tcp_bbr                20480  0

# === 步骤 2: 设置默认拥塞控制 ===
sysctl -w net.ipv4.tcp_congestion_control=bbr
# 验证
sysctl net.ipv4.tcp_congestion_control
# net.ipv4.tcp_congestion_control = bbr

# === 步骤 3: 配置 FQ 队列调度器(关键!)===
tc qdisc replace dev eth0 root fq
# 验证
tc qdisc show dev eth0
# qdisc fq 8001: root refcnt 2 ...

# === 步骤 4: 持久化配置 ===
# /etc/sysctl.d/99-bbr.conf
cat > /etc/sysctl.d/99-bbr.conf << 'EOF'
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
EOF
sysctl --system

# === 步骤 5: 验证生效 ===
ss -ti dst REMOTE_HOST | grep bbr
# 应该显示 bbr 而不是 cubic

需要注意的部署场景

# === 容器环境 ===
# 拥塞控制算法是内核级设置
# 容器共享宿主机内核 → 所有容器使用相同的算法
# 修改需要在宿主机上操作(或使用特权容器)

# Kubernetes 中修改(需要 DaemonSet)
# apiVersion: apps/v1
# kind: DaemonSet
# metadata:
#   name: bbr-tuning
# spec:
#   template:
#     spec:
#       hostNetwork: true
#       containers:
#       - name: tuner
#         image: busybox
#         command: ["sh", "-c", "sysctl -w net.ipv4.tcp_congestion_control=bbr && sleep infinity"]
#         securityContext:
#           privileged: true

# === 云环境 ===
# AWS: EC2 实例通常可以直接切换到 BBR
#   注意:ENA 驱动支持 FQ 调度器
# GCP: 默认已启用 BBR(Google 自己的算法)
# Azure: 可以启用,但需要手动配置

# === 负载均衡器 ===
# LB 终止 TCP 连接时,LB 的拥塞控制算法才生效
# L4 LB (IPVS/透传模式): 不涉及(直接转发)
# L7 LB (Nginx/Envoy): 在 LB 服务器上配置 BBR

七、BBR vs CUBIC 实测对比

测试方法论

# 科学的 A/B 对比需要控制变量

# === 测试环境 ===
# 两台服务器通过可控链路连接
# 用 tc netem 模拟不同网络条件

# 添加延迟和带宽限制
tc qdisc add dev eth0 root handle 1: tbf rate 100mbit burst 256k latency 400ms
tc qdisc add dev eth0 parent 1: netem delay 50ms

# 添加随机丢包(模拟真实网络)
tc qdisc change dev eth0 parent 1: netem delay 50ms loss 0.1%

# === 测试脚本 ===
for algo in cubic bbr; do
    sysctl -w net.ipv4.tcp_congestion_control=$algo
    echo "=== Testing $algo ==="
    iperf3 -c 10.0.0.2 -t 60 -i 10 --json > /tmp/${algo}_result.json
    sleep 5  # 冷却期
done

# === 提取对比数据 ===
for algo in cubic bbr; do
    echo "--- $algo ---"
    echo -n "Throughput: "
    jq '.end.sum_sent.bits_per_second / 1e6' /tmp/${algo}_result.json
    echo -n "Retransmits: "
    jq '.end.sum_sent.retransmits' /tmp/${algo}_result.json
done

典型场景对比结果

# === 场景 1: 数据中心内部(RTT 0.5ms, 10Gbps, 无丢包) ===
# CUBIC: 9.42 Gbps
# BBR:   9.41 Gbps
# 结论:差异不大,CUBIC 更成熟稳定

# === 场景 2: 同城跨机房(RTT 5ms, 1Gbps, 0.01% 丢包) ===
# CUBIC: 940 Mbps
# BBR:   945 Mbps
# 结论:基本相同

# === 场景 3: 跨国链路(RTT 150ms, 100Mbps, 0.1% 丢包) ===
# CUBIC: 72 Mbps
# BBR:   93 Mbps  ← BBR 明显更好
# 结论:高 BDP + 丢包,BBR 优势显现

# === 场景 4: Bufferbloat 环境(RTT 20ms, 50Mbps, 路由器缓冲 500ms) ===
# CUBIC: 48 Mbps, 延迟 ~300ms
# BBR:   47 Mbps, 延迟 ~25ms  ← 延迟降低 12 倍!
# 结论:BBR 在 Bufferbloat 环境中的延迟优势巨大

# === 场景 5: 高丢包无线网络(RTT 30ms, 20Mbps, 2% 丢包) ===
# CUBIC: 14 Mbps
# BBR v1: 18 Mbps,但重传率高
# BBR v3: 17 Mbps,重传率改善
# 结论:BBR 略好,但 v1 的重传问题需要 v3 修复

# === 场景 6: 混合流(BBR + CUBIC 共享 100Mbps) ===
# BBR v1 + CUBIC: BBR 占 75Mbps, CUBIC 占 25Mbps(不公平)
# BBR v3 + CUBIC: BBR 占 55Mbps, CUBIC 占 45Mbps(基本公平)

选型决策矩阵

场景 推荐算法 理由
数据中心内部 CUBIC 成熟稳定,BBR 无明显优势
数据中心内部(ECN 环境) DCTCP 或 BBR v3 ECN 避免丢包,低延迟
面向用户的 Web 服务 BBR 减少 Bufferbloat 延迟
跨国数据传输 BBR 高 BDP 链路利用率更高
CDN 边缘节点 BBR 用户链路多样,BBR 适应性好
视频流媒体 BBR 低延迟,减少 rebuffer
与 CUBIC 流共存 BBR v3 v3 修复了公平性问题
游戏服务器 BBR 延迟敏感场景
数据库复制(内部网络) CUBIC 稳定性优先

八、BBR 的监控与诊断

ss 中的 BBR 状态

# 详细的 BBR 状态信息
ss -ti state established dst 10.0.0.2

# 输出示例:
# bbr wscale:7,7 rto:224 rtt:20.3/0.5 ato:40 mss:1448
#   cwnd:171 ssthresh:164
#   bytes_sent:1568440000 bytes_retrans:14480 bytes_acked:1568425520
#   send 97.8Mbps lastrcv:24 lastack:24
#   pacing_rate 122.2Mbps delivery_rate 97.5Mbps
#   app_limited busy:8400ms
#   bbr:(bw:97.5Mbps,mrtt:20.1,pacing_gain:1.25,cwnd_gain:2)
#   rcv_space:29200 rcv_ssthresh:1448000 minrtt:20.1

# BBR 特有字段解读:
# bbr:(...)          — BBR 算法的内部状态
# bw:97.5Mbps        — 估算的瓶颈带宽 (BtlBw)
# mrtt:20.1          — 估算的最小 RTT (RTprop), 单位 ms
# pacing_gain:1.25   — 当前 pacing 增益
#   1.0  → ProbeBW CRUISE(稳态)
#   1.25 → ProbeBW UP(探测更多带宽)
#   0.75 → ProbeBW DOWN(排空队列)
#   2.89 → Startup(初始探测)
#   0.35 → Drain(排空 Startup 的排队)
# cwnd_gain:2        — cwnd 增益因子

# 持续监控 BBR 状态
watch -n 0.5 "ss -ti dst 10.0.0.2 | grep -A3 bbr"

常见 BBR 问题诊断

# === 问题 1: BBR 配置了但没生效 ===
# 症状:ss 显示的仍然是 cubic 而不是 bbr
ss -ti | head -3
# cubic wscale:7,7 ...  ← 还是 cubic!

# 排查:
# a) BBR 模块是否加载?
lsmod | grep bbr
# 如果为空:modprobe tcp_bbr

# b) 系统设置是否正确?
sysctl net.ipv4.tcp_congestion_control
# 如果不是 bbr:sysctl -w net.ipv4.tcp_congestion_control=bbr

# c) 已建立的连接不会自动切换!
# 只有新连接才使用新设置
# 需要重启应用或等旧连接断开

# === 问题 2: BBR 吞吐量低于预期 ===
# 排查步骤:

# a) 检查是否有 FQ 调度器
tc qdisc show dev eth0
# 如果没有 fq → BBR 的 pacing 不生效
tc qdisc replace dev eth0 root fq

# b) 检查 BtlBw 估算是否准确
ss -ti dst TARGET | grep 'bw:'
# 比较 bw 值和链路实际带宽
# 如果 bw 远小于实际带宽 → 可能有瓶颈

# c) 检查 RTprop 是否合理
ss -ti dst TARGET | grep 'mrtt:'
# 比较 mrtt 和 ping 的最小 RTT
# 如果 mrtt 远大于 ping RTT → BBR 可能没有进入 ProbeRTT

# d) 检查是否被流量控制限制
ss -ti dst TARGET | grep rcv_space
# 如果 rcv_space 很小 → 接收窗口限制了 BBR

# === 问题 3: BBR 导致过多重传 ===
# 症状:切换到 BBR 后重传率明显上升
ss -ti dst TARGET | grep retrans
# bytes_retrans 很大

# 可能原因:
# a) BBR v1 不响应丢包 → 升级到 BBR v3 的内核
# b) 链路丢包率很高(> 5%)→ 考虑退回 CUBIC
# c) FQ 调度器配置问题 → 检查 tc 配置

Prometheus 监控 BBR 指标

# 使用 node_exporter 的 textfile collector 采集 BBR 指标
cat > /opt/bbr_metrics.sh << 'SCRIPT'
#!/bin/bash
METRICS_FILE="/var/lib/node_exporter/textfile/bbr.prom"
{
    echo "# HELP tcp_bbr_connections Number of TCP connections using BBR"
    echo "# TYPE tcp_bbr_connections gauge"
    echo "tcp_bbr_connections $(ss -ti | grep -c '^bbr ')"

    echo "# HELP tcp_cubic_connections Number of TCP connections using CUBIC"
    echo "# TYPE tcp_cubic_connections gauge"
    echo "tcp_cubic_connections $(ss -ti | grep -c '^cubic ')"

    # 提取所有 BBR 连接的平均 BtlBw
    echo "# HELP tcp_bbr_avg_bw_mbps Average BBR estimated bandwidth in Mbps"
    echo "# TYPE tcp_bbr_avg_bw_mbps gauge"
    avg_bw=$(ss -ti | grep -oP 'bw:\K[0-9.]+' | \
        awk '{sum+=$1; n++} END {if(n>0) print sum/n; else print 0}')
    echo "tcp_bbr_avg_bw_mbps $avg_bw"
} > "$METRICS_FILE"
SCRIPT
chmod +x /opt/bbr_metrics.sh

# 每 15 秒采集一次
# crontab: * * * * * for i in 0 15 30 45; do sleep $i; /opt/bbr_metrics.sh; done

九、BBR 与 QUIC

BBR 最初是为 Google 的 QUIC 协议设计的,两者有天然的契合:

# QUIC 与 BBR 的配合优势:

# 1. 用户态实现
# QUIC 在用户态实现 → 拥塞控制也在用户态
# 可以更快地迭代和部署新版本 BBR
# 不需要升级内核

# 2. 精确的 RTT 测量
# QUIC 每个包都有唯一的 Packet Number(不像 TCP 的 SEQ 可以重传)
# → RTT 测量更精确 → BBR 的 RTprop 估算更准
# TCP 的 Karn 算法问题(重传包无法测 RTT)在 QUIC 中不存在

# 3. 无队头阻塞
# QUIC 的多流多路复用 → 一条流的丢包不影响其他流
# BBR 可以独立控制每条流的拥塞

# 4. 连接迁移
# QUIC 支持连接迁移(IP 地址变化时不断连接)
# BBR 需要适应新路径的 BtlBw 和 RTprop
# QUIC + BBR 的连接迁移后重新探测逻辑更成熟

# Google 的 QUIC 实现(Chromium / Envoy)默认使用 BBR
# 在 HTTP/3 (QUIC) 上 BBR 的效果通常优于在 TCP 上
# 因为 QUIC 提供了更好的 RTT 测量和丢包信息

十、工程决策:何时用 BBR

推荐使用 BBR 的场景

# 1. 面向最终用户的服务(Web/API/CDN)
# 用户的网络环境多样:家用路由器、移动网络、跨国链路
# BBR 在 Bufferbloat 环境中的延迟优势对用户体验影响大
# Google、Cloudflare、Akamai 都在使用 BBR

# 2. 高延迟高带宽的跨地域传输
# 跨洲际数据复制、CDN 回源
# CUBIC 的恢复速度在高 BDP 下仍然不够快
# BBR 直接按估算带宽发送,不需要慢慢爬升

# 3. 视频流媒体服务
# 视频流的质量对延迟和吞吐量都敏感
# BBR 减少的 Bufferbloat 直接降低 rebuffer 率
# YouTube 使用 BBR 后 rebuffer 率下降 18%

不推荐使用 BBR 的场景

# 1. 纯数据中心内部通信
# 数据中心内部链路质量可控,Bufferbloat 不严重
# CUBIC 更成熟稳定,出问题时社区经验更丰富
# 如果数据中心支持 ECN → 考虑 DCTCP

# 2. 高丢包率的不可控网络(且无法升级到 BBR v3)
# BBR v1 在高丢包率下重传过多
# 如果内核版本较老(< 6.x),CUBIC 可能更安全

# 3. 与大量 CUBIC 流共存的环境(BBR v1)
# BBR v1 的公平性问题会挤压 CUBIC 流
# 升级到 BBR v3 或统一使用一种算法

十一、案例:CDN 边缘节点启用 BBR 的效果

# 背景:某 CDN 服务商在边缘节点从 CUBIC 切换到 BBR
# 节点分布:全球 30+ PoP,主要服务静态资源和视频
# 内核版本:6.1 (BBR v3)

# === 切换前基线(CUBIC, 采集 7 天平均) ===
# 全球平均 TTFB: 85ms (p50), 340ms (p95)
# 视频 rebuffer 率: 2.3%
# 平均吞吐量: 45 Mbps(单流)
# 重传率: 0.8%

# === 灰度切换(10% 节点,7 天对比) ===

# BBR 组 vs CUBIC 组对比:
#                    CUBIC      BBR       变化
# TTFB p50:          85ms       78ms      -8.2%
# TTFB p95:          340ms      220ms     -35.3%  ← 高延迟用户收益最大
# TTFB p99:          1200ms     480ms     -60.0%
# 视频 rebuffer:     2.3%       1.9%      -17.4%
# 平均吞吐 (单流):  45Mbps     48Mbps    +6.7%
# 重传率:            0.8%       0.9%      +12.5%  ← 略有上升,可接受

# 分地区差异:
# 北美/欧洲(低 Bufferbloat): TTFB 降低 5-10%
# 东南亚/非洲(高 Bufferbloat): TTFB 降低 40-60%
# 移动网络用户: TTFB 降低 30-50%

# 结论:全量推广 BBR,p95+ 延迟改善最为显著
# 对 Bufferbloat 严重的发展中地区用户影响最大

十二、结论

BBR 代表了拥塞控制的范式转换:从”观察丢包然后反应”到”主动估算网络容量然后控制”。这个转变的工程影响是深远的——在 Bufferbloat 普遍存在的互联网上,BBR 能同时提供高吞吐和低延迟。

几个关键认知:

  1. BBR 不是银弹。 它在 Bufferbloat 环境中优势巨大,但在数据中心内部或低延迟可控网络中,与 CUBIC 差距不大。选择算法要看具体场景。

  2. FQ 调度器是 BBR 正常工作的前提。 没有 FQ, BBR 的 pacing 机制无法生效,效果大打折扣。部署 BBR 时必须同时配置 tc qdisc replace dev eth0 root fq

  3. 版本很重要。 BBR v1 的公平性和重传问题在 v3 中得到了显著改善。如果可能,使用最新的内核版本以获得 BBR v3。

  4. 监控 BBR 的状态。 ss -ti 提供了 BtlBw、RTprop、pacing_gain 等关键指标。部署后持续监控这些值,确保 BBR 的估算与实际网络状况一致。

  5. BBR 和 QUIC 是天然搭档。 QUIC 的精确 RTT 测量和用户态实现使 BBR 能发挥最大效果。如果你在使用 HTTP/3, BBR 几乎是默认最佳选择。

拥塞控制只是 TCP 性能的一个维度。下一篇我们将系统性地讲解 TCP 调优——把 sysctl 参数、socket 选项和拥塞控制串起来,形成完整的调优方法论。


上一篇:TCP 拥塞控制经典算法:从 Reno 到 CUBIC

下一篇:TCP 调优实战:内核参数与 socket 选项完全指南

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2025-07-17 · network

【网络工程】TCP 拥塞控制经典算法:从 Reno 到 CUBIC

TCP 拥塞控制是互联网流量管理的核心机制。本文从 AIMD 的数学直觉出发,逐步剖析 Reno、NewReno、BIC、CUBIC 的演进动机与工程差异,通过内核参数观测和实测数据帮助读者理解拥塞窗口行为、选择合适的拥塞控制算法。

2026-04-20 · linux / networking

【Linux 网络子系统深度拆解】TCP 内核实现(下):数据传输与拥塞控制

tcp_sendmsg 把用户数据拷到 sk_buff 就完事了?远没有。后面还有 Nagle 合并、TSQ 限流、cwnd/rwnd 双窗口门控、RACK-TLP 丢包检测、拥塞状态机五态跳转、sk_pacing_rate 软件限速。本文从 Linux 6.6 内核源码拆解 TCP 数据传输的完整路径——从 send() 到 ACK 处理——以及拥塞控制框架 tcp_congestion_ops 的可插拔架构。

2025-07-30 · network

【网络工程】内核网络参数调优:sysctl 全景与实战

Linux 内核网络参数是系统网络性能的基础旋钮。本文从 /proc/sys/net/ 的参数体系出发,系统讲解收发缓冲区自动调优、TCP Backlog 队列、conntrack 连接追踪表、SYN Flood 防护参数、TIME_WAIT 管理,以及参数调优的系统化方法论——先基准、再调整、后验证。


By .