给服务器启用 BBR:为什么开、怎么开,以及在 2% 丢包下快了多少倍

2026-10-20 Linux 运维

同样的带宽、同样的线路,为什么有的服务器单线程下载能跑满,有的只有几 Mbps?

多数时候不是带宽不够,而是 TCP 把"丢包"误解成了"拥塞"。这篇文章讲清楚 BBR 改了什么、怎么在 Ubuntu/Debian 上开起来,最后用一次可复现的实验量一下它到底能快多少。

一、BBR 改的到底是什么

传统拥塞控制算法(Linux 默认的 CUBIC)遵循一条朴素规则:丢包 = 网络堵了 = 减速。收到丢包就把拥塞窗口砍半,然后慢慢爬回来。这套逻辑在二十年前的网络里是对的,那时路由器缓冲区浅,丢包几乎只发生在拥塞时。

今天的网络不是这样:

  • 跨境的国际出口、运营商互联点,常年带着 0.5%~3% 的随机丢包;
  • 无线链路(4G/5G、Wi-Fi)本身就会丢包;
  • 云厂商的虚拟网络也会因为瞬时排队而丢包。

在这些链路上,CUBIC 会不停地把"线路抖动"当成"拥塞",窗口刚爬上去就被砍下来,带宽永远吃不满。表现就是:带宽 100M,单线程下载只有 2~5 MB/s,而且忽快忽慢。

BBR(Bottleneck Bandwidth and RTT)换了一套模型:它不再看丢包,而是持续测量两个数——

  • 瓶颈带宽:这条路径最多能跑多快(通过观察 ACK 到达速率估算);
  • 最小 RTT:路径的物理延迟。

两者相乘就是这条管道里"能装多少数据"(BDP)。BBR 把在途数据量稳在这个值附近,并周期性做小幅探测(probe bandwidth / probe RTT)。只要没出现持续排队,它就不因为丢包而减速。

一句话总结差别:CUBIC 在猜"是不是堵了",BBR 在算"这条管子有多粗"。

二、先确认内核支持

BBR 从 Linux 4.9 进入主线,2017 年之后的发行版基本都带。先看三件事:

# 1. 内核版本
uname -r
# 6.8.0-138-generic

# 2. 内核里有哪些拥塞控制算法可用
sysctl net.ipv4.tcp_available_congestion_control
# net.ipv4.tcp_available_congestion_control = reno cubic bbr

# 3. 编译配置里有没有 BBR(=m 表示编成模块)
grep CONFIG_TCP_CONG_BBR /boot/config-$(uname -r)
# CONFIG_TCP_CONG_BBR=m

上面是一台 Ubuntu 24.04 + 内核 6.8 的真实输出,bbr 已经在可用列表里。

如果第 2 步的输出里没有 bbr,按情况处理:

# 模块存在但没加载
sudo modprobe tcp_bbr
lsmod | grep bbr
tcp_bbr                20480  0

如果 modprobe 报 FATAL: Module tcp_bbr not found,说明内核没编这个模块——要么内核版本太老(< 4.9),要么是精简内核。OpenVZ、部分 LXC 容器是和宿主机共享内核的,容器里怎么调都没用,必须让宿主机开。

三、启用:两行配置

不建议直接 sysctl -w 了事——重启就没了。写进 sysctl.d:

sudo tee /etc/sysctl.d/99-bbr.conf > /dev/null <<'EOF'
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
EOF

sudo sysctl --system

两行的分工不一样,第二行才是 BBR 本身:

  • net.ipv4.tcp_congestion_control = bbr:把默认拥塞控制算法换成 BBR;
  • net.core.default_qdisc = fq:把默认排队规则换成 fq(Fair Queue)。

为什么一定要配 fq:BBR 是"按估算速率给数据包打节拍"的算法(pacing),它需要一个能按时间片精确调度发包的队列。fq 就是干这个的。用 fq_codel 也能跑,但官方推荐组合一直是 fq + bbr。

想先试试效果、不写文件,可以用临时参数:

sudo sysctl -w net.core.default_qdisc=fq
sudo sysctl -w net.ipv4.tcp_congestion_control=bbr

重启后失效,适合先验证再固化。

四、验证:四步,缺一步都不算开好

# ① 算法确实切过去了
sysctl -n net.ipv4.tcp_congestion_control
# bbr

# ② 模块确实在内存里
lsmod | grep bbr
# tcp_bbr                20480  0

# ③ 看一条真实连接的算法(新建连接才准)
ss -tin state established '( dport = :443 )' | head -3

ss -tin 的输出里能直接看到算法名和窗口:

# 切换前建立的连接
ESTAB 0 0 10.8.0.5:443 203.0.113.7:51744
     cubic wscale:7,7 rto:204 rtt:38.2/12.1 ato:40 mss:1448 ...

# 切换后新建的连接(第三列是本地地址)
ESTAB 0 0 10.8.0.5:443 203.0.113.9:51802
     bbr wscale:7,7 rto:204 rtt:38.2/12.1 ato:40 mss:1448 ...

每行开头那个单词就是这条连接实际在用的算法——cubic 还是 bbr,一眼就能看出来。

# ④ 队列规则确实是 fq
tc qdisc show dev eth0
# qdisc fq 8001: root refcnt 2 limit 10000p flow_limit 100p buckets 1024 ...

注意第 ③ 步的坑:tcp_congestion_control 只影响新建立的连接,已经 ESTABLISHED 的 SSH、长连接、WebSocket 仍然跑在旧算法上。所以刚改完别急着测速,先断开重连,或者看新连接。

五、实测:2% 丢包下差了 115 倍

"BBR 更快"这句话在什么条件下成立?我在一台 Ubuntu 24.04(内核 6.8)上用隔离的网络命名空间做了对照实验——两条 veth 对接,中间用 tc netem 注入丢包和延迟,完全不碰生产流量。

实验结构:

[snd 命名空间] ── veth ── [rcv 命名空间]
                            └─ netem delay 10ms loss N%

核心命令(完整脚本见文末):

# 建两个命名空间,用 veth 对接
ip netns add snd; ip netns add rcv
ip link add veth-s type veth peer name veth-r
ip link set veth-s netns snd; ip link set veth-r netns rcv
ip netns exec snd ip addr add 10.200.0.1/24 dev veth-s
ip netns exec rcv ip addr add 10.200.0.2/24 dev veth-r
ip netns exec snd ip link set veth-s up
ip netns exec rcv ip link set veth-r up

# 服务端固定算法,客户端反向测下载(server → client)
ip netns exec rcv sysctl -qw net.ipv4.tcp_congestion_control=cubic
ip netns exec snd iperf3 -c 10.200.0.2 -R -t 8 -J

# 注入劣质链路:10ms 延迟 + 2% 丢包
ip netns exec rcv tc qdisc replace dev veth-r root handle 1: netem delay 10ms loss 2%

每种条件跑 3 轮、每轮 6 秒,取中位数(单次测量抖动很大,见下文):

链路条件 CUBIC(中位数) BBR(中位数) 倍差
无丢包、无延迟(veth 基线) 42.6 Gbps 27.4 Gbps 0.64×
2% 丢包 + 10ms 延迟 14.2 Mbps(11.5 / 14.2 / 96.8) 1632.9 Mbps(1648.8 / 1632.9 / 1627.1) 115×
5% 丢包 + 10ms 延迟 6.3 Mbps(11.4 / 6.3 / 5.9) 1320.8 Mbps(1320.8 / 1379.3 / 1210.5) 210×

数据里有三个值得说的点:

1. 无丢包时 BBR 反而更慢(42.6 → 27.4 Gbps)。 这不是测量错误。veth 的 RTT 只有 40 微秒,几十 Gbps 的量级早就撞上 CPU 了——BBR 要给每个包算 pacing 时间戳,每包开销比 CUBIC 高,在纯 CPU 瓶颈下就吃亏。这说明 BBR 不是"无条件更快",它换来的是抗丢包能力,代价是更多 CPU 和更激进的重传。 在万兆以内的真实链路上这个差异可以忽略。

2. 丢包一出现,局面立刻反转。 2% 丢包时 CUBIC 只剩 14.2 Mbps,BBR 还有 1.63 Gbps;丢包升到 5%,CUBIC 崩到 6.3 Mbps,BBR 仍有 1.32 Gbps。原因很简单:CUBIC 每次丢包都砍窗口,丢包率越高,窗口越爬不回来;BBR 只在探测到"排队"时退让,随机丢包不影响它的带宽估计。

3. CUBIC 在丢包链路上极不稳定。 注意 2% 丢包那行里 CUBIC 的三次结果:11.5、14.2、96.8 Mbps——同一条件能差 8 倍以上。这正是"忽快忽慢"的底层原因:窗口被砍到低位后能不能爬回来,取决于丢包落在哪个阶段。BBR 三次都在 1.6 Gbps±1%,稳定得多。

4. BBR 的重传次数高两个数量级(2% 丢包下约 1.8 万次 vs CUBIC 约 150 次)。这是设计使然:BBR 会持续试探更高速率,超发的包丢了就重传。所以开了 BBR 之后 netstat -s 里的重传数会明显上升,这是正常现象,不是故障;但如果重传率高到影响应用(比如 RPC 超时),就要回头看看是不是链路质量太差。

别把这个 115 倍当成承诺。 实验里的瓶颈完全由 netem 制造,没有真实的路由器、没有运营商限速、没有 CDN。真实环境里你能拿到的提升取决于:瓶颈是不是真的在丢包、是不是单流、对端是否也有足够大的接收窗口。跨境单线程下载从 2 MB/s 提到 10 MB/s 是常见结果,从 2 MB/s 提到 200 MB/s 就不现实了——那得看线路本身的带宽。

六、线上怎么看出收益(不用搭实验环境)

开完 BBR,用三条命令就能观察实际效果:

# ① 下载一个大文件,看平均速度
curl -o /dev/null -w 'speed=%{speed_download} B/s  time=%{time_total}s\n' \
     https://你的域名/一个大文件.zip

# ② 看连接的实际 RTT 和窗口
ss -tin state established '( sport = :443 )' | head -20

# ③ 判断丢包发生在哪一段
mtr -rwzc 50 目标IP

mtr 的价值在于区分两种丢包:中间跳丢包但末端不丢,通常是路由器 ICMP 限速,不影响 TCP;从某一跳开始一直丢到终点,才是真的链路丢包——这种情况 BBR 才有发挥空间。

另外要认清方向:BBR 管的是"发送方向"。 你在服务器上开 BBR,优化的是服务器往外发数据(用户下载你的页面、你的文件)。如果瓶颈在服务器接收方向(比如你从外面往服务器传文件),开服务器上的 BBR 没用,得在发送端开。

七、回滚

改动只有两行配置,回滚也就两步:

# 临时回退(立即生效,新连接使用)
sudo sysctl -w net.ipv4.tcp_congestion_control=cubic

# 永久回退
sudo rm /etc/sysctl.d/99-bbr.conf
sudo sysctl --system

net.core.default_qdisc 可以保持 fq——它对普通流量没有坏处,fq 本身就带公平排队,回退算法不必连它一起撤。

八、六个常见的坑

现象 原因 处理
sysctl -n net.ipv4.tcp_congestion_control 还是 cubic 配置文件没被加载,或被更高优先级的文件覆盖 sysctl --system 看加载顺序;/etc/sysctl.conf 里旧值会盖住 sysctl.d
设置成功,重启后又变回 cubic 只用了 sysctl -w,没写文件 落到 /etc/sysctl.d/99-bbr.conf
重启后 qdisc 还是 fq_codel default_qdisc 只对之后新建的队列生效,现有网卡不重建 tc qdisc replace dev eth0 root fq,或重启网络
容器里 modprobe 失败 OpenVZ/LXC 共享宿主机内核,容器无权加载模块 在宿主机上开;或换 KVM 机型
ss -tin 里老连接还是 cubic 拥塞控制只对新连接生效 断开重连,或重启服务
开了 BBR 但下载没变快 瓶颈不在丢包:可能是带宽打满、CDN 限速、对端窗口小 先用 mtr 确认是不是路径丢包

还有一类比较隐蔽的:云厂商的镜像或初始化脚本会重写 sysctl。腾讯云、阿里云的部分镜像会在 cloud-init 里下发网络参数,如果你发现配置写了却被改回去,检查 /etc/cloud/cloud.cfg.d/ 下有没有相关的 sysctl 配置。

九、结论:什么时候值得开

值得开:

  • 跨境、跨运营商的服务器(国际出口常年带丢包);
  • 长肥管道(高带宽 × 高延迟,比如中美之间 200ms RTT);
  • 服务器主要做下载分发(文件、镜像、视频切片)。

收益有限、甚至可能略亏:

  • 纯内网、同城机房,RTT 几毫秒且几乎不丢包——CUBIC 已经能跑满;
  • CPU 已经吃紧的小机器——BBR 的每包开销更高;
  • 上传为主的服务(BBR 在服务器上只管发送方向)。

最后一句实话:BBR 治的是"丢包被误判为拥塞"这一种病。如果线路本身带宽就只有 5 Mbps,开什么算法都变不出 50 Mbps。判断顺序永远是——先量出瓶颈在哪,再决定要不要换算法。


附:实验脚本骨架(Ubuntu 24.04 实测通过,需要 iperf3,用 root 执行)