带宽100M但传输只有30M?你的服务器可能该换TCP算法了

带宽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_rmemtcp_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确认问题在网络传输层,再考虑换算法。

相关推荐
日月云棠9 小时前
JAVA数据结构与算法 - 基础:常用集合简述
java·算法
SilentSamsara9 小时前
运算符重载:让自定义对象支持 +、[]、in 操作
开发语言·python·算法·青少年编程·pycharm
小黑蛋9129 小时前
HTTP、TLS 与证书深度解析 —— 从裸奔到全副武装的安全通信之旅
后端
随风,奔跑9 小时前
RabbitMQ
后端·rabbitmq
日月云棠9 小时前
JAVA数据结构与算法 - 基础:BlockingQueue
java·算法
8K超高清9 小时前
CCBN展会多图回顾
人工智能·算法·fpga开发·接口隔离原则·智能硬件
前端白袍10 小时前
代码规范:RESTful API 全面介绍
后端·restful·代码规范
神奇小汤圆10 小时前
一次 JVM OOM,资深工程师应该如何完整复盘?
后端
孟陬10 小时前
一个小小 alias,提升开发幸福感
前端·后端·命令行