
带宽跑不满这件事我踩过的坑比想象中多。有一年帮朋友看一台跨地域的备份机1Gbps 的口子scp 单线程只跑到 30MB/s第一反应是硬盘不行换成 NVMe 还是那个数第二反应是被限速问了机房说没限最后ss -ti一看拥塞算法还是 cubic改成本地编译的 BBRplus 模块之后同一条命令跑到 90MB/s 以上峰值摸到 110MB/s。从那以后凡是遇到「带宽明明够、传输就是上不去」的 linux 机器我会先看一眼拥塞控制这一层。这篇就围绕 BBRplus 展开怎么在 linux 上安装、怎么判断装的是真货、以及装完之后「加速失败」——也就是明明看着生效了却没效果——这一类问题怎么一步步排。BBRplus 不是主线内核里的东西它是一个社区补丁分支需要在原内核上打补丁重编或者直接换成打包好的内核正因为绕了这一道出问题的点比开一个开关多得多。文章适合已经能熟练操作 linux、手上有自己的服务器或虚拟机、想搞清楚「为什么装了没效果」的人完全没碰过内核模块编译的也能跟我会把每一步的意图讲清楚。1. 先搞清楚 BBRplus 到底改了什么1.1 带宽跑不满问题常常出在拥塞控制这一层TCP 发送端有一个「在途数据量」的概念也就是已经发出去但还没被确认的字节数。这个数字太小管道空着带宽浪费太大路由器缓冲区被塞满排队延迟飙升丢包之后又集体降速。拥塞控制算法干的就是这件事决定在途数据量该是多少。传统上主流的做法是丢包驱动比如 CUBIC。它的逻辑很像开车不认路、靠撞墙刹车不断加大在途数据量直到某个路由器缓冲区溢出丢了包才意识到「哦超了」然后把窗口砍一半再来。这种策略在低延迟局域网里没问题一旦链路 RTT 拉长、缓冲区又浅就会出现「撞墙—回退—再撞墙」的循环平均吞吐自然上不去还顺带把延迟顶起来。很多人在自己的服务器上做压力测试发现时延抖动大、曲线像锯齿根子就在这。BBR 换了个思路它不去猜「我能塞多少」而是持续测量两个量瓶颈带宽 BtlBw 和最小往返时延 RTprop。两者的乘积就是那条链路理论上能容纳的数据量。发送速率贴着这个估算值走既不去撞缓冲区也不让管道空着。用生活类比就是CUBIC 是闭着眼靠碰撞摸边界BBR 是拿着测距仪匀速过弯。这里有个容易被忽略但非常关键的性质拥塞控制只在发送端起作用不需要对端配合也不需要中间设备支持。这是它性价比高的原因——你只需要改自己这台机器。反过来说如果你的瓶颈在「下载」方向也就是数据是别人发给你的那你这台机器上的 BBRplus 一点用都没有得去调对端的发送端。我见过不少人给下载慢的机器装了三天模块最后发现方向搞反了。1.2 BBR、BBRplus 和主线内核自带的版本差异先把几个容易混的名字理清楚这也是后面排查问题的基础。名称来源主要特征维护状态CUBIClinux 主线默认丢包驱动高带宽低延迟链路表现好长肥管道吃亏主线持续维护BBRv1linux 4.9 起进主线模型驱动估算 BtlBw 与 RTprop高丢包链路优势明显主线维护BBRplus社区补丁分支多数基于 4.14 系列在 BBRv1 基础上调整了探测节奏与丢包响应高丢包环境下掉速比原版少一些社区维护活跃度不高BBRv2 / v3主线外的补丁集引入对 ECN 的响应、改进与其他算法的共处表现补丁形式未进主线需要说清楚的是BBRplus 的分支不止一份不同打包者手里的 diff 不完全一样有的把算法注册名写成bbrplus有的为了兼容性同时注册了bbr。所以后面我给出的命令里你看到的名字要和tcp_available_congestion_control里实际列出来的对上不能照抄。另一个现实是5.10、5.15、6.1 这些 LTS 内核自带的 BBR 已经相当能打了。我这几年测下来跨地域、丢包率在 0.5% 以内的链路上主线 BBR 和 BBRplus 的差距基本在测量误差范围内只有在丢包率上到 2% 以上、RTT 又超过 150ms 的烂链路上BBRplus 才能看出一点优势。如果你只是想让自家网站和文件服务更顺畅先把主线 BBR 开起来、把fq队列规则配上八成问题就解决了没必要折腾编译。1.3 哪些场景值得折腾哪些纯属浪费时间我把这些年遇到的情况分个类你对照自己的实际情况看值得上 BBRplus 的场景跨地域的大文件分发和备份、自建对象存储的回源拉取、视频推流与拉流服务、远程桌面和串流、以及任何需要压满上行带宽的长连接任务。这些场景的共同点是单条 TCP 连接跑不出物理带宽而链路又有一定丢包。不值得折腾的场景同样明确内网机器之间的通信RTT 不到 1ms换什么算法都一样带宽本身已经被业务打满的机器拥塞控制解决的是「用不满」不是「不够用」以 UDP 为主的业务比如自建的游戏服务或语音服务BBR 只管 TCP对 UDP 毫无影响还有 OpenVZ、LXC 这类共享宿主内核的容器模块根本加载不进去后面会专门讲。还有一个判断标准很实用先跑一次同机房的 iperf3再跑一次跨地域的 iperf3。同机房能跑满、跨地域跑不满换拥塞控制有意义两边都跑不满就别在这层浪费时间了先去查磁盘 IO、CPU 和网卡中断。2. 安装前的环境盘点与方案选型2.1 三件事先定生死内核版本、虚拟化类型、模块签名动手之前三条命令能帮你省下大量返工时间。第一条看内核版本uname -rBBR 相关的接口在 4.9 以后才有。如果输出是 3.10 开头那多半是 CentOS 7 系列这种内核上没有对应实现必须先换内核才有得谈。我见过最离谱的情况是在 2.6.32 上折腾了两个小时最后才发现基础条件根本不成立。第二条看虚拟化类型systemd-detect-virt输出kvm、vmware、xen说明你有独立内核可以自由加载模块输出openvz、lxc、docker说明你共享宿主内核你没有权限加载任何模块也没法换内核。这类环境的唯一出路是联系宿主方在宿主机上开启客户端做什么都没用。这一条是「加速失败」里最常见、也最容易被误判成配置问题的原因。第三条看是否开启了安全启动mokutil --sb-state输出SecureBoot enabled的话未签名的第三方模块会被内核直接拒绝加载日志里会留下module verification failed之类的字样。这不是 bug是安全策略处理方式要么导入自己的密钥并做 MOK 登记要么在固件设置里关掉。云主机一般不受影响本地物理机和部分虚拟机要注意。顺带确认一件事内核头文件或者完整源码树在不在。ls -l /lib/modules/$(uname -r)/build这个软链接必须存在且指向真实目录。它指向一个不存在的路径时编译会以「找不到头文件」的形式报错但根因其实在这里。2.2 三条安装路线各自适合什么人路线做法优点风险适合谁整内核替换安装别人打包好的 BBRplus 内核 deb/rpm重启生效一次到位参数已调好换内核意味着网卡驱动、存储驱动全变重启后有一定概率起不来有控制台或救援模式、愿意承担重启风险的机器树外模块编译保留现有内核只把tcp_bbrplus.ko编出来加载不动内核风险可控回滚只需卸载模块需要本地编译环境内核一升级模块就失效生产环境、在线业务多数人的选择直接用主线 BBR加载内核自带的 BBR 模块改两个 sysctl零编译零重启没有 BBRplus 的调整99% 的常规需求我个人的偏好很明确能不动内核就不动内核。整内核替换这条路在 VPS 上有个经典事故场景——装完重启网卡驱动换了版本不认虚拟网卡SSH 直接连不上只能去后台开 VNC 或者重装系统。如果你确实要走这条路请务必确认服务商提供了控制台或者救援模式别在纯远程、无带外的机器上冒险。树外模块这条路的思路是内核本身有个「拥塞控制算法注册表」算法可以通过模块动态注册进去。我们只要把补丁里的算法源码拿出来作为一个独立模块编译modprobe之后它就会出现在可用列表里。内核本体完全没动出问题rmmod或者改回 sysctl 就行整个回滚过程不超过十秒。后面的实操主要按这条路走。2.3 动手前的回滚预案别嫌麻烦在线业务上改动任何网络参数之前我会做四件事你可以照抄第一把当前状态存一份档。uname -r /root/bbr-backup.txt sysctl -a /root/sysctl-backup-$(date %F).txt lsmod /root/lsmod-backup-$(date %F).txt第二如果你是换内核路线备份引导配置。cp -a /boot/grub2/grub.cfg /root/grub.cfg.bak 2/dev/null || cp -a /boot/grub/grub.cfg /root/grub.cfg.bak并且确认旧内核没有被卸载grubby --default-kernel或者grub-set-default能切回去。RHEL 系默认保留三个内核Debian 系靠/etc/apt/apt.conf.d/里的配置控制别一边换内核一边执行清理旧内核的操作。第三确认内存和编译环境够用。free -m gcc --version make --version编译内核模块本身不占什么内存但机器如果只有 512MB 而且没有 swapmake中途被 OOM 杀掉是很常见的。临时加一块 swap 文件比死磕更省事。第四选时间窗口。改net.ipv4.tcp_congestion_control这个参数本身对已有连接无影响只对新连接生效所以它比想象中安全但如果你顺手也改了队列规则或者缓冲区参数已有连接的表现在极端情况下会受影响。业务低峰期操作永远是成本最低的保险。3. 从源码到生效的完整安装流程3.1 依赖安装与内核源码准备先按发行版装编译工具链。Debian 和 Ubuntuapt update apt install -y build-essential dkms linux-headers-$(uname -r) kmodRHEL、CentOS、Rocky、Almayum install -y gcc make kernel-devel-$(uname -r) kernel-headers-$(uname -r) kmod dnf install -y gcc make kernel-devel-$(uname -r) kmod # 新版本用 dnf这里的重点是$(uname -r)必须原样带上。我遇到过有人直接apt install linux-headers-generic装上的头文件版本和当前运行内核差了一个小版本号编译能过加载直接报invalid module format查了半小时才反应过来。判断方法很简单modinfo -F vermagic /lib/modules/$(uname -r)/kernel/net/ipv4/tcp_bbr.ko 2/dev/null # 或者对任意一个现有模块 modinfo -F vermagic $(modinfo -n ext4 | head -1)输出的第一段就是内核版本字符串它必须和你uname -r完全一致。拿到算法源码之后通常是补丁形式需要从对应版本的内核源码里取出net/ipv4/tcp_bbrplus.c。如果你拿到的是一份完整的补丁文件可以用tar -xf linux-4.14.xxx.tar.xz -C /usr/src/ cd /usr/src/linux-4.14.xxx patch -p1 /root/bbrplus.patch ls net/ipv4/tcp_bbrplus.cpatch命令的-p1是剥掉一层路径前缀这是标准做法如果打补丁时提示Hunk #1 FAILED说明你手上的源码版本和补丁基线不匹配强行打上去编出来的东西行为不可预期这时候应该换源码版本而不是用--fuzz硬顶。3.2 把算法编译成树外模块源码准备齐之后单独建一个目录放 Makefile。树外模块的 Makefile 不需要写成完整的内核构建脚本核心就是告诉kbuild哪些文件属于这个模块obj-m tcp_bbrplus.o KDIR : /lib/modules/$(shell uname -r)/build PWD : $(shell pwd) all: $(MAKE) -C $(KDIR) M$(PWD) modules clean: $(MAKE) -C $(KDIR) M$(PWD) clean install: $(MAKE) -C $(KDIR) M$(PWD) modules_install depmod -a这里几个点解释一下理解了就不会被报错牵着走。-C $(KDIR)是把工作目录切到内核构建目录借用内核自己的编译规则M$(PWD)是告诉它「外部模块在这里」编完会回到这个目录取产物。没有-C的话直接make会被当成内核源码根目录构建然后报一堆莫名其妙的错。开始编译make -j$(nproc)编完目录里会出现tcp_bbrplus.ko。先别急着装检查一下modinfo tcp_bbrplus.ko重点看三行输出vermagic是不是和当前内核一致depends有没有依赖别的模块description和license是否正常。老内核对这个模块的license声明有要求写成非 GPL 兼容的字符串会直接拒绝加载。确认没问题就装进去make install ls -l /lib/modules/$(uname -r)/extra/make install底层调的是modules_install装到/lib/modules/$(uname -r)/extra/下depmod -a是关键一步它负责刷新模块依赖索引。跳过depmod的典型症状就是modprobe报Module not found但文件明明躺在那里。这个坑我踩过不止一次尤其是手动cp文件之后忘了刷新索引。如果你的机器会在意内核更新建议直接在模块目录里放一份 DKMS 配置让它在内核升级后自动重编PACKAGE_NAMEtcp-bbrplus PACKAGE_VERSION1.0 BUILT_MODULE_NAME[0]tcp_bbrplus DEST_MODULE_LOCATION[0]/kernel/net/ipv4 MAKE[0]make KDIR/lib/modules/${kernelver}/build CLEANmake clean AUTOINSTALLyes放好之后dkms add -m tcp-bbrplus -v 1.0 dkms build -m tcp-bbrplus -v 1.0 dkms install -m tcp-bbrplus -v 1.0 dkms statusDKMS 的价值在于内核一升级它就自动跟着重编省掉「升完内核发现模块没了」这种半夜被叫起来的场景。代价是每次升级内核都要占用几十秒 CPU 和一点磁盘空间小内存机器上要留意。3.3 加载模块、切换算法和队列规则模块装好之后先手动加载一次modprobe tcp_bbrplus lsmod | grep -i bbr dmesg -T | tail -20dmesg这一步别省。模块加载失败的原因全部体现在这里常见的几类后面会专门讲。加载成功的话算法就会出现在可用列表里sysctl net.ipv4.tcp_available_congestion_control假设输出里有bbrplus那就切换过去sysctl -w net.ipv4.tcp_congestion_controlbbrplus sysctl -w net.core.default_qdiscfqfq这个队列规则和 BBR 是搭配设计的它负责给数据包做发送节奏控制内核里的 BBR 在没有 pacing 的情况下会退化成一种比较激进的行为效果打折。有些教程会让你用fq_codel那个也能用但在高带宽场景下fq更合适我实测跨地域大流量传输fq的曲线更均匀。注意fq在老内核上可能以内置形式存在也可能需要单独模块加载失败就modprobe sch_fq。检查一下当前网卡上的队列规则ip link show tc qdisc show dev eth0ip link输出的网卡名要用来替换eth0现在很多机器是ens3、enp1s0这种命名。队列规则对新建立的连接生效更明显已有连接的队列不会重建所以改完最好测一条新连接。3.4 让配置在重启后自动生效手动敲的命令重启就没了。持久化要分两步走分别管模块和参数。模块的自动加载靠modules-load.decho tcp_bbrplus /etc/modules-load.d/bbrplus.conf参数的持久化靠sysctl.dcat /etc/sysctl.d/99-bbrplus.conf EOF net.core.default_qdisc fq net.ipv4.tcp_congestion_control bbrplus EOF这里的顺序问题值得单独说。systemd-sysctl在启动时读取sysctl.d里的配置如果它执行的时候模块还没被加载那么tcp_congestion_control这一条会被静默忽略——你重启后发现又变回 cubic 了就是这个原因。较新的 systemd 已经把模块加载排在 sysctl 之前但老版本上并不可靠。我的处理方式是再加一个兜底单元确保模块先就位再写参数[Unit] DescriptionApply BBRplus congestion control Afternetwork-pre.target systemd-modules-load.service Beforenetwork.target Wantssystemd-modules-load.service [Service] Typeoneshot RemainAfterExityes ExecStart/sbin/modprobe tcp_bbrplus ExecStart/sbin/sysctl -w net.core.default_qdiscfq net.ipv4.tcp_congestion_controlbbrplus [Install] WantedBymulti-user.targetsystemctl daemon-reload systemctl enable --now bbrplus-apply.service systemctl status bbrplus-apply.service下一次重启之后用这三条命令复核uname -r sysctl net.ipv4.tcp_congestion_control lsmod | grep -i bbr三者都对上才算真的装成功了。4. 「装了但没生效」——加速失败排查实录4.1 先分清是真失败还是假失败排查的第一原则不要凭感觉判断用命令确认。判断分三层。第一层算法有没有被内核认出来sysctl net.ipv4.tcp_available_congestion_control列表里没有你想用的名字问题在模块层去看第 4.2 节。列表里有名字但全局当前值不对sysctl net.ipv4.tcp_congestion_control还是cubic的话问题在参数写入层可能是持久化顺序问题也可能是被别的地方覆盖了。第二层实际的连接用的是什么算法。这一层最容易被忽略全局设置只是默认值应用程序可以通过setsockopt单独指定某个 socket 的拥塞算法。有些框架、有些语言运行时会这么做你全局改了也没用。查法ss -tin state established输出里每条连接会带一串 TCP 内部信息开头那个单词就是这条连接实际用的算法。如果你看到目标连接上写着cubic那就找到了得从应用侧改或者接受现实。第三层性能有没有实际变化。这一步要用可对比的测试不能靠「感觉快了点」。# 对端 iperf3 -s # 本机 iperf3 -c 对端地址 -t 30 -P 4 -O 3-O 3是跳过前 3 秒也就是把慢启动阶段排除掉。很多人测出来没变化就是因为把慢启动那几秒算进了平均值——不同算法在慢启动阶段的差异被稀释了剩下的是同一条链路的物理上限。多跑几轮取中位数比跑一次看峰值有意义得多。4.2 模块层面的典型坑坑一vermagic 不匹配。报错长这样insmod: ERROR: could not insert module tcp_bbrplus.ko: Invalid module format。原因通常是编译时用的头文件和当前运行内核版本不一致。直接看modinfo tcp_bbrplus.ko | grep vermagic uname -r两个字符串必须一致。不一致就重装对应版本的头文件包然后make clean make重编。别想着用--force参数强插内核符号 CRC 对不上强插进去是给自己埋雷。坑二符号版本不兼容。报错类似disagrees about version of symbol tcp_register_congestion_control或者Unknown symbol in module。前者说明内核开启了模块版本校验而你编模块用的源码树和运行内核不是同一份构建产物后者说明你依赖的函数没有被导出给模块使用。这两种情况都只能回到「用匹配的源码树重新编译」这条路没有捷径。坑三安全启动拦截。报错是module verification failed: signature and/or required key missing。处理方式两条一是进固件关掉安全启动本地机器可行云主机一般没这个开关二是生成一对密钥用内核源码里的scripts/sign-file给模块签名再通过mokutil --import把公钥登记进 MOK 列表重启时在蓝色界面上确认。第二条路麻烦但正规本地工作站建议走这条。坑四算法名重名冲突。有些 BBRplus 分支为了省事把自己的算法也注册成bbr而你的内核里已经内置了主线 BBR。这时候注册会失败dmesg里可能只有一行不起眼的提示而lsmod显示模块是加载成功的——因为模块加载和算法注册是两件事。判断方法就是看tcp_available_congestion_control里到底有没有多出来东西。坑五容器环境。前面提过OpenVZ、LXC、未开启特权的 Docker 容器里你没有加载模块的权限。Docker 容器里的网络命名空间是独立的就算宿主机改了算法容器里看到的默认值也可能是另一套。这种环境下改了sysctl还会报Operation not permitted或者写完没反应。4.3 算法生效了速度还是上不去这是最让人头大的一类因为拥塞控制确实是正常的瓶颈在别处。我按出现频率排一下。队列规则没配好。检查tc qdisc show dev 网卡名如果显示的是pfifo_fast而不是fq说明net.core.default_qdisc没生效或者被别的地方覆盖了。注意这个参数只影响新建的队列改完要看新连接。还有一种情况是网卡上有别的整形规则比如服务商在物理层做的限速那就不归你管了。CPU 单核成为瓶颈。高带宽下的 TCP 处理是单核软中断任务10Gbps 网卡压满一个核是常事。用mpstat -P ALL 1或者top -H看如果某个核的%soft长期在 80% 以上那限速的不是网络而是 CPU。这种情况下换什么拥塞算法都没用得考虑开多队列、调中断亲和性或者干脆多开几条连接分摊。网卡 offload 配置不一致。检查ethtool -k eth0 | grep -E tso|gso|gro|checksum大部分场景开着是好事但某些虚拟化环境下 offload 反而会带来额外的重传和乱序。如果发现重传率异常高可以试着关掉再对比。MTU 和路径 MTU 黑洞。症状是连接建立正常、传输到一定量就卡住然后又恢复。临时开启探测sysctl -w net.ipv4.tcp_mtu_probing1如果这一条下去明显改善基本可以确认是链路上有设备吞掉了 ICMP 的fragmentation needed报文。业务本身是 UDP。再强调一遍拥塞控制只管 TCP。如果你的业务跑在 UDP 上改这些参数一点作用都没有。对端接收窗口太小。如果对端的接收缓冲区没调大你发得再快也会被对方按窗口卡住。看ss -tin里的rwnd字段长期贴着cwnd说明瓶颈在对端。4.4 常见问题速查表现象可能原因排查命令处理方式modprobe报 Module not found没装或没刷新索引ls /lib/modules/$(uname -r)/extra/、depmod -a重新make install并depmod -aInvalid module formatvermagic 不匹配modinfo tcp_bbrplus.ko | grep vermagic装匹配版本头文件后重编Unknown symbol依赖符号未导出dmesg -T、modinfo -F depends tcp_bbrplus.ko换用匹配的内核源码树重编module verification failed安全启动拦截mokutil --sb-state关安全启动或签名并登记 MOK列表里有算法但当前值是 cubic参数写入被覆盖或顺序问题sysctl net.ipv4.tcp_congestion_control检查 sysctl.d 与模块加载顺序重启后失效只写了 sysctl 没写 modules-load重启后lsmod | grep bbr用兜底 systemd 单元全局改了但某连接没变应用setsockopt覆盖ss -tin state established从应用侧配置容器里报 Operation not permitted共享宿主内核systemd-detect-virt换 KVM 类实例或联系宿主方生效了但速度没变瓶颈在 CPU、队列规则或对端mpstat -P ALL 1、tc qdisc show按 4.3 节逐项排除上传快下载慢方向搞反了ss -tin看方向调对端发送端5. 参数微调、效果验证与长期维护5.1 值得改的 sysctl 参数以及为什么下面这些是我在多数服务器上会调、也解释得清理由的参数。不要盲目抄网上一大串「优化参数」改坏了的排查成本远高于收益。参数建议值为什么这么改风险net.core.default_qdiscfq配合 BBR 做发送节奏控制老内核可能需要单独加载模块net.ipv4.tcp_congestion_controlbbrplus或bbr换掉丢包驱动的默认算法应用层可能覆盖net.core.rmem_max16777216放开单 socket 接收缓冲区上限内存占用随并发连接数上涨net.core.wmem_max16777216同上发送侧同上net.ipv4.tcp_rmem4096 131072 16777216最小值、默认值、最大值三段自动调节通常不用动net.ipv4.tcp_wmem4096 16384 16777216同上发送侧同上net.ipv4.tcp_slow_start_after_idle0空闲后不重置拥塞窗口短连接密集的业务受益明显公平性上略有牺牲net.ipv4.tcp_mtu_probing1应对 PMTU 黑洞探测会产生少量额外报文net.ipv4.tcp_notsent_lowat131072减少不必要的排队降低延迟极端吞吐场景可能略有影响有几条要特别提醒。net.ipv4.tcp_tw_recycle这个参数在新内核里已经被移除了老教程里还在写照抄会被sysctl报错拒绝别管它。tcp_timestamps不要关关掉之后部分链路的重传和 RTT 测量精度会下降得不偿失。缓冲区参数不要一上来就设成几百兆那个值乘以并发连接数就是最坏情况的内存占用小机器上这么干会把自己搞挂。计算缓冲区大小时有个简单公式带宽字节/秒乘以 RTT秒就是理论 BDP。比如 1Gbps 带宽、200ms RTTBDP 大约是 25MB。缓冲区设到 BDP 的两倍左右就够了设成 16MB 在多数百兆到千兆的跨地域链路上是合理值如果你的带宽只有 100Mbps那 4MB 都富余。5.2 怎么验证改了到底有没有用验证要避开两个陷阱慢启动和单次采样。跨地域链路我一般这么测# 先测当前算法 sysctl net.ipv4.tcp_congestion_control iperf3 -c 对端 -t 30 -P 4 -O 3 # 切回对照 sysctl -w net.ipv4.tcp_congestion_controlcubic iperf3 -c 对端 -t 30 -P 4 -O 3 # 再切回来 sysctl -w net.ipv4.tcp_congestion_controlbbrplus同一条链路、同一时段、交替测三轮取中位数对比。单次测出来的差异可能是链路上其他流量的波动不能作为结论。除了吞吐还要看两个指标。重传率用nstat -az | grep -i -E Retrans|TcpExtTCPLost把两次测试的差值除以发送的总包数就是这一轮的重传率。BBR 系算法的特点不是「绝对零重传」而是重传之后不会像丢包驱动算法那样把窗口砍掉一半所以你会看到重传率比 CUBIC 高一些但吞吐更好——这是正常现象别看到重传就以为出问题了。RTT 和窗口则用ss -tin state established dport :443输出里的rtt、cwnd、rwnd、retrans字段都值得看。cwnd稳定在一个较大值、rtt曲线平缓说明算法在正常工作cwnd呈现明显的锯齿说明还在用丢包驱动的逻辑多半是算法没真正切过去。5.3 内核升级之后怎么办这是长期维护里最容易翻车的地方。内核一升级/lib/modules/$(uname -r)/整个目录都变了你之前编的模块自然就不在列表里重启之后算法就没了。三种应对思路第一用 DKMS 自动重编前面给过配置。优点是省心缺点是内核升级时如果源码树和补丁基线不匹配DKMS 编译会失败但开机照样继续不会主动告警你得自己看dkms status才知道。第二锁住内核版本不让它自动升级。Debian 系apt-mark hold linux-image-$(uname -r) linux-headers-$(uname -r)RHEL 系可以用yum versionlock或者直接在配置里排除内核包。这条路适合「我就是要这个版本、别给我变」的场景但长期不升内核会错过安全修复自己权衡。第三也是我目前越来越倾向的做法把 BBRplus 当成一次性过渡方案而不是长期依赖。用主线内核自带的 BBR 加前面那套调参在绝大多数链路上已经能满足需求BBRplus 那点边际收益在维护成本面前不一定划算。我现在的做法是新机器直接开主线 BBR只有那些确实在烂链路上有明确瓶颈、并且已经验证过 BBRplus 有可量化提升的老机器才继续维护这个模块而且一定会配上 DKMS 和监控——监控的做法很简单一个 cron 每小时跑一次sysctl -n net.ipv4.tcp_congestion_control结果不是预期值就往自己的告警通道发消息比出问题之后才发现强太多。最后分享一个我踩过的小坑有一次在sysctl.d里写参数写成了tcp_congestion_control bbrplus漏了net.ipv4.前缀。sysctl -p直接报错退出但因为在启动脚本里执行、输出没被看到结果所有配置都没生效而我在另一头排查了半小时的模块加载问题。现在我的习惯是每次改完立刻手动跑一遍sysctl -p /etc/sysctl.d/99-bbrplus.conf看有没有报错再重启验证。这个动作花不了十秒能省掉一整晚。