带宽100M但传输只有30M?你的服务器可能该换TCP算法了
摘要:服务器带宽100M,iperf测出来只有30M。线路没问题、网卡没问题、MTU也正常。最后发现是TCP拥塞控制算法在拖后腿。本文从一次实际排查出发,讲清楚Reno、CUBIC、BBR的区别,以及什么时候该换、怎么换。
关键词:TCP拥塞控制、BBR、CUBIC、带宽优化、Linux网络调优
分类:网络 / Linux运维 / 性能优化
一、从一个"带宽跑不满"的问题说起
有个客户找我,说服务器100M独享带宽,但传文件怎么都跑不满。
我先让他跑iperf:
bash
iperf3 -c 公共测试IP -t 30
css
[ ID] Interval Transfer Bitrate
[ 5] 0.00-30.00 sec 108 GBytes 30.9 Mbits/sec
100M带宽只跑出30M。
排除了网卡(千兆没问题)、排除了线路(独享确认过)、排除了MTU。最后查到一个参数:
bash
sysctl net.ipv4.tcp_congestion_control
ini
net.ipv4.tcp_congestion_control = cubic
CUBIC,Linux默认的拥塞控制算法。
换成BBR:
bash
sysctl -w net.ipv4.tcp_congestion_control=bbr
sysctl -w net.core.default_qdisc=fq
再测:
css
[ ID] Interval Transfer Bitrate
[ 5] 0.00-30.00 sec 347 GBytes 99.5 Mbits/sec
30M到99.5M。同样的服务器、同样的网络,只换了一个内核参数,速度翻了三倍。
二、TCP拥塞控制是什么
TCP发数据不是一股脑全塞出去。它会根据网络状况动态调整发送速率,避免把网络堵死。这个"根据网络状况调整速率"的策略,就是拥塞控制算法。
打个不严谨的比方:开车上高速,限速120但前面有车流,你得根据前车距离决定油门踩多深。拥塞控制算法决定的就是"什么时候加速、什么时候减速、怎么判断前面堵不堵"。
不同的判断方式,效果差距巨大。
三、三代算法的演进
Reno:最老的思路,靠丢包判断网络状况
Reno的策略是这样的:
先慢启动------一开始发得慢,收到确认就加速,指数增长。到了一定阈值之后改成线性增长,一点一点试探上限。一旦发现丢包,立刻把发送速率减半,然后慢慢恢复。
这个思路在早期网络环境下没问题。那时候设备缓冲区小,拥塞了就丢包,丢包确实是"路堵了"的可靠信号。
但现在的问题是:丢包不一定代表拥塞。
比如长距离传输(中国到美国,RTT 150ms+),中间设备的缓冲区可能很大。拥塞了不丢包,只是数据包在缓冲区里排队,延迟变高。Reno探测不到这种情况,速率上不去。
反过来,无线网络里的随机丢包可能只是信号干扰,不代表拥塞。Reno却会因此大幅降速,浪费带宽。
CUBIC:Linux默认,比Reno聪明但核心思路没变
CUBIC从2006年开始就是Linux默认算法,大部分服务器现在还在用。
它改进了窗口增长曲线------从Reno的线性增长变成了三次函数曲线。恢复速度快了很多,高带宽场景下窗口增长效率也更高。
但本质没变:CUBIC还是靠丢包来判断网络状况。 没丢包就一直试探上限,丢了就减速。
这在两种场景下有问题:
高延迟链路。 带宽时延积(BDP)= 带宽 × 延迟。一条100M带宽、100ms延迟的链路,BDP约1.25MB。CUBIC的拥塞窗口需要时间涨到能填满这个BDP的大小。延迟越高,需要的时间越长。在这个过程中带宽利用率很低。
有大缓冲区的网络设备。 现代交换机路由器的缓冲区越来越大。拥塞了不丢包,数据包全堆在缓冲区里排队,延迟飙到几百毫秒。CUBIC以为网络很好继续猛发,结果是延迟越来越高。这就是Bufferbloat(缓冲区膨胀)。
那个客户的服务器走的是跨省线路,RTT大约25ms。CUBIC在这个延迟下窗口增长很慢,带宽利用率只有30%左右。
BBR:不看丢包,看带宽和延迟
BBR是Google在2016年提出的,Linux 4.9开始支持。
BBR的核心思路完全不同。它不关心丢不丢包,而是持续测量两个指标:
- 链路的最大传输速率(瓶颈带宽)
- 链路的最小时延(最小RTT)
发送速率控制在"瓶颈带宽 × 最小RTT"附近------刚好填满链路但不造成排队。
这样做的好处是:
- 高延迟场景下,BBR能快速探测到可用带宽,不需要像CUBIC那样慢慢涨窗口
- 少量随机丢包不会导致大幅降速
- 能主动避免Bufferbloat,不让数据包在缓冲区里堆积
BBR的适用边界:
同机房内网(延迟<1ms、无丢包):CUBIC和BBR差别不大。内网环境下CUBIC也能跑满,换不换无所谓。
跨网传输(延迟>20ms或有丢包):BBR效果明显。
公平性方面:BBR v1在与CUBIC共享链路时可能抢到更多带宽。BBR v2改善了这个问题,但截至2025年初,主线内核里集成的还是BBR v1。大部分场景下这不是问题,但如果同一台机器上有BBR和CUBIC的流量走同一条链路,值得关注一下。
四、实测对比
iperf测试数据
场景A:同机房(RTT < 1ms,无丢包)
bash
# CUBIC
iperf3 -c 目标IP -t 30 -C cubic
# 94.2 Mbits/sec
# BBR
iperf3 -c 目标IP -t 30 -C bbr
# 95.1 Mbits/sec
几乎没差别。内网环境CUBIC也能跑满。
场景B:跨省(RTT ~30ms,偶发丢包)
bash
# CUBIC
iperf3 -c 目标IP -t 30 -C cubic
# 45.3 Mbits/sec
# BBR
iperf3 -c 目标IP -t 30 -C bbr
# 88.7 Mbits/sec
BBR快了将近一倍。
场景C:跨国(RTT ~150ms,有一定丢包)
bash
# CUBIC
iperf3 -c 目标IP -t 30 -C cubic
# 12.8 Mbits/sec
# BBR
iperf3 -c 目标IP -t 30 -C bbr
# 68.5 Mbits/sec
BBR快了5倍。高延迟下CUBIC的窗口涨得太慢,带宽利用率很低。
对实际业务的影响
iperf是理想场景。实际业务要分情况:
大文件传输/备份: 效果最明显,跨网场景速度提升2-5倍。
API调用(小数据包): 影响不大。一个请求/响应通常几KB,传输时间远小于服务器处理时间,拥塞控制算法几乎没有存在感。
视频推流/直播: 效果明显。持续大流量传输,BBR能更好利用带宽。
数据库跨网主从复制: 有改善。binlog传输是持续的中等流量,BBR能减少复制延迟。
WebSocket长连接: 有改善。BBR对网络抖动的容忍度更高,连接更稳定不容易因为误判拥塞而降速。
五、怎么查看和切换
查看当前算法
bash
# 当前使用的拥塞控制算法
sysctl net.ipv4.tcp_congestion_control
# net.ipv4.tcp_congestion_control = cubic
# 内核支持的所有算法
sysctl net.ipv4.tcp_available_congestion_control
# net.ipv4.tcp_available_congestion_control = reno cubic
如果输出里没有bbr,说明内核太低或者没加载BBR模块。
检查内核版本
bash
uname -r
# 5.10.0-xx.el8.x86_64
BBR需要Linux 4.9及以上。建议5.10+,这个版本的BBR已经足够稳定。
加载模块并切换
bash
# 加载BBR模块
modprobe tcp_bbr
# 确认加载成功
lsmod | grep bbr
# tcp_bbr 20480 0
# 临时切换(重启后失效)
sysctl -w net.ipv4.tcp_congestion_control=bbr
sysctl -w net.core.default_qdisc=fq
bash
# 永久生效
cat >> /etc/sysctl.conf << 'EOF'
# BBR拥塞控制
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
EOF
sysctl -p
net.core.default_qdisc = fq 建议配上。BBR配合Fair Queue调度器(fq)效果最好。不配也能跑,但fq能帮BBR做精确的发送节奏控制。
验证
bash
# 确认算法已切换
sysctl net.ipv4.tcp_congestion_control
# net.ipv4.tcp_congestion_control = bbr
# 查看已有连接的算法(新建连接才用新算法,已有连接不变)
ss -tin | head -5
切换算法只对新建连接生效。已经建立的TCP连接(数据库连接池、WebSocket等)还是用的旧算法。要让所有连接生效,需要重启应用让连接重建。
六、切换注意事项
不是所有场景都要换
同机房内网:CUBIC够用,不用折腾。BBR的优势在跨网和高延迟场景。
跨网传输:建议换,效果明显。
生产环境先灰度
虽然BBR很成熟了,生产环境还是建议先在一台机器上验证:
bash
# 切换前记录基准数据
iperf3 -c 测试IP -t 60 -C cubic > /tmp/before_bbr.txt
# 切换
sysctl -w net.ipv4.tcp_congestion_control=bbr
sysctl -w net.core.default_qdisc=fq
# 切换后对比
iperf3 -c 测试IP -t 60 -C bbr > /tmp/after_bbr.txt
观察一两天,确认业务指标没有异常再批量推广。
BBR v1和v2
目前主线内核集成的是BBR v1。BBR v2改进了公平性和对丢包的响应策略,但还在开发中,需要打补丁。大部分场景BBR v1够用,不用追v2。
七、顺手一起调的内核参数
拥塞控制算法换了,这几个参数建议一起调:
bash
cat >> /etc/sysctl.conf << 'EOF'
# BBR拥塞控制
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
# TCP缓冲区大小(根据带宽延迟积调整)
# 默认值太小,高延迟场景下会限制吞吐
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
# 连接数
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.ip_local_port_range = 1024 65535
# TIME_WAIT相关
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
EOF
sysctl -p
tcp_rmem和tcp_wmem的第三个数字(最大值)设到16MB,保证高延迟场景下TCP缓冲区能自动扩大到足够大。默认值通常只有几MB,在跨国传输场景下可能成为瓶颈。
tcp_fin_timeout控制的是FIN_WAIT_2状态的超时时间,不是TIME_WAIT。FIN_WAIT_2是被动关闭方收到FIN后的状态,超时后直接关闭。设短一点能更快释放连接资源。
八、速查表
| 场景 | CUBIC表现 | BBR表现 | 建议 |
|---|---|---|---|
| 同机房内网 | 跑满 | 跑满 | 不用换 |
| 跨省(~30ms) | 可能只用50%带宽 | 接近跑满 | 建议换 |
| 跨国(150ms+) | 可能只用10-20% | 50-70% | 强烈建议换 |
| 无线/有随机丢包 | 丢包就降速 | 不受少量丢包影响 | 建议换 |
| 小包API调用 | 影响小 | 影响小 | 换不换都行 |
| 大文件传输 | 高延迟下差 | 明显提升 | 建议换 |
| 数据库跨网复制 | 可能有延迟 | 复制延迟改善 | 建议换 |
最后
回到开头那个客户。100M带宽只跑出30M,换BBR之后99.5M。
这个参数不要钱、改起来一分钟、效果立竿见影。但确实有不少运维和开发不知道这个东西的存在。存在"带宽跑不满"问题的服务器,值得查一下net.ipv4.tcp_congestion_control,如果是cubic,换成bbr试试。
不过BBR不是万能药。如果你的瓶颈在应用层处理慢、数据库查询慢、或者下游接口响应慢,换拥塞控制算法帮不上忙。先用curl -w或者tcpdump确认问题在网络传输层,再考虑换算法。