如何在香港服务器的 Linux 环境中启用 BBR 拥塞控制,降低网络游戏跨国玩家延迟?

凌晨 2 点,我还在调试湾仔机房里那排崭新的 2U 服务器。空调出风口对着我呼呼作响,脚边是刚拆封的光模块包装袋。值班群里,美国西海岸和菲律宾玩家吐槽“夜里团战一到就卡、技能延迟飙到 200+ms”。Grafana 上的两条线像心跳一样波动:RTT p95 从 140ms 抬到了 220ms,重传率 在高峰期窜到 4% 以上。带宽其实还没打满,我第一反应是——典型的 Bufferbloat(队列膨胀)。
我在跳线架前蹲了三分钟,决定从传输层下手:把默认的 CUBIC 切到 BBR,再把出口队列换成可精准节流的 fq。这篇手记,就是那一夜我如何在香港服务器上启用和落地 BBR,并把跨国玩家的体感延迟从“能玩但难受”拉回“能打团能连招”。
我的现场环境(供你对照)
不同硬件/内核/网络商会影响效果;你可以把下面当作一个“可复制的标尺”。
机房/网络
- 位置:香港(HKG)
- 运营商:双上联(IX + 国际专线),提供 BGP 接入
- 上行:10GbE(骨干) + 1GbE(测试/备用)
服务器硬件
| 项 | 参数 |
|---|---|
| 机型 | 2U 独立服务器 |
| CPU | AMD EPYC 7443P(24C/48T,基频 2.85GHz) |
| 内存 | 128GB DDR4-3200 |
| 系统盘 | 2 × 1.92TB NVMe(RAID1,mdadm) |
| 网卡 | Intel X710-DA2(双口 10GbE,支持 RSS/多队列) |
操作系统
- CentOS 7.9(默认内核 3.10) → 通过 ELRepo 升级到 kernel-ml(≥ 5.x/6.x)
- 部分边缘代理/房间服跑在 Ubuntu 20.04 LTS(5.4 内核)
业务侧
- 游戏房间服(TCP:匹配/登录/聊天/WebSocket;UDP:状态同步/帧数据)
- 前置接入:Envoy + Nginx(TLS 终止 & 反向代理)
为什么是 BBR?一句话原理 + 三张图的认知
- BBR(Bottleneck Bandwidth and RTT)不是靠丢包来判断拥塞(和 Reno/CUBIC 不同),而是估算链路瓶颈带宽与传播时延,用**精确节流(pacing)**把发送速率卡在“既不饿网、也不挤队列”的水平。
- 当晚我们的问题不是“带宽不够”,而是突发业务堆在出口队列,让玩家的排队时延变成了主要矛盾。BBR + fq 能直接命中这个问题。
- BBR 只对 TCP 流直接生效;但把根 qdisc 换成 fq 后,UDP 也能受益于更合理的队列调度(突发被平滑,排队更短)。
经验提示:开启 BBR 后,“吞吐更稳 + p95/p99 延迟更低”一般是可以预期的;但极限吞吐在某些链路上未必高于 CUBIC。我们的目标是战斗场景体感而非极限测速成绩。
升级/启用 BBR 的两条路径
- 路径 A(CentOS 7):先升级到新内核(ELRepo 的 kernel-ml),再切换 qdisc 与 TCP 拥塞算法。
- 路径 B(Ubuntu 20.04+/Debian 10+):原生内核即可启用,大多不需要换核。
下面我把 CentOS 7 的“完整落地”写成可复制的 Runbook;Ubuntu 也附上简表。
路径 A:CentOS 7 一次到位的实操 Runbook
目标:从 3.10 内核切到 ≥5.x/6.x(自带 BBR),将根队列设为 fq,拥塞控制设为 bbr,并完成网卡/中断/容器等周边收尾。
风险共识与回滚预案(真的很重要)
维护窗口:至少 30 分钟,可回滚(保留旧内核)
回滚方法:GRUB 选择旧内核启动;或 grub2-set-default 切回旧条目
外围检查:
第三方内核模块(DPI/加密卡/存储 HBA)兼容性
监控/采集 Agent(eBPF 版本、内核头文件)
1)确认当前内核与可用拥塞算法
uname -r
sysctl net.ipv4.tcp_available_congestion_control
sysctl net.core.default_qdisc
预期:3.10 环境看不到 bbr,default_qdisc 多为 pfifo_fast。
2)启用 ELRepo & 安装 kernel-ml
# 导入 key 与源
sudo rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
sudo yum install -y https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm
# 安装新内核(长期稳定/主线之一)
sudo yum --enablerepo=elrepo-kernel install -y kernel-ml
# 查看可用的内核条目
sudo awk -F\' '/menuentry / {print $2}' /etc/grub2.cfg
注:生产上我倾向于 kernel-ml;如果你更保守,也可以选 kernel-lt(长期支持)。
3)设置默认启动条目(BIOS/UEFI 两种路径)
# BIOS 机器
sudo grub2-set-default 0
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
# UEFI 机器
sudo grub2-set-default 0
sudo grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg
重启:
sudo reboot
4)重启后验证内核与 BBR 可用性
uname -r
sysctl net.ipv4.tcp_available_congestion_control | cat
预期:输出包含 bbr cubic reno 之类。
5)切换根队列为 fq,拥塞算法为 bbr(永久生效)
编辑 /etc/sysctl.d/99-bbr.conf:
net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr
# 推荐的 socket 缓冲区与 backlog 调优(按需)
net.core.rmem_max=268435456
net.core.wmem_max=268435456
net.ipv4.tcp_rmem=4096 87380 268435456
net.ipv4.tcp_wmem=4096 65536 268435456
net.core.somaxconn=4096
net.ipv4.tcp_fastopen=3
加载并验证:
sudo sysctl --system
sysctl net.ipv4.tcp_congestion_control
sysctl net.core.default_qdisc
6)确认 qdisc 生效(对物理网卡/聚合口分别确认)
# 以 eth0 举例
sudo tc -s qdisc show dev eth0 | sed -n '1,10p'
预期:根队列为 fq,limit/flow_limit/quantum 等参数可见。
7)多队列网卡与中断亲和(X710/RSS)
高并发房间服下,软中断负载可能成为隐藏瓶颈。把队列拉开、避免单核热区,可进一步降低抖动。
# 查看中断分布
cat /proc/interrupts | egrep -i 'ixgbe|i40e|ens|eth'
# 临时示例:将 eth0 的 RPS 打到 0-7(8核示例)
echo f0 > /sys/class/net/eth0/queues/rx-0/rps_cpus
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries
for i in /sys/class/net/eth0/queues/rx-*/rps_flow_cnt; do echo 4096 > $i; done
# 生产建议:结合 irqbalance 或手工按 NUMA 绑核
8)容器/命名空间的 qdisc 覆盖
Docker/Podman 下,容器侧使用 veth,root qdisc 需要在宿主机的 veth 对端设置:
# 找到 veth 对端(示意)
ip link
sudo tc qdisc replace dev vethXXXX root fq
我在那晚踩的一个坑:宿主机 eth0 是 fq,容器内还在用 pfifo_fast,导致部分流量仍然排队膨胀。把 veth 对端也换成 fq 后,UDP 抖动肉眼可见地收敛。
9)NAT/防火墙与连接跟踪
并发上来后,conntrack 槽位不足会放大重试与排队:
sudo sysctl -w net.netfilter.nf_conntrack_max=2621440
sudo sysctl -w net.netfilter.nf_conntrack_buckets=655360
路径 B:Ubuntu/Debian 简表
# 启用 BBR(5.4+ 通常原生可用)
sudo bash -c 'cat >/etc/sysctl.d/99-bbr.conf' <<'CONF'
net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr
CONF
sudo sysctl --system
sysctl net.ipv4.tcp_congestion_control
sudo tc -s qdisc show dev eth0 | head
观测与验证:不是“开了就算”,要看“怎么跑”
1)看 TCP 连接是否真的在用 BBR
# 选择一个活跃 TCP 连接 PID
ess -tna | grep ESTAB | head
# 查看拥塞算法、拥塞窗口、pacing 等
sudo ss -ti dst :443 | sed -n '1,30p'
预期:能看到 cong bbr、pacing_rate、delivery_rate 等字段。
2)合成压测(跨地域)
我用三台探针(东京、新加坡、洛杉矶)跑 iperf3 和 mtr,压测 TCP/UDP 的延迟与抖动:
iperf3 -c <HKG_IP> -t 60 -P 4
iperf3 -u -c <HKG_IP> -b 50M -t 60
mtr -rwzbc100 <HKG_IP>
3)前后对比数据(节选)
业务尖峰(UTC+8 晚上 22:00-23:00)窗口的抓取。单位:毫秒/百分比。
| 地区 | 指标 | 调整前(CUBIC+pfifo_fast) | 调整后(BBR+fq) | 变更 |
| US-West(洛杉矶) | RTT p50 | 145 | 132 | -8.9% |
| RTT p95 | 221 | 176 | -20.4% | |
| RTT p99 | 268 | 209 | -22.0% | |
| TCP 重传率 | 3.8% | 1.2% | -2.6pp | |
| UDP 抖动(ms) | 12.1 | 8.4 | -30.6% | |
| 菲律宾(马尼拉) | RTT p50 | 68 | 61 | -10.3% |
| RTT p95 | 112 | 87 | -22.3% | |
| RTT p99 | 149 | 115 | -22.8% | |
| TCP 重传率 | 2.1% | 0.9% | -1.2pp | |
| UDP 抖动(ms) | 7.8 | 5.5 | -29.5% |
解释:UDP 的抖动下降,主要来自 fq 根队列 的突发平滑与更公平的排队,而非 BBR 本身(BBR 作用于 TCP)。
业务侧联动:别让应用层“打回原形”
WebSocket/长连接:启用 TCP_NODELAY,避免 Nagle 延迟;遵循内核 pacing,不做用户态延迟发送。
Envoy/Nginx:
关闭过度的 proxy_buffering,对实时通道使用更小的 proxy_buffers。
开启 reuseport,把 accept 压力均摊到多核。
SO_MAX_PACING_RATE:如果你有大包突发上传(如录像/回放),可以按流量类型为套接字设置上限,让 pacing 更可控。
Nginx 片段示例:
worker_processes auto;
worker_rlimit_nofile 200000;
events { use epoll; multi_accept on; }
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
# 按需调小实时接口的缓冲
proxy_buffering off;
}
stream {
server {
listen 7001 reuseport;
proxy_pass backend_pool;
}
}
常见坑位与我的处理笔记
只改了拥塞算法,忘记换根 qdiscBBR
需要精确节流;pfifo_fast 不支持 pacing。必须把 default_qdisc 换成 fq(fq_codel 也可,但我在高并发房间里更偏好 fq 的流级别公平)。
容器 veth 忘记配置
qdisc宿主生效、容器不生效,表现为 UDP 抖动改善有限。把 veth 对端也换成 fq 即可。
老内核 + Virtio 的 GSO/TSO
兼容性过旧内核的 pacing 和大包分片存在已知边角。升级到 5.x/6.x 后基本解决。不建议在 3.x 上“硬开” BBR 补丁。
队列太深
某些网关出厂 txqueuelen 夸张大,短流排队严重:
ip link set dev eth0 txqueuelen 1000 # 按需要
conntrack 槽位不够
高峰期新建连接抖动、握手重试。增大 nf_conntrack_max/buckets 并监控饱和度。
测评方法陷阱
纯 speedtest有时反映不出改动价值。要看 p95/p99 延迟、重传率、UDP 抖动,并在业务时段测。
跨境路由与对端队列
我们在一段时间内观察到某电信对端“晚高峰丢包”,BBR 无法治愈一切——该打的路由/专线还是要打,必要时在 HKG 前加 边缘 PoP/Relay。
高阶补充:qdisc 的取舍与参数小抄
fq:支持 pacing、流级别调度,默认即可;可以按需调 flow_limit(默认 100)以容纳更多并发流。
fq_codel:同样支持 pacing,并自带主动队列管理(AQM),在复杂混部场景有优势。
# 切换为 fq_codel(如果你偏好 AQM)
sudo tc qdisc replace dev eth0 root fq_codel
sudo tc -s qdisc show dev eth0 | head
复盘与结尾:凌晨 3:40 的那条弹幕
重启后一刻,监控的重传率曲线先掉了一截。接着 tc 的统计里,drops 不再连续增长。我们把 mtr 又跑了一轮,p95 从 220ms 掉回 170ms 左右。三点半,西海岸那边还在打架,公屏上飘过一句:“今晚不卡,能连上三段了。”
我靠在机柜门上,听着风机声,脑子飞快过一遍刚才的改动清单——这不是“换个算法”的玄学,而是一套面向排队时延的工程化组合拳:新内核 + BBR + fq + 中断与队列治理 + 业务侧配合。至于你在自己的环境如何拿捏节奏?
建议先做一台影子服试点:两周;再在低峰灰度;最后在业务高峰前完成全量切换——用数据说话。
附:一键化脚本(CentOS 7)
仅供参考,请在测试机验证后再上生产。
#!/usr/bin/env bash
set -euo pipefail
# 1) 安装 ELRepo & kernel-ml
rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
yum install -y https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm
yum --enablerepo=elrepo-kernel install -y kernel-ml
# 2) 设默认内核
if [ -d /boot/efi/EFI/centos ]; then
grub2-set-default 0
grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg
else
grub2-set-default 0
grub2-mkconfig -o /boot/grub2/grub.cfg
fi
echo "net.core.default_qdisc=fq" > /etc/sysctl.d/99-bbr.conf
echo "net.ipv4.tcp_congestion_control=bbr" >> /etc/sysctl.d/99-bbr.conf
cat <<EOF >> /etc/sysctl.d/99-bbr.conf
net.core.rmem_max=268435456
net.core.wmem_max=268435456
net.ipv4.tcp_rmem=4096 87380 268435456
net.ipv4.tcp_wmem=4096 65536 268435456
net.core.somaxconn=4096
net.ipv4.tcp_fastopen=3
EOF
sysctl --system
# 3) 可选:提高 conntrack
sysctl -w net.netfilter.nf_conntrack_max=2621440
sysctl -w net.netfilter.nf_conntrack_buckets=655360
# 4) 提示
echo "[OK] Kernel 安装完成,请执行 reboot 后验证:uname -r && sysctl net.ipv4.tcp_congestion_control"
如果你此刻也站在香港机房里,手里捏着扳手、盯着那条 p99 曲线,那么我建议你:先把排队问题打穿。BBR 不是银弹,但在我们这类“跨国玩家、晚高峰突发”的游戏场景里,它是最务实的一记重拳。祝你今晚也能早点收工。