夜雨聆风学习资料网

ARTICLE · 1338459

网卡如何提高网络吞吐:队列、卸载、旁路与总线约束

网卡如何提高网络吞吐:队列、卸载、旁路与总线约束

导论

网络吞吐从来不是「网卡更猛」这一个变量,而是四层天花板里最矮的那一层决定的:物理链路能跑多少、网卡内部能并行多少、主机接口(PCIe/DMA/内存)能喂多少、软件栈每包要花多少 CPU。这四层的量级差得很远——链路已经到了 800 Gb/s,PCIe 5.0 x16 单向只有约 63 GB/s,而一颗核跑完整协议栈每秒只能处理一两百万个小包。

本文按「先算账、再逐层拆」的顺序展开:第一章把四个约束换算成具体数字(pps、纳秒、GB/s、核数),确立「小包才是吞吐的真正考场」这一前提,并说明 FEC 如何吃掉标称带宽;第二章讲并行,多队列、RSS、MSI-X 与中断亲和,并点明全文反复出现的硬约束——同一条流永远落在同一个队列——以及发送侧对偶的 XPS 与 qdisc 锁;第三章讲如何少打断 CPU,硬件中断合并与自适应 DIM、软件侧的 gro_flush_timeout,以及吞吐-延迟曲线;第四章讲把逐包工作交给硬件,校验和、TSO/GSO、LRO/GRO、VLAN、隧道的卸载家族与各自的陷阱;第五章讲少搬一次内存,描述符环水位、DMA 与 IOMMU 的隐性开销、巨帧的真实收益与代价,以及链路层流控(pause/PFC)在「丢包」与「反压」之间的取舍;第六章讲把网卡变成「半个 CPU」,SR-IOV、TC flower 硬件流表、SmartNIC/DPU 各自卸载什么、代价是什么;第七章讲绕开内核的四条路——RDMA、DPDK、AF_XDP、XDP,以及它们各自适合谁;第八章讲横向扩展,链路聚合为什么救不了单流、多路径与拓扑收敛比;第九章给出一套分层测量方法,以及六个最常见的「测出来不对」的假象;第十章用一张取舍矩阵把技术按场景组合起来,收尾回答「什么时候该换架构」。

一句话主线:提升吞吐只有三种动作——把逐包成本变成批量成本(合并、巨帧、批量描述符),把 CPU 的活搬到硬件(卸载、流表、DPU),把单核串行变成多核/多设备并行(多队列、RSS、聚合、旁路)。 其余技术都是这三件事的变体。


一、先把账算清楚:四个硬约束

1.1 线速与包长:pps 与 Gbps 是两个坐标

同样 100 Gb/s,64 B 小包是 148.8 Mpps,1518 B 大包只有 8.1 Mpps,相差 18 倍。所有「吞吐」讨论必须先锁定包长,否则数字没有意义。

速率
64 B 帧(84 B 线路占用)
1518 B 帧(1538 B 线路占用)
10 GbE
14.88 Mpps
0.81 Mpps
25 GbE
37.2 Mpps
2.03 Mpps
50 GbE
74.4 Mpps
4.07 Mpps
100 GbE
148.8 Mpps
8.13 Mpps
200 GbE
297.6 Mpps
16.3 Mpps
400 GbE
595.2 Mpps
32.5 Mpps
800 GbE
1190.5 Mpps
65.0 Mpps

还要扣掉线路编码与 FEC 的开销,而且不同 PMD 扣的不一样:

PMD 类别
编码 / FEC
净荷折算
标称 100G 的实际上限
10GBASE-R(无 FEC)
64b/66b
线路 10.3125 GBd,净荷本身就是 10 Gb/s
—
25GBASE-R、100GBASE-SR4/CR4
RS-FEC(528,514)
×514/528
约 97 Gb/s
100GBASE-DR/FR/LR、400G/800G 主流 PMD
RS-FEC(544,514)
×514/544
约 94.5 Gb/s

别把 64b/66b 的 3% 与 FEC 叠加扣两次:标称的 10G/25G/100G 指的就是 MAC 净荷速率,64b/66b 已经体现在线路波特率里(10GbE 对应 10.3125 GBd)。所以「100G 口实测 94 Gb/s」是 DR/FR 类模块的正常上限,换成 SR4 模块能多出约 3 Gb/s——差距来自 FEC 方案,不是网卡配置。

1.2 CPU 的账:一颗核能吃多少 pps

一个包走完整内核路径(中断、NAPI、skb 分配、四层解析、socket 查找、拷贝到用户态)的典型成本是 300–800 ns,即单核 1.2–3 Mpps。把这张账和上表对齐:

场景
需要的 pps
纯内核栈需要的核数(按 2 Mpps/核)
10G 小包
14.9 M
约 7 核
100G 小包
148.8 M
约 74 核
100G 大包
8.1 M
约 4 核
800G 小包
1190 M
约 595 核(不可能)

结论很直接:大包场景瓶颈在链路和 PCIe,小包场景瓶颈在 CPU。 小包要跑到线速,靠加核是条死路(核数、内存带宽、锁竞争都会先崩),必须卸载、合并或旁路。

1.3 总线账:PCIe 代际与速率的匹配关系

PCIe 是每方向独立的通道,而以太网是全双工,所以「网卡单向吞吐」必须和「PCIe 单向带宽」对齐比。

PCIe 代际
每 lane 原始速率
编码
x16 单向带宽
单向可承载的以太网
3.0
8 GT/s
128b/130b
约 15.7 GB/s
100G(12.5 GB/s)刚好,无余量
4.0
16 GT/s
128b/130b
约 31.5 GB/s
2×100G(25 GB/s)可以
5.0
32 GT/s
128b/130b
约 63 GB/s
400G(50 GB/s)可以;800G(100 GB/s)不够
6.0
64 GT/s
PAM4 + FLIT
约 128 GB/s
800G 可以
7.0
128 GT/s
PAM4 + FLIT
约 256 GB/s
1.6T 可以

注意口径:3.0~5.0 三行已扣掉 128b/130b 的开销,6.0/7.0 两行给的是编码前原始速率——FLIT 头与轻量 FEC 还要再吃掉 5%–8%,所以 6.0 x16 实际可用约 120 GB/s 而不是 128。

由此得到两条很实用的排障经验:

  • 「标称 200G、实测 100G 出头」多半是总线问题,但要先分清单口还是双口。 单口 200G 需要 25 GB/s 单向:PCIe 4.0 x16(31.5 GB/s)够用但余量只剩四分之一,插槽降到 x8(15.7 GB/s)就只够约 126 Gb/s——这正是「200G 卡跑出 100G 出头」的典型成因;双口 2×200G 需要 50 GB/s,4.0 x16 根本喂不饱,必须上 5.0 x16(63 GB/s)。
  • 800G 网卡在 PCIe 5.0 平台上物理上跑不满。 这也是 800G 网卡普遍配套 PCIe 6.0 平台的原因(PCIe 7.0 规范已于 2025 年 6 月发布,128 GT/s,x16 双向 512 GB/s)。

1.4 内存与 NUMA:看不见的第三个天花板

网卡写内存、CPU 读内存,同一份数据在内存里至少被访问两次;100 Gb/s 单向意味着约 12.5 GB/s 的持续 DMA 写入,叠加协议栈读取和 socket 拷贝,对内存带宽和 LLC 的压力远超直觉。跨 NUMA 访问会把延迟抬高 1.5–2 倍、带宽近乎减半,所以队列中断、DMA 内存、应用线程必须同 NUMA node,否则多队列再多也会被远端内存拖住。

1.5 软件栈的固定成本(一句话)

系统调用、skb 生命周期、分层解析、锁、唤醒——这些每包固定开销是小包吞吐的死敌。


二、并行:多队列、RSS 与中断向量

2.1 队列是网卡的并行单位

一个队列(queue)由三样东西绑在一起:独立的描述符环(DMA 收发环形缓冲)、独立的中断向量(MSI-X)、独立的 NAPI 实例(内核侧轮询上下文)。理想情况三者 1:1——MSI-X 向量不够时多个队列会共享一个向量,但每个 RX 队列仍有自己的 NAPI 实例;所以「队列数」大体就是网卡的并行度。

ethtool -l eth0          # 查看当前/最大 combined、rx、tx 队列数ethtool -L eth0 combined 16

队列数不是越多越好,受三个上限约束:MSI-X 表项(PCIe 规范上限 2048 向量,网卡通常实现 64–256)、网卡内部缓存与调度资源、每个队列的描述符环内存。实践中队列数 ≈ 参与处理的核数最划算;队列远多于核数时,多个队列抢同一个核,反而增加缓存抖动和调度开销。

2.2 RSS:把不同的流散到不同队列

RSS(Receive Side Scaling)是网卡侧的硬件分流:对报文的四元组(可含 L4 端口)做 Toeplitz 哈希,用一张 indirection table(典型 128 项)把哈希值映射到队列号。哈希 key 通常 40 字节,可由驱动/ethtool 设置。

ethtool -x eth0                 # 查看 indirection table 与 hash keyethtool -X eth0 equal 16        # 均分到 16 个队列ethtool -X eth0 weight 8 8 4 4  # 按权重分配

RSS 带来三个必须记住的结构性限制:

  1. 同一条流永远落在同一个队列。 这是为了保序(乱序会被 TCP 当成丢包),代价是单流吞吐上限 = 单队列 + 单核的处理能力。哪怕有 64 个队列,一个 TCP 连接也只能用一个。这条限制解释了后面几乎所有「单流打不满」的现象。
  2. 哈希只覆盖网卡能解析到的层。 普通 IP 报文没问题;但 VXLAN/Geneve 这类隧道,若不开启内层哈希(需要网卡支持,对应 ethtool 键 rx-udp_tunnel-port-offload,net/ethtool/common.c:69,以及驱动特定的 tunnel RSS 配置),整条隧道会退化成「按外层五元组哈希」——一条隧道一个队列,多租户场景下极易打爆单核。
  3. 在线改 indirection table 会让在途流短暂乱序。ethtool -X 重排映射后,同一条连接的新包可能落到新队列,与旧队列里还没处理完的包乱序,TCP 会当成丢包而掉窗。改表尽量在低峰一次改到位,别在压测中途反复调。

另外,做 NAT/负载均衡的中间设备建议使用对称哈希(源/目的互换后哈希一致),否则一条连接的两个方向可能落在不同队列、不同设备,会话状态和队列水位都对不上。

2.3 把队列钉到核上

grep eth0 /proc/interrupts        # 看每个队列的 MSI-X 向量号与分布ethtool -S eth0 | head            # 队列级统计,确认队列在工作

irqbalance 在吞吐场景往往帮倒忙(会把同一网卡的队列摊到跨 NUMA 的核上)。常见做法是关掉 irqbalance,按队列手工写 /proc/irq/<n>/smp_affinity,或由驱动/脚本一次性绑定;同时确认每一列中断计数大致均匀——某一列远高于其他,就是哈希不均或分流失效。

2.4 硬件定向:flow director / ntuple

RSS 是「按哈希平均」,而 ntuple 过滤是「按规则精确投递」:

ethtool -N eth0 flow-type tcp4 src-ip 10.0.0.1 dst-port 3306 action 3ethtool -n eth0                   # 查看已下发的规则

用于把关键流(数据库复制、存储流量、交易行情)钉到专用队列/专用核,把控制面与数据面隔离。规则命中优先级高于 RSS;表容量通常只有几百到几千条,且规则不匹配时会回落到 RSS,这点在排障时很容易被忽略。

2.5 软件侧的补位(结论)

队列少于核数时,靠内核的 RPS/RFS/aRFS 在软件层二次分流;aRFS 还能让网卡把「某条流该去哪个核」的映射回报给硬件,形成硬件加速的 RFS。

2.6 发送侧的并行:XPS 与 qdisc 锁

RSS 只解决收包。发送方向能不能多核并行,取决于另外两件事,而它们经常被忽略——这也解释了为什么「收能打满、发打不满」:

  1. XPS(Transmit Packet Steering):用一张 CPU 掩码决定「哪些核允许用哪个 TX 队列」,入口是 /sys/class/net/eth0/queues/tx-*/xps_cpus,内核侧 netif_set_xps_queue()(include/linux/netdevice.h:4107)。没配 XPS 时,多个核会集中往少数 TX 队列里塞包,在队列锁上互相等。
  2. qdisc 的串行化:每个 netdev_queue 有一把 _xmit_lock(netdevice.h:709);非 lockless 的 qdisc 还要靠 qdisc_run_begin() 的 running 位保证同一时刻只有一个 CPU 在跑这个 qdisc(include/net/sch_generic.h:202)。只有带 TCQ_F_NOLOCK 的 qdisc(sch_generic.h:92,noqueue 与 mq/mqprio 的叶子队列属这类)才真正允许多核同时发包。
tc qdisc show dev eth0                              # 期望看到 mq / mqprio,而不是单个 pfifo_fastcat /sys/class/net/eth0/queues/tx-*/xps_cpus        # 每个 TX 队列允许哪些核echo f > /sys/class/net/eth0/queues/tx-3/xps_cpus   # 掩码 f = 只允许 CPU 0-3

这与 RSS 是同构的:收靠 RSS 把流分到队列,发靠 XPS + 多队列 qdisc 把流分到队列,两边都被「同一条流只走一个执行单元」锁住。所以单流双向打满靠的是单队列 + 单核的能力,堆核数没用。


三、少打断 CPU:中断合并与自适应

3.1 中断的真实成本

100G 小包按「每包一次中断」算是 1.49 亿次/秒。一次中断的进入/退出、寄存器保存、缓存污染合计 1–2 µs,算下来需要 150–300 秒 CPU 时间才能服务 1 秒的流量——物理上不可能。中断合并(interrupt coalescing)是让这条路可行的前提。

3.2 参数语义

ethtool -c eth0ethtool -C eth0 rx-usecs 16 rx-frames 64 adaptive-rx on
参数
语义
调大后的效果
rx-usecs
 / tx-usecs
收到第一包后等待多少微秒再上报中断
中断变少、延迟变高
rx-frames
 / tx-frames
累积多少帧就上报中断
中断变少、延迟变高
rx-usecs-irq
 / tx-usecs-irq
中断正在被主机服务期间
生效的另一组阈值(include/uapi/linux/ethtool.h:404-408)
理论上可再压中断数;但多数现代驱动没实现,ethtool -c 里显示 n/a
adaptive-rx
 / adaptive-tx
由 DIM 算法按流量动态调参
无需手工调,兼顾两段

usecs 与 frames 是谁先到谁触发:小包高 pps 场景下 frames 先到,大包低 pps 场景下 usecs 先到。合并之后,单队列中断从「百万级/秒」降到「几万级/秒」,中断成本从主导项变成零头。

内核侧的自适应实现是 DIM(lib/dim/net_dim.c):它把「每包处理成本」和「每次中断成本」建模,在吞吐与延迟之间做二分调整。

3.3 吞吐-延迟曲线:这里没有免费午餐

目标
典型配置
代价
吞吐优先(大流量转发、备份)
rx-usecs 32 rx-frames 64 adaptive-rx on
单包延迟增加数十微秒,尾延迟变差
均衡(通用服务)
adaptive-rx on
,让 DIM 自己调
极端流量下可能抖动
延迟优先(交易、实时)
关闭合并(rx-usecs 0 rx-frames 0)+ SO_BUSY_POLL
CPU 占用显著上升,吞吐反而可能下降

高吞吐与低延迟在这条曲线上是相反方向,这是网卡调优最基本的取舍,任何「既要又要」的方案(如自适应 DIM + 定时轮询)本质是在曲线上找折中点。

3.4 busy poll:用 CPU 换延迟

net.core.busy_poll(微秒)与 SO_BUSY_POLL 让 socket 在等数据时直接轮询网卡队列,跳过「睡眠—中断—唤醒」这条链路。它属于低延迟手段,在高吞吐场景会烧掉整核,与上一节的中断合并是两种相反的策略。

轮询把整核占住,所以还要配套两件事,否则省下的延迟会被抖动吃掉:把核隔离出去(isolcpus=、nohz_full=,kernel-parameters.txt:2739、:4513),以及关掉深度 C-state——CPU 一旦进 C6,退出延迟是几十微秒量级,轮询探到的「空闲」反而更贵。

3.5 第三层:软件中断合并(gro_flush_timeout + napi_defer_hard_irqs)

硬件合并之外,NAPI 里还能再做一层「合并中断」:把硬中断推迟几次轮询,改用高精度定时器批量 flush GRO。两个旋钮挂在网卡级 sysfs:

cat /sys/class/net/eth0/napi_defer_hard_irqs   # 连续多少次轮询内有工作量,就继续不开中断cat /sys/class/net/eth0/gro_flush_timeout      # 单位 ns:定时器到点才 flush GRO

语义在 net/core/dev.c:6776-6803:一轮 poll 有工作量时,先把 defer_hard_irqs_count 重置为 napi_defer_hard_irqs;只要它仍大于 0,就把 IRQ 保持关闭、改由 gro_flush_timeout 到点触发 gro_flush_normal()。内核给软件设备准备的默认值就在 netdev_sw_irq_coalesce_default_on()(dev.c:11982-11988):20000 ns + defer 1。

它比硬件合并更省中断——因为合并发生在 GRO 之后,一次定时器能带出一大批包,而不是按「帧数/微秒」提前上报。代价是延迟直接由 gro_flush_timeout 决定:调成 100 µs 以上,单包延迟就跟着走。这是 100G 收包压软中断的常用手段,也是「中断数降到几万/秒、但尾延迟变差」的主要原因之一。


四、把逐包工作交给硬件:卸载家族

4.1 一张表看清 offload

ethtool -k eth0     # 全量特性表ethtool -K eth0 <feature> on|off
特性
ethtool 键(7.1)
卸载的是什么
主要收益
主要陷阱
收包校验和
rx-checksum
(≤6.x:rx-checksumming)
每包 O(n) 校验和计算
协议栈少扫一遍报文,GRO 也免复算
关掉后 GRO 仍会合并,但每包要在软件里补算校验和,且校验失败会被 flush(net/ipv4/tcp_offload.c:420-426)
发包校验和
tx-checksum-ip-generic
发送前逐段算校验和
省 CPU、省一次内存写
虚拟化下由 host 兜底,guest 侧「免费」是假象
TCP 分段
tx-tcp-segmentation
(≤6.x:tcp-segmentation-offload,TSO)
按 MSS 切分大包
每段不再一次驱动入口/DMA 映射
抓包会看到「巨型包」,易误判
UDP 分段
tx-udp-segmentation
(USO)
UDP 大包切分
批量发送、省调用
依赖 MTU 与 GSO 尺寸配置
收包合并
rx-lro
(≤6.x:large-receive-offload,LRO)/ rx-gro-hw
硬件把同流小包合成大包
协议栈每包开销摊薄
转发/路由场景禁用
(见 4.3)
分散聚集
tx-scatter-gather
(≤6.x:scatter-gather)
多段内存一次 DMA
免去线性化拷贝
fraglist 与 skb 布局相关,排障复杂
VLAN 剥离/插入
rx-vlan-hw-parse
 / tx-vlan-hw-insert(≤6.x:rx-vlan-offload / tx-vlan-offload)
VLAN tag 处理
省一层解析
与桥接/多租户 tag 策略相互影响
隧道端口
rx-udp_tunnel-port-offload
隧道解封装与内层哈希
隧道流量也能 RSS 分散
端口表有限,配置漏了会退化为单队列
硬件哈希上报
rx-hashing
把 RSS hash 写进描述符
内核免重算,GRO/RPS 直接复用
需要 L4 型 hash 才有意义
硬件流表
hw-tc-offload
TC flower/OVS 规则下发网卡
转发不进内核(见 6.2)
表容量有限、首包走慢路径
加解密
esp-hw-offload
 / tls-hw-tx-offload、tls-hw-rx-offload
IPsec/kTLS 的对称运算
每包加密成本归零
算法/密钥协商受硬件支持矩阵约束

⚠️ 7.x 把一批特性字符串改成了 tx-* / rx-* 前缀(net/ethtool/common.c:14-75,并非全部——hw-tc-offload、esp-hw-offload、rx-hashing 保持原样):rx-checksumming→rx-checksum、tcp-segmentation-offload→tx-tcp-segmentation、large-receive-offload→rx-lro、scatter-gather→tx-scatter-gather、rx-vlan-offload→rx-vlan-hw-parse。ethtool -k 打印的就是这张表,-K 也按这串名字匹配,所以在 7.1 上写旧名会匹配不上、直接报错。动手前先跑一遍 ethtool -k,照本机打印的名字写。

4.2 校验和卸载的三种语义(一句话版)

收包侧硬件验证过 → CHECKSUM_UNNECESSARY(GRO 直接跳过复算);硬件给出整包校验和 → CHECKSUM_COMPLETE;发包侧「从某偏移开始由硬件算」→ CHECKSUM_PARTIAL。这三个状态贯穿全栈,是理解「为什么关掉 rx-checksum 吞吐掉一半」的关键。

4.3 LRO 与 GRO:名字像,用途完全不同

  • GRO 是软件合并(内核 NAPI 里做),可以按需关闭、可以保留足够信息;
  • LRO 是硬件合并,把多个同流报文合成一个超大包再上交,省 CPU 最狠,但破坏了「一包一决策」的语义:转发/路由/防火墙需要逐包看头部,合并后时间戳、校验和、TTL 语义都会失真,所以这类设备上必须关闭 LRO(或改用 rx-gro-hw,它在保留语义的前提下做硬件 GRO)。

判断标准很简单:这台机器是流量的终点还是中转站? 终点可以开 LRO,中转站不能。

4.4 TSO 的收益为什么这么大

没有 TSO 时,一个 64 KB 的发送窗口要拆成约 44 个 MSS 段,每段各走一次驱动入口、一次 doorbell(写网卡寄存器)、一次 DMA 映射。有 TSO 时应用写一个大 skb,网卡自己切分并逐段补校验和——CPU 侧的调用次数从「段数」变成 1。这就是「大包吞吐看 PCIe,小包吞吐看卸载」的由来:TSO 优化的是发送侧每字节的固定成本,而不仅是带宽。

4.5 卸载的边界

卸载不是魔法,只是把成本从 CPU 搬到硬件:硬件队列深度、规则表项、描述符格式都是有限的;而且卸载会削弱可观测性——抓到的包不再是线路上真实的包。生产环境建议保留一套「关闭 LRO/TSO 的观测口」,否则很多问题在抓包层面根本看不见。


五、少搬一次内存:描述符环、DMA 与巨帧

5.1 描述符环是「在途容量」,也是第二个串行点

每个队列的 ring 就是它能容纳的在途包数。环太小 → 突发时来不及回收,硬件直接丢包(rx_missed_errors、rx_no_buffer_count 上涨);环太大 → 缓存失效、延迟上升、内存占用增加。

ethtool -g eth0            # 当前与最大 ringethtool -G eth0 rx 4096    # 100G 建议 4096 起ethtool -S eth0 | grep -E 'missed|no_buffer|dropped'

经验值:10G/25G 用 1024–2048,100G 用 4096,400G/800G 往往需要 4096 以上并配合更多队列。判断依据不是「调大就好」,而是丢包计数是否归零、以及延迟是否可接受。

同时要意识到:单个 ring 是串行消费的,一个队列一次只能由一个 NAPI 上下文收割。所以「单队列 ring 的处理速率」也是单流吞吐的天花板之一。

5.2 DMA 与 IOMMU:总线上的隐性开销

  • 写合并(write combining)/ DMA coalescing:把多个小 DMA 写合成一次总线事务,减少 PCIe TLP 数量,这是网卡内部一项常被忽略的吞吐优化。
  • 收发方向并不对称:收包是设备向内存 write,可以流水、乱序完成;发包是设备从内存 read,每个请求都要等一个 completion,受 outstanding read 数量与 MRRS 限制。所以同一个 x16 插槽上「收得动、发不动」是常见现象,瓶颈在 PCIe 的读并发度而不是标称带宽。
  • MPS / MRRS:Max Payload Size 与 Max Read Request Size 决定单个 TLP 能搬多少字节。BIOS 常把 MPS 保守设成 128 B;pcie_bus_perf 会按父总线允许的最大值设 MPS,并把 MRRS 提到最大(Documentation/admin-guide/kernel-parameters.txt:5166-5170),对大包 DMA 吞吐有直接帮助。
  • IOMMU:开启后每包都要做地址翻译/映射,小包场景开销可观。虚拟化必须开;裸金属追求极限吞吐时常用 iommu=pt(passthrough,设备走恒等映射)。既要隔离又要吞吐,可用 iommu.strict=0(lazy 模式,延迟 TLB 失效换取吞吐,kernel-parameters.txt:2627-2633)——代价是设备隔离性下降。
  • DMA 掩码:老驱动只声明 32 位 DMA 能力时会限制可用内存区间并增加 bounce buffer 拷贝,现代 64 位网卡不应出现。

5.3 巨帧:用「包更大」换「包更少」

MTU 从 1500 提到 9000,同样的数据量包数降到约 1/6,每包固定成本被摊薄近 6 倍——这是不改硬件、不改架构就能拿到的最大一笔吞吐收益。

ip link set eth0 mtu 9000ping -M do -s 8972 <peer>      # 验证端到端真的支持

代价必须一起算:

收益
代价
包数降为约 1/6,pps 压力骤降
全链路必须一致(交换机、虚机、存储后端),任何一跳 1500 就会 PMTU 黑洞
每包 CPU 成本摊薄,中断/描述符压力下降
单包串行化时间变长(9 KB 在 100G 上占线约 0.72 µs),小包混跑时尾延迟变差
大文件传输吞吐显著提升
丢包重传代价放大 6 倍,无损网络要求更高

适用:存储网络、备份链路、集群内部东西向流量;不适用:公网出口、多租户混合流量、以低延迟小包为主的业务。

5.4 页面复用与零拷贝(结论)

  • page pool:把「每包一次 buddy 分配/释放」变成「从池里取、还回池」,同时缓存 DMA 地址,避免每包重新映射——100G 收包路径的必备项,见《page pool 设计原理与使用》。
  • 发送侧零拷贝:sendfile()/splice() 省掉用户态到内核的拷贝;MSG_ZEROCOPY 省掉内核到网卡的拷贝(但需要处理 completion 通知)。
  • 接收侧零拷贝:AF_XDP 的 UMEM、devmem TCP 让网卡 DMA 直接落到用户态内存,见《内核网络零拷贝实现与 skb 设计》。

5.5 链路层流控:要么丢包,要么反压

队列满的时候只有两种结局:丢掉(让上层重传)或反压(让对端先别发)。这条选择题对有效吞吐的影响,比调 ring 大小大得多,却常被跳过。

ethtool -a eth0                                  # 看当前 pause 帧配置ethtool -A eth0 autoneg on rx on tx on           # 开全局 pause
  • 802.3x pause(全局反压):缓冲区快满就发 pause 帧让对端停。代价是整条链路一起停(HOL 阻塞),一个慢接收方的影响会被放大到这条链路上的所有流。
  • PFC(802.1Qbb,按优先级反压):只暂停某个优先级,是无损网络(RoCEv2)的前提;配不好会带来 PFC 风暴和死锁(见 8.3)。
  • 共享 buffer 与水线:100G+ 网卡把片上 buffer 按 pool 划分,xoff/xon 水线太紧会频繁 pause、太松就退化成 tail-drop。这部分不在 ethtool 里,要看厂商工具或 devlink sb(net/devlink/sb.c,devlink sb pool show / devlink sb occupancy show)。

经验规则:以 TCP 为主的通用流量宁可丢包也别开全局 pause(TCP 的拥塞控制比链路反压更懂全局);RDMA 必须靠 PFC + ECN 做到近无损;两者混跑要按优先级隔离,否则一条 RDMA 流就能把整链路的 TCP 流量一起按停。


六、把网卡变成「半个 CPU」:SR-IOV、硬件流表与 DPU

6.1 虚拟化:两条路径,性能差一个数量级

路径
数据怎么走
瓶颈
适用
virtio + vhost
guest → virtqueue → vhost 线程 → host 栈 → 物理网卡
每包一次 kick/中断,vhost 线程成为单点
通用云主机、需要热迁移
SR-IOV VF 直通
guest 直接持有 VF 的队列与中断,DMA 直达 guest 内存
VF 数量、迁移能力、host 不可见
高性能实例、NFV
echo 4 > /sys/class/net/eth0/device/sriov_numvfs   # 创建 4 个 VF

SR-IOV 的本质是把队列和中断「切」给虚机,让 guest 绕过 host 协议栈和 vSwitch,性能接近裸金属。代价是可观测性(host 看不到 VF 内部流量)、迁移复杂度和 IOMMU 分组约束。折中方案是 vhost-user + DPDK:把 vSwitch 搬到用户态轮询,仍保留迁移能力。

注意一个常见误判:guest 里 ethtool -k 显示 TSO/GSO 全开,不代表真的卸载了。 virtio 的 offload 往往是「假卸载」——guest 侧省了,host 侧由 vhost 用软件补上,总成本没降。

6.2 硬件流表:让转发完全不进内核

tc filter add dev eth0 ingress protocol ip flower \   dst_ip 10.0.0.5 skip_sw action mirred egress redirect dev eth1

skip_sw 的含义是「必须由硬件执行,硬件不支持就报错」——这一个参数决定了规则是真卸载还是假卸载。首包走慢路径建流,后续同流报文由网卡 eSwitch 直接转发,主机 CPU 归零。

  • 收益:转发吞吐从「每包一次内核处理」直接到线速,且不占核;
  • 代价:流表容量(几千到几万条)、首包延迟、规则变更的抖动、以及调试困难(包没进内核,tcpdump 看不到)。

OVS 的 hardware offload 是同一机制的规模化版本,云场景常见。

6.3 SmartNIC / DPU:卸载的边界在哪里

DPU 的形态是「多核 ARM + 加速器 + 独立内存 + 独立 PCIe 通道」,典型卸载对象:vSwitch/OVS、IPsec/MACsec、TLS、NVMe-oF、压缩、正则匹配。

判断要不要上的算式很朴素:(被卸载的每包 CPU 成本 × pps)是否显著大于 DPU 的固定成本(采购、功耗、运维、可编程资源上限)。对 100G 小包转发、加密存储网关这类「每包成本高且量大」的场景成立;对普通 Web 服务往往不成立——把 RDMA 或简单卸载开好就够了。


七、绕开内核:RDMA、DPDK 与 AF_XDP

7.1 为什么必须绕

内核路径的固定成本是「系统调用 + skb + 分层解析 + 拷贝 + 调度」,量级在微秒;而线速小包允许的每包预算只有 100 ns 级。差了一个数量级,靠优化参数补不回来,只能换路径。

7.2 RDMA:让远端 CPU 完全不参与

  • 形态:InfiniBand、RoCEv2(以太网承载)、iWARP;
  • 机制:QP(队列对)+ CQ(完成队列),应用直接向网卡下发工作请求,网卡 DMA 读写已注册内存;支持单边 READ/WRITE——对端 CPU 不参与、不拷贝、不进协议栈;
  • 收益:100G/400G 线速而 CPU 接近空闲,延迟进入亚微秒级;
  • 前提:RoCEv2 需要「无损或近无损」网络——PFC 防丢包 + ECN/DCQCN 防 incast,配置不当会在多打一时吞吐塌陷;
  • 适用:AI 训练集合通信(AllReduce/AllToAll)、分布式存储、HPC;
  • 不适用:连接数巨大、短连接的通用业务(QP 资源与连接模型不匹配)。

7.3 DPDK:把网卡交给用户态独占

  • PMD 轮询模式驱动 + 大页内存 + 独占核 + burst 收发(典型 32 个一批);
  • 无中断、无系统调用、无协议栈,单核可做到 10–20 Mpps 量级;
  • 代价:独占核(必须给 OS 留核)、没有内核协议栈(要么自研轻量栈要么只做转发)、运维与可观测性全靠自己。

7.4 AF_XDP / XDP:折中路线

XDP 在驱动最早点执行 eBPF 程序,可以 PASS/DROP/TX/REDIRECT;AF_XDP 让 XDP 把包重定向到用户态 UMEM,实现零拷贝到应用,同时保留内核的管理面(路由、防火墙、工具链)。

适合「比内核快、比 DPDK 好集成」的场景(如负载均衡、采集、轻量网关)。限制在于需要驱动支持 XDP native 模式与独立的 XDP 队列,且一个 XDP 队列仍是单核消费。

7.5 四条路线对比

方案
100G 小包可行性
每包 CPU
延迟
隔离性/生态
典型用途
内核栈(调优后)
需数十核,实际不可行
300–800 ns
数十 µs
生态最全
通用服务
AF_XDP
可行(多队列多核)
100 ns 级
十 µs 级
保留内核管理面
网关、采集、LB
DPDK
可行(独占核)
数十 ns
亚十 µs
需自建栈,隔离好
NFV、转发、测试仪表
RDMA
线速最轻松
近零(网卡承担)
亚 µs
需专用语义与无损网络
AI、存储、HPC

八、横向扩展:聚合、多路径与拓扑

8.1 链路聚合救不了单流

ip link add bond0 type bond mode 802.3ad \   xmit_hash_policy layer3+4

bond 的哈希按流分发到成员口,因此聚合带宽只对多流有效:两块 100G 做 bond,单条 TCP 连接仍然是 100G,永远到不了 200G。这一点和 RSS 是同一条物理规律在不同层的重复——流的保序需求把并行度锁死在一个执行单元上。

模式选择上,802.3ad(LACP)需要交换机配合但可检测链路故障;balance-rr 会把单流拆到多口,但会造成乱序,TCP 场景下反而降吞吐。

8.2 多路径与协议层聚合

MPIO(存储侧多路径)、NFS pNFS、MPTCP 都能做到「单连接跨多路径」,但都需要两端与中间设备同时支持;它们解决的是单流天花板,代价是复杂度和乱序/重排开销。

8.3 拓扑与收敛比:端到端线速的前提

  • 收敛比:leaf-spine 1:1 无超订才能保证任意两点线速;3:1 超订在 incast 下必然排队。
  • ECMP:多路径负载均衡同样受「五元组哈希」限制,单流仍走单路径。
  • 单机多卡:按 NUMA/rail 就近分配,避免跨 socket 转发;GPU 直连网卡的 rail-optimized 拓扑是 AI 集群标配。
  • 无损域:PFC 配置要有边界,PFC 风暴和 HOL 阻塞会把局部拥塞放大成全域网吞吐塌陷。

九、怎么证明吞吐真的上去了

9.1 分层测量:从物理层往上查

层
命令/文件
看什么
链路
ethtool eth0
、ethtool -S eth0
Speed/Duplex 是否协商到位;rx_errors/crc_errors/frame_errors
队列/中断
/proc/interrupts
、ethtool -S
各队列中断计数是否均匀;rx_missed_errors/rx_no_buffer_count
软中断
/proc/net/softnet_stat
、mpstat -P ALL 1
第 2 列 drop、第 3 列 time_squeeze、第 12 列 backlog 长度;%soft 是否单核打满
协议栈
ss -ti
、netstat -s
cwnd/rtt/retrans、重传与丢包计数
应用
iperf3 -c <ip> -P 8 -t 30 -w 4M --bidir
多流 vs 单流、双向同时压测

softnet_stat 的列序值得记准(net/core/net-procfs.c:145-155):第 1 列 processed、第 2 列 dropped、第 3 列 time_squeeze、第 10 列 received_rps、第 11 列 flow_limit_count;7.1 还多出第 12~15 列——backlog 总长(input + process)、CPU 号、input 队列长度、process 队列长度。第 12 列是查「哪颗核积压了」最快的入口,比只看 dropped 有用。

第 2 列非零说明 backlog 溢出,通常是单核软中断处理不过来或 RPS 配置不均;第 3 列增长说明 NAPI budget 被耗尽——两者含义不同,别混。

9.2 六个最常见的假象

现象
真实原因
对策
100G 网卡实测只有 30–40 G
单条 TCP 流 + 窗口不足(BDP 限制)
用 -P 8 多流;调 tcp_rmem/wmem 到 BDP 以上
标称 200G 只跑到 100G 多
PCIe 代际/宽度(x8 或 4.0)
查 lspci -vv 的 LnkSta,换插槽/平台
抓包看到 64 KB 的「巨包」
GSO 尚未落地,抓的是发送路径
属正常;判断线路上真实包长要看对端或交换机计数
吞吐锯齿状波动
队列浅丢包重传、PFC 反压、pacing 缺失
加 ring、配 BQL/pacing、检查无损域配置
CPU 很闲但吞吐上不去
接收端存储/应用锁/单线程处理
查应用侧;不是网络问题
虚机里 offload 全开却很慢
vhost 软件兜底、virtio 通知开销
换 SR-IOV 或 vhost-user+DPDK

9.3 两个天花板公式

  • 单流上限 ≈ min(链路速率, 窗口/RTT, 单队列处理能力, 单核处理能力)
  • 多流上限 ≈ min(链路速率, 全部队列/全部核的处理能力, PCIe 单向带宽, 内存带宽)

遇到「跑不满」时,先判断卡在哪一项,再决定是调参(窗口、ring、队列)还是换架构(旁路、DPU)。


十、取舍矩阵:按场景组合技术

场景
必开
建议
不要
高并发 Web/API(多小流)
多队列+RSS、TSO/GSO、GRO、中断自适应
SO_REUSEPORT
、RPS 补位、ring 2048
开 LRO 后做转发;把所有队列绑一个核
低延迟交易/行情
关闭中断合并、SO_BUSY_POLL、队列绑核
ntuple 定向关键流、NUMA 就近、iommu=pt
巨帧(串行化延迟);自适应合并
AI 训练/分布式存储
RDMA(RoCEv2)、巨帧、无损网络
rail 拓扑、GPUDirect、多路径
用 TCP 栈跑集合通信
云主机/虚拟化
SR-IOV 或 vhost-user+DPDK、host 侧 GRO
DPU 卸载 vSwitch/加密
依赖 guest 的「假 offload」做容量规划
边缘转发/NFV
DPDK 或 AF_XDP、硬件流表
多队列独占核、零拷贝
逐包进内核 + iptables 长链
备份/大数据传输
巨帧、TSO、大 ring、多流
链路聚合、pacing 防丢包
单流压测(永远测不出上限)
研发/观测环境
关闭 LRO/TSO/GSO 保证抓包真实
单独留一个观测口
用生产配置抓包下结论

落地 checklist(十二条)

  1. 先确认 Speed/Duplex 和 lspci 的链路宽度,别在错误的物理前提上调软件。
  2. 队列数设为参与处理的核数,队列与核、内存同 NUMA node。
  3. RSS indirection table 与队列数对齐,隧道场景确认内层哈希已开。
  4. 开 adaptive-rx;吞吐场景再叠加 rx-frames 64,仍压不住软中断就用 gro_flush_timeout + napi_defer_hard_irqs(注意延迟会跟着走)。
  5. 校验和、TSO/GSO、SG 全开;转发设备关 LRO。
  6. 100G 起把 ring 提到 4096,用 rx_missed_errors 验证而不是凭感觉。
  7. 内网存储/备份链路开巨帧,并做端到端 ping -M do 验证。
  8. 单流跑不满先算 BDP,再考虑多流或 MPTCP,别先怀疑网卡。
  9. 虚机性能问题先分清「真卸载」还是「vhost 兜底」。
  10. 上 DPU/RDMA 之前,先算「每包成本 × pps」这笔账,确认收益大于固定成本。
  11. 发送侧单独验一遍:tc qdisc 是 mq/mqprio(叶子队列带 TCQ_F_NOLOCK)、xps_cpus 按核配好;单流发送本来就只能用一个队列,别拿它当多核指标。
  12. 流控策略要显式确认:ethtool -a 核对 pause/PFC——TCP 通用流量不开全局 pause,RDMA 域必须 PFC + ECN,混跑时按优先级隔离。

结语:天花板在哪里

把四层天花板的量级并排放在一起,结论一目了然:

层
100G 场景的天花板
800G 场景的天花板
物理链路
约 94 Gb/s(扣 FEC)
约 750 Gb/s
PCIe x16
5.0 足够(63 GB/s)
需 6.0(128 GB/s),5.0 物理不够
单核软件栈
约 15–35 Gb/s(1500 B 报文);64 B 小包只有 1–2 Gb/s
完全不可行
单流(窗口/RTT 限制)
数 Gb/s 到数十 Gb/s
需 RDMA 级机制

所以「提升网卡吞吐」这件事的正确答案从来不是某个开关,而是认清当前瓶颈在哪一层,然后用那一层对应的手段:链路层用巨帧和聚合减少包数与单点,主机层用多队列和 PCIe 代际匹配把并行度和带宽铺开,网卡内部用卸载和流表把每包成本搬走,软件层在「内核调优 → AF_XDP → DPDK → RDMA」这条谱系上按延迟与生态的取舍选点。

一旦某一层的量级差了一个数量级(典型是小包 + 100G 以上),调参就到此为止,必须换架构。


延伸阅读

外部标准与文档

  • IEEE 802.3df-2024(800 Gb/s 以太网标准,2024 年 2 月批准):https://www.ieee802.org/3/df/
  • IEEE P802.3dj(200/400/800 Gb/s 与 1.6 Tb/s,预计 2026 年发布):https://www.ieee802.org/3/minutes/nov23/2311_3dj_closed_report.pdf
  • PCI-SIG PCIe 7.0 规范(2025 年 6 月发布,128 GT/s,x16 双向 512 GB/s):https://www.businesswire.com/news/home/20250611299049/en/PCI-SIG-Releases-PCIe-7.0-Specification-to-Support-the-Bandwidth-Demands-of-Artificial-Intelligence-at-128.0-GTs-Transfer-Rates
  • man ethtool(-k/-K/-c/-C/-g/-G/-l/-L/-x/-X/-N/-n/-S 的完整语义)
  • Linux 内核文档 Documentation/networking/scaling.rst、netdev-FAQ.rst
  • DPDK 程序员指南(PMD、hugepage、burst 模型)

回首页,搜索你想要的文档

在首页搜索框输入关键词,快速定位资料

文库首页搜索指引 立即搜索

相关学习资料