网络吞吐瓶颈分析:为什么需要巨型帧和TCP卸载

吞吐上不去,第一反应就是加带宽。但我这些年处理过的性能事故,十有八九不是带宽不够,而是数据包到了网卡以后,系统根本来不及处理。你想想看,万兆网卡理论能跑 10Gbps,结果实测只有 3-4Gbps,网卡和交换机都没问题,CPU 却已经 100% 了。

为什么会这样?因为每个数据包都要经过一次硬中断、一次软中断,再加上协议栈一轮又一轮的拷贝。包越小,数量越多,CPU 就越累。今天我们就来拆解这个核心矛盾,看看巨型帧和 TCP 卸载到底怎么救场。

传统 MTU 的“小包海啸”

标准以太网帧的 MTU 是 1500 字节。去掉头部开销,有效载荷也就 1460 字节。传输一个 1GB 的文件,按照小包算,要拆成 68 万个包。每个包都走一遍完整的中断-协议栈-拷贝路径,CPU 光处理中断就快崩溃了。

我印象很深,有一家游戏公司做流媒体分发,千兆链路跑到 400Mbps 就上不去了。线上业务一波动,服务器直接丢包。查到最后,发现是网卡中断亲和性没配好,所有中断都打在一个 CPU 上。但根本原因还是包太多了,CPU 扛不住那个中断频次。

指标MTU 1500MTU 9000(巨型帧)
传输 1GB 的包数≈ 728,000≈ 121,000
中断次数(网卡侧)极高降低 ~83%
CPU 协议栈开销重显著减轻
链路利用率70-80% 常见瓶颈轻松超 95%

一句话理解:巨型帧不是提速,而是减负——用更少的包搬运同样的数据,CPU 省下的精力就能去处理别的任务。

中断开销:被低估的吞吞吐因

每个数据包到达网卡,都会触发一次硬中断。CPU 停下当前工作,切换上下文去喂协议栈。一次中断本身只消耗几微秒,但架不住每秒几十万次。我在调优 Nginx 反向代理时就踩过这个坑:明明开启了 RSS(接收端缩放),硬中断打到多个 CPU 上,但只要一个 CPU 的软中断配额用完,性能就急剧下降。

所以后来我养成了个习惯:先看 /proc/interrupts 的分布,再看 mpstat -I CPU 的软中断负载。如果某个 CPU 的软中断占比超过 50%,基本就是包处理瓶颈。

实用检查命令:
watch -n 1 'cat /proc/interrupts | grep eth0'
mpstat -P ALL 1 3 | grep -E "CPU|soft"

巨型帧:一个优雅的破局方案

启用巨型帧(Jumbo Frame),就是把 MTU 从 1500 提升到 9000 字节。包数量直接降到原来的 1/6,中断次数和协议栈开销自然也成比例下降。

说白了,这就是个“以空间换时间”的经典策略。但要注意,巨型帧需要全链路支持——两端网卡、交换机,甚至中间的光模块都得配置 9000 字节的 MTU。我之前遇到过一个案例,服务器配了 9000,中间某台老旧交换机默认只支持 1500,结果业务直接不通。查了两天才定位到,后来我写了个脚本专门做端到端 MTU 探测。

# 端到端 MTU 探测脚本(Linux)
df /mnt/data/testfile  # 创建个大文件
ping -M do -s 1472 -c 3 192.168.1.1   # 检测 1500 路径
ping -M do -s 8972 -c 3 192.168.1.1   # 检测 9000 路径
# 如果报错 "Frag needed",说明路径不支持该 MTU

曾经踩过的坑:巨型帧不是开关一开就完事的。我曾经在迁移存储网络时,只改了服务器的 MTU,忘了改 ISCSI 侧交换机端口,导致磁盘 I/O 狂掉。一定要在链路两端及所有中间设备上逐一验证。

TCP 卸载:把脏活扔给网卡

巨型帧解决了包数量问题,但协议栈处理还是得 CPU 干。TCP 卸载(TSO、GRO、LRO)就是让网卡硬件帮忙分担这些重复计算。

  • TSO(TCP 分段卸载):发送时,应用层直接扔一个大块数据给网卡,网卡自己拆成多个 TCP 段。CPU 只需要一次系统调用。
  • GRO(通用接收卸载):接收时,网卡把连续的小包合并成一个大数据包再交给协议栈。中断次数和工作量同步下降。
  • LRO(大接收卸载):GRO 的前身,功能类似但只支持 TCP。

我在设计高并发日志采集系统时,测试过开与不开 GRO 的差距。关闭时,单机只能扛 2Gbps;开启后直接冲到 8Gbps。当然,不是所有场景都适合卸载——比如转发设备或 DPI 应用,反而需要原包信息,这时卸载反而碍事。

# 查看当前卸载状态(RHEL/CentOS)
ethtool -k eth0 | grep -E "tcp-segmentation-offload|generic-segmentation-offload|generic-receive-offload|lro"

# 开启 TSO / GRO(临时)
ethtool -K eth0 tso on
ethtool -K eth0 gro on

# 建议写入 rc.local 或 netplan 配置

巨型帧 + TCP 卸载:1+1 > 2 的效果

单独用巨型帧,包数量减少但协议栈依然要处理每个大帧;单独用卸载,包数量没变但单包处理变快。两者叠加时,包数量降到原来 1/6,且每个包的处理流程也被硬件分担了大半,CPU 几乎从数据路径中解放出来。

我在做分布式存储压测时试过这组合:不开任何优化,吞吐 1.8Gbps;只开巨型帧,4.5Gbps;只开 TSO+GRO,5.2Gbps;全开,直接到 9.3Gbps。CPU 利用率从 85% 降到了 30% 以下。效果立竿见影。

巨型帧与TCP卸载协同优化路径 应用层数据 传统流程 CPU 全量处理 中断+协议栈 ≈728k 包 巨型帧 MTU 9000 TCP 卸载 TSO+GRO CPU 减负 ≈121k 包 效果对比 传统:CPU 85% → 吞吐 1.8 Gbps;优化后:CPU 30% → 吞吐 9.3 Gbps

什么时候不要用?

巨型帧和卸载不是银弹。我总结了几个需要关闭的场景:

  1. 中间设备不支持:比如老旧三层交换机和某些云环境,巨型帧会导致分片或丢包。
  2. 流量方向不对称:像防火墙、负载均衡这类设备,需要保留原始包大小和头部信息。卸载会打乱数据。
  3. CPU 原本就有大量余力:比如 10G 链路但应用只跑 1G,加这些优化反而增加网卡功耗和延迟。