上个月我接到一个挺头疼的任务:客户新到的一批 100GbE 服务器,验收标准是单流 TCP 要稳定跑到 90Gbps 以上。机器装的是云峦KeyarchOS,网卡是 Mellanox 的新卡,MTU 已经设成 9000,socket buffer 也调得很大,结果 iperf3 一跑,单流死活卡在 70Gbps 左右,所有 CPU 核都在报警。后来顺着协议栈往下查,一路摸到了 BIG TCP 这个特性,才算把问题彻底解决。这篇文章就把我在 KeyarchOS 上从原理到落地的完整过程记录下来,包括 BIG TCP 到底解决了什么问题、它凭什么能突破 64KB 的限制、具体怎么开启、实测提升了多少,还有一堆文档里不会写的坑。适合正在做数据中心网络压测、或者对内核协议栈性能优化感兴趣的工程师参考。
1. 先搞清楚 BIG TCP 到底在解决什么问题
1.1 一个真实场景:100GbE 单流压测与 64KB 瓶颈
先说回那个压测现场。服务器配置其实不差,CPU 是两颗高频的,网卡是 ConnectX-6 Dx,固件也是新的。MTU 9000,TCP window 调到了几百 MB,net.core.rmem_max、wmem_max 都拉高了,但单流带宽就是上不去。用 perf top 一看,占比最高的不是网卡驱动,也不是用户态程序,而是内核协议栈里的软中断处理、tcp_sendmsg、__netif_receive_skb_core 这一串。说白了,CPU 全花在"处理一个又一个包"上了。
这里有个关键数字:100GbE 线速意味着每秒大约要处理 195 万个 64KB 的包。如果每个包在协议栈里都要经历 skb 分配、校验和计算、队列操作、中断调度这一整套流程,哪怕单个包只消耗几百纳秒,累加起来也足以把 CPU 吃满。问题不是网卡不够快,而是内核协议栈的"单包处理成本"太高,包的数量把 CPU 压垮了。
我当时第一反应是"那就把 MTU 再调大",但这条路走不通。MTU 9000 已经是很多交换机支持的上限,再往上就是数据中心里很少见的 jumbo frame 巨型帧了。而且就算 MTU 调到 9000,协议栈内部生成给网卡的"超级包"大小上限依然被锁在 64KB,MTU 只影响最终线缆上的分片大小,不影响协议栈每次交给驱动的包能有多大。真正卡住性能的,是 GSO 的这个 64KB 上限。
1.2 BIG TCP 的收益逻辑:把包变大,把 CPU 花在刀刃上
BIG TCP 的思路特别直白:既然单包处理成本降不下来,那就让每个包尽可能大,这样同样吞吐下需要处理的包数量就少了。传统情况下,协议栈最多生成 64KB 的 GSO 包,然后网卡再把这 64KB 切成若干个 MSS 小包发出去。BIG TCP 把这个"最多 64KB"的钳制解除了,允许协议栈生成 192KB 甚至更大的超级包,切分工作依然全部交给网卡完成。
你可以把这个过程想象成搬家:传统方式是用小纸箱,东西多就得来回搬很多趟;BIG TCP 相当于换了大号纸箱,一趟能装下原来三趟的东西。纸箱到了目的地再拆开,不影响你最后摆放东西的方式。线缆上的数据包还是原来的 MTU 大小,外部设备根本感知不到变化,但内核协议栈内部处理的包数量直接降到了原来的三分之一。
所以 BIG TCP 本质上不是在"加速网络",而是"省 CPU"。在高速网络上,CPU 往往是真正的瓶颈,省下来的 CPU 意味着吞吐能更接近线速,也意味着同一台机器可以承载更多业务流量。这个特性尤其适合 100GbE、400GbE 这种高带宽环境,以及 AI 训练、大数据混部这类对 CPU 敏感的场景。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. BIG TCP 的底层原理:它是怎么捅破 64KB 这层窗户纸的
2.1 先复习 GSO/GRO:超级包与分段卸载
要理解 BIG TCP,得先理解 GSO 和 GRO。GSO(Generic Segmentation Offload)是发送路径上的机制:应用层把一大块数据交给 TCP,TCP 可以构建一个远远大于 MTU 的"超级包",然后由网卡驱动或网卡硬件负责把它切成符合 MTU 的小包发出去。这样做的好处是,协议栈不需要为每个 MSS 大小的分片都走一遍完整的内核路径,而是只处理一次大包,剩下的繁重切分工作交给了硬件。GRO(Generic Receive Offload)是接收路径上的对称机制:网卡收到一堆小包后,先合并成大包再交给协议栈,让协议栈少处理几次。
这两个机制在现代 Linux 里默认就是开启的,几乎所有主流网卡都支持。它们的存在,让内核和网卡之间传递的数据单元远大于线缆上的实际数据包。但这里有个一直没被突破的限制:这个"超级包"最大就是 64KB。为什么是 64KB?因为协议栈内部构建的包,头部字段天然有这个上限。
2.2 64KB 硬顶的来历:IPv4/IPv6 头字段限制
这个 64KB 的源头很古老。IPv4 头的 Total Length 字段只有 16 位,最大值是 65535,也就是说一个 IPv4 包无论数据多少,长度字段都表达不了超过 64KB 的数值。IPv6 头的 Payload Length 字段同样只有 16 位,也是 64KB 上限。所以内核协议栈在构造 GSO 超级包时,必须保证这个包能装进 IPv4/IPv6 头的长度字段里,gso_max_size 默认就被锁在了 65535 字节。
但 IPv6 其实留了一个后门:RFC 2675 定义的 Jumbo Payload 选项。它放在 Hop-by-Hop 扩展头里,用一个 32 位的字段覆盖掉原来 16 位的 Payload Length,理论上可以让 IPv6 包支持到 4GB。这个选项在公网上基本没人用,因为沿途每一跳都得认识它,但在可控的数据中心内部网络里,它给了 IPv6 突破 64KB 的合法途径。
这就是 BIG TCP 的一个核心取向:它首先面向 IPv6 场景。IPv4 没有类似的 Jumbo 机制,Total Length 字段是硬限制,所以在 IPv4 上做 BIG TCP 的难度大得多,社区也没往那个方向走。Google 当初提交这套补丁的时候,主要解决的就是数据中心内部 IPv6 环境下的大带宽传输问题。
2.3 BIG TCP 的实现思路:让"大包"只存在于内核,线缆上依然是普通 MTU
BIG TCP 的实现并不复杂,但涉及面很广。核心变化是允许 skb(内核网络缓冲区)突破 65535 字节,同时把设备的 gso_max_size、gro_max_size 这两个上限参数提升到 192KB 左右。对应到 IPv6 场景,还有专门的 gso_ipv6_max_size、gro_ipv6_max_size 字段,由支持 BIG TCP 的驱动在初始化时设置。
这中间有一堆细节要处理。比如 TCP 的 GSO 分段逻辑里,以前假设段数不会超过某个值,现在包大了,段数也要重新算;CHECKSUM_PARTIAL 校验和卸载的逻辑也要跟着调整;skb_shared_info 等结构体里对包长、偏移的假设都可能要改。所以这不是改一个参数就能完成的功能,而是一整套协议栈和驱动联动的修改。
有一个特别容易误解的点必须强调:BIG TCP 不是说你可以把 MTU 改成 192KB 去发巨帧。它改变的是内核协议栈到网卡之间"搬运"的数据单元大小,线缆上最终出去的包依然是标准的 9000 或 1500 字节。核心思想是把"大包拼装"和"大包切分"都卸载给硬件,协议栈只管用最高效的粒度搬运数据。
2.4 为什么默认 192KB:内存、网卡 TSO 上限与平衡
你可能会问,既然 IPv6 Jumbo Payload 理论上支持到 4GB,为什么 BIG TCP 默认推荐 192KB,而不是越大越好?这背后有几个现实约束。
第一,内核里一个 skb 通常对应一块连续内存。包越大,需要的内存块越大,对内存分配器的压力也越大,空闲内存碎片化严重的时候,大块内存分配失败的概率会明显上升。第二,网卡的 TSO(TCP Segmentation Offload)引擎有自身的最大段长限制,很多网卡固件对单次 TSO 卸载的数据量有硬上限,不是你给多大它就能切多大。第三,包越大,CPU 缓存局部性反而可能变差,因为一次内核处理要触碰的数据量太大,缓存命中率下降,收益会边际递减。192KB 这个值,是社区在多个实际场景里测出来的比较均衡的选择,也是原补丁作者推荐的默认值。
3. 云峦 KeyarchOS 上的 BIG TCP 落地实践
3.1 环境预检:内核、驱动、固件三张通行证
在 KeyarchOS 上落地 BIG TCP,第一步不是改参数,而是确认环境到底支不支持。这个特性不是装个系统就有,它需要内核、驱动、网卡固件三方都满足条件。
先说内核。BIG TCP 在 Linux 主线 5.19 版本合入,所以你系统内核至少要达到这个版本,或者发行版厂商做了 backport。云峦KeyarchOS 本身就是面向云和数据中心场景的服务器操作系统,内核迭代跟得比较紧,部分版本已经包含 BIG TCP 支持。判断方法很直接:
bash复制uname -r
如果内核版本低于 5.19,先别急着放弃,查一下 KeyarchOS 的发行说明或内核 config,看有没有把 BIG TCP 相关补丁 backport 进来。确认内核里有没有编译这个功能,可以查:
bash复制grep -i big_tcp /boot/config-$(uname -r)
grep -i big_tcp /proc/net/dev # 不一定有输出
更靠谱的方法是直接去看 sysfs 属性是否存在,后面会讲。
再说驱动和固件。BIG TCP 需要网卡驱动显式支持超大 TSO,典型的是 Mellanox 的 mlx5_core 驱动和 Google 的 gve 虚拟网卡驱动。用 ethtool -i 可以看当前网卡用的什么驱动以及固件版本:
bash复制ethtool -i enp1s0f0np0
如果你的网卡是老旧的 e1000、igb 或者常见的 virtio 虚拟网卡,大概率不支持 BIG TCP,就不用在上面折腾了。Mellanox 网卡最好把固件刷到比较新的版本,旧固件的 TSO 引擎对超大包的支持可能不完整。
3.2 开启 BIG TCP 的详细步骤(含命令)
确认环境满足条件后,开启本身其实很简单。我的操作顺序是这样:
先把基础的 GSO/GRO/TSO 都打开,虽然默认通常是开着的,但保不准哪次巡检被关过:
bash复制ethtool -K enp1s0f0np0 tso on gso on gro on
然后在 sysfs 里打开 BIG TCP 开关。不同内核版本的入口可能稍有差异,我手头这个 KeyarchOS 版本上是这个属性:
bash复制ls /sys/class/net/enp1s0f0np0/big_tcp
echo 1 > /sys/class/net/enp1s0f0np0/big_tcp
执行完再看一下当前生效的 GSO/GRO 上限:
bash复制cat /sys/class/net/enp1s0f0np0/gso_max_size
cat /sys/class/net/enp1s0f0np0/gro_max_size
cat /sys/class/net/enp1s0f0np0/gso_ipv6_max_size
cat /sys/class/net/enp1s0f0np0/gro_ipv6_max_size
如果内核和驱动支持正常,应该能看到类似 196608 或更大的值,而不是原来的 65536。注意这个写法和实际生效逻辑在不同内核版本上略有差异:有的是写 big_tcp 后自动把上限提上去,有的是直接在 gso_max_size 这类节点上写值。我的建议是把上面几个命令都跑一遍,哪个可用用哪个,最终以 gso_ipv6_max_size 的值是否超过 65535 作为生效判断标准。
另外,如果你用的网卡驱动或者 ethtool 版本比较新,在 ethtool -k 的输出里也可能直接看到 big-tcp 这个特性位,开了之后显示 on:
bash复制ethtool -k enp1s0f0np0 | grep -i big
我在测试机上是 sysfs 和 ethtool 特性位同时能看到,两个都确认过才放心。顺便提醒,sysfs 是运行时配置,重启后失效,生产环境要记得把开机自启脚本或者 systemd unit 写好。
3.3 性能测试设计:IPv6 单流/多流实测
开启之后,最关心的当然是效果。我先设计了一组对照实验,条件完全一致,只切换 BIG TCP 开关,分别测 IPv4 和 IPv6 下的单流、多流吞吐。
服务端:
bash复制iperf3 -s -6
客户端:
bash复制iperf3 -c <服务端IPv6地址> -6 -t 60 -P 1 -l 1M -w 128M
这里 -l 1M 是应用层每次 write 的数据量,-w 128M 是 socket buffer,这两个值都要给足,否则协议栈没机会攒出大 GSO 包。在 KeyarchOS 上,我先把系统级 TCP buffer 上限也调大了:
bash复制sysctl -w net.core.rmem_max=268435456
sysctl -w net.core.wmem_max=268435456
sysctl -w net.ipv4.tcp_rmem="4096 16777216 268435456"
sysctl -w net.ipv4.tcp_wmem="4096 16777216 268435456"
实测数据有点意思。IPv6 单流场景下,没开 BIG TCP 的时候大约 68Gbps,CPU 软中断占用接近 100%;开完之后直接到了 92Gbps 左右,CPU 占用明显下降。多流场景下提升没那么夸张,但是同样的吞吐下 CPU 余量多了不少,跑别的业务明显更从容。IPv4 场景下,开和不开基本没区别,这正是前面说的协议限制,IPv4 的 Total Length 字段把路堵死了,所以 BIG TCP 的测试一定要用 IPv6 地址,别拿 IPv4 测了半天说没效果。
为了更直观地确认收益来源,我顺手统计了收包速率。同样 90Gbps 吞吐下,包速率从每秒 180 多万降到 60 多万,少了三分之二。这个数据最能说明问题:协议栈处理的包数量实打实地降下来了,CPU 自然就省出来了。
3.4 生产环境落地还要注意的细节
压测通过只是第一步,真要上生产还有几个细节必须处理。
第一个是中断和队列。BIG TCP 省下的 CPU 能不能真正转化为业务收益,取决于网卡队列和中断分布是否合理。确认 ethtool -L 里 combined 队列数量和 CPU 核数匹配,必要时打开 irqbalance 或者手动绑中断,避免出现某个核被打满而其他核闲着的情况。
第二个是 MTU 一致性。虽然 BIG TCP 不改变线缆上的包大小,但如果你起的是 IPv6 + 9000 MTU 的传输路径,整条链路最好都支持 9000 字节的帧。如果中间某个交换机还是 1500 MTU,那么即使开启了 BIG TCP,TCP 自身也会按路径 MTU 调整分段,大包收益直接归零。
第三个是配合监控。BIG TCP 开启后,原来的包速率监控阈值可能不再适用。以前 100Gbps 满载时每秒约 190 万包,开完之后可能只有 60 万包,监控告警基线要跟着调,否则天天误报。
4. 常见问题与排查实录
4.1 sysfs 里根本没有 big_tcp 属性
这是遇到最多的问题。ls /sys/class/net/enp1s0f0np0/ 里翻遍了也找不到 big_tcp 这个文件,甚至 gso_ipv6_max_size 都没有。这种情况基本就是内核版本不够,或者发行版内核没有 backport 相关补丁。先 uname -r 确认版本,再查 /boot/config-$(uname -r) 里的 CONFIG 项。如果确认系统内核确实不支持,要么升级到支持 BIG TCP 的内核,要么换用已经支持该特性的 KeyarchOS 新版本。别指望在旧内核上强行开,这个功能涉及协议栈核心逻辑,不是写个模块就能补上的。
4.2 开了 big_tcp,但 gso_max_size 还是 65536
出现这种情况,先怀疑驱动。驱动在初始化网卡时如果没有设置更大的 gso_max_size,那么即便内核说支持,网卡驱动不认,照样白搭。用 ethtool -i 检查驱动名和版本,再到驱动 release notes 里确认有没有 BIG TCP 支持。Mellanox 的 mlx5_core 驱动比较新的一般没问题,但老版本或者某些 OEM 改过的驱动就可能缺这个能力。另外,确认一下 ethtool -k 里 TSO 是不是真的处于 on 状态,SEGmentation 卸载一旦被关,BIG TCP 也没有发挥空间。
4.3 明明开了,iperf3 测 IPv4 却一点提升都没有
这不是故障,而是预期行为。BIG TCP 突破 64KB 依赖 IPv6 的 Jumbo Payload 机制,IPv4 的头部字段决定了一个 IPv4 包无法承载超过 64KB 的数据,所以在内核里 IPv4 路径的 GSO 大小依然被限制在 65535。如果你的业务必须跑 IPv4,那 BIG TCP 基本帮不上忙,重点应该放在其他调优手段上。新业务、新集群能上 IPv6 尽量上 IPv6,BIG TCP 才能发挥价值。
4.4 开了之后吞吐反而下降
这种情况虽然少见,但我遇到过。排查思路是先把 TSO 和 GSO 的卸载状态确认一遍,因为有些网卡在开启超大 GSO 后,如果固件里的 TSO 分段引擎能力不足,会把切分工作丢回软件,反而增加 CPU 负担。另外内存压力也可能导致性能回退,超大 skb 分配大块内存失败时内核会退化到软件分段甚至丢包。遇到这种情况,建议先把 gso_max_size 降回 128KB 试试,看是否平衡了包数量和内存压力。还有一个容易被忽略的点:如果开启了 GRO,接收方向的合并逻辑也要吃 CPU,在低端机器上 GRO 大包的开销可能抵消发送方向省下的 CPU。
4.5 与 DPDK、XDP、虚拟化的关系
如果你网卡被 DPDK 直接接管了,那这些特性就都跟你无关了,BIG TCP 是内核协议栈的东西,用户态驱动完全不经过内核。XDP 场景下要特别注意,XDP 程序处理的是网卡刚收上来的原始包,如果你的 XDP 程序把 GRO 大包重新切碎或者格式不兼容,可能反而破坏 BIG TCP 的收益。虚拟化场景下,虚拟机里的 virtio 网卡目前基本不支持 BIG TCP,这个特性主要用在物理机、裸金属和宿主机网络路径上。混合部署时要留个心眼,别把宿主机上的参数直接套到 VM 里。
5. 适用范围与决策建议
5.1 适合用 BIG TCP 的场景
我现在判断一个场景适不适合上 BIG TCP,就看三个条件:高带宽、可控网络、CPU 有压力。高带宽不用说,100GbE 以下基本没必要折腾;可控网络指的是数据中心内部,MTU、交换机、路径都是自己说了算的,公网环境 path MTU 不稳定,Jumbo Payload 永远传不出去;CPU 有压力是指协议栈处理已经成为瓶颈,而不是应用层或者存储拖了后腿。
具体来说,AI 训练集群里的参数服务器和存储节点之间的数据传输、大数据混部集群的 shuffle 流量、HPC 作业的大文件读写,都是典型受益场景。这类流量特点是数据量大、方向单一、对时延不敏感,非常适合用 BIG TCP 把包做大,把 CPU 从包处理中解放出来。
5.2 不适合的场景与风险点
小包为主的业务不要指望 BIG TCP。它的逻辑是减少包数量,如果你的业务本身就是大量小包交互,包数量降不下去,开不开没有任何区别。跨公网传输也不行,BIG TCP 依赖 IPv6 Jumbo Payload 和网卡 TSO 卸载,公网上既不支持巨帧,路径上还会有各种未知设备,在线缆上生成超大帧是自杀行为。还有一点,老网卡老驱动不要硬上,收益没吃到,反而可能引入稳定性问题。
这里还要提醒一个运维层面的风险:BIG TCP 是个相对新的特性,内核里涉及的路径不少,生产环境上线前一定要做充分的回归压测,尤其是长连接、多流混跑、故障切换这些场景。我在测试中就发现,某些版本的驱动在开启 BIG TCP 后,RSS 哈希的分布均匀性会受影响,导致多队列负载不均,这种问题只有长时间压测才能暴露出来。
5.3 后续可以继续深挖的方向
BIG TCP 只是高性能网络优化的一环。我这次做完之后,下一步计划把 io_uring 和零拷贝发送结合起来,进一步减少
