如何在香港服务器的 Linux 系统中通过 ethtool 与 RPS/XPS 调优,提升万兆网络吞吐?

凌晨 2 点,我站在香港葵涌机房第 7 列机架前,手里拿着一根 3 米 DAC 线,另一只手在 KVM 上敲着命令。白天用户抱怨跨境同步卡顿、夜间全量备份打满 CPU 却跑不到 7 Gbps。我盯着 top 里那条咬死在 softirq 上的核,心里明白:该动刀到网卡队列、中断亲和和 RPS/XPS 了。
一、现场环境与目标
目标:把同机房万兆链路(10GbE)在通用内核与标准驱动前提下,从“多流 6~7 Gbps、不稳定”调到“稳定 9.4~9.8 Gbps”,同时降低软中断抖动与丢包。
硬件与软件清单(实测环境)
| 项 | 规格/版本 |
|---|---|
| 机型 | Dell R740xd(双路) |
| CPU | Intel Xeon Gold 6230R ×2(每颗 26C/52T,基频 2.1GHz) |
| 内存 | 256 GB(2× NUMA 节点) |
| 网卡 | Intel X520-DA2(82599ES,双口,驱动 ixgbe) |
| 交换机 | Arista 7050 系列(对端万兆,LACP 关闭,单口直连) |
| 介质 | DAC 3 m(直连铜缆) |
| MTU | 9000(端到端已确认) |
| OS | CentOS 7.9(3.10.0-1160 系列内核) |
| 工具 | ethtool、irqbalance、tuned、iperf3、numactl、perf |
说明:如果你用的是 Intel X710/XL710(驱动 i40e)或 Mellanox ConnectX-4 Lx,思路一致,命令与参数名略有差异,文中会点到。
二、基线测试(调优前)
确认网卡与队列
ethtool -i eth0
ethtool -l eth0 # 支持的队列数(channels)
ethtool -g eth0 # 当前 RX/TX ring 大小
ethtool -k eth0 # offload 开关(TSO/GSO/GRO/LRO 等)
cat /sys/class/net/eth0/device/numa_node
性能与瓶颈观测
iperf3 -c <对端IP> -P 8 -t 60 -J
sar -n DEV 1 5
cat /proc/interrupts | egrep 'ixgbe|eth0'
top -H -p $(pidof ksoftirqd/0) # 观察软中断核
ethtool -S eth0 # 驱动统计
基线结果(1500 MTU、默认配置,跨机柜同交换机)
| 指标 | 单流 | 4 流 | 8 流 |
|---|---|---|---|
| 吞吐(Gbps) | 3.4 | 6.1 | 7.2 |
| 发送端 CPU(softirq) | 1 核 90%+ | 2 核 160% | 2~3 核 200%+ |
rx_no_buffer / missed |
偶发 | 升高 | 明显 |
| 中断分布 | 集中在 CPU0/CPU1 | 轻微扩散 | 仍集中 |
| 时延抖动(p99, μs) | 210 | 245 | 270 |
典型症状:单队列/少队列、中断亲和不合理、软中断挤在少数核、RX ring 偏小、RSS/XPS 未充分利用。
三、调优思路:四条主线
队列与 ring 容量:让网卡拥有足够的 RX/TX ring 和 多队列(RSS) 去承压,避免 rx_no_buffer。
中断与软中断分布:合理的 IRQ 亲和 + RPS 把 RX 处理从少数核解放到目标 NUMA 上的多核。
发包亲和:使用 XPS(Transmit Packet Steering)让 TX 选择合适队列,降低锁竞争。
合并与 offload 策略:GRO/TSO/GSO 与 中断合并(coalescing) 找到“吞吐/CPU/时延”的平衡点。
额外:跨机房/跨境链路请校准 TCP 窗口/拥塞控制,见文末“加餐”。
四、实操步骤(逐项落地)
注意:远程操作风险——调 -L、-G、-K、-C 可能短瞬断流。务必在本地串口 / OOB 或与网络同事配合窗口内进行。
1)开启更多队列与更深的 ring
# 查看最大支持队列
ethtool -l eth0
# 例如 ixgbe 报告:Pre-set max: combined 16
# 设置为 16(或根据 CPU/流量定为 8/12/16)
ethtool -L eth0 combined 16
# 提升 ring 深度(以 4096 为例;按驱动支持为准)
ethtool -G eth0 rx 4096 tx 4096
解释:更多队列 + 更深 ring 能显著降低 RX 丢包(rx_no_buffer_count),解放高 PPS 时的抖动。
2)合理的 flow hash(硬件 RSS)
# 查看/设置 flow hash 使 TCP4/TCP6 按 五元组(IP+端口)分散
ethtool -n eth0 rx-flow-hash tcp4
ethtool -N eth0 rx-flow-hash tcp4 sdfn
ethtool -N eth0 rx-flow-hash tcp6 sdfn
sdfn 表示用源/目的 IP + L4 端口做 hash;某些驱动参数略不同(i40e 支持更细粒度的 hash 组合)。
3)中断合并(Coalescing)
# 初始建议:RX 8us / TX 16us;视时延敏感度与 CPU 负载微调
ethtool -C eth0 rx-usecs 8 rx-frames 0 tx-usecs 16 tx-frames 0
取舍:更大的 rx-usecs 降低中断频率、减 CPU,但增 p99 时延。万兆吞吐优先时,8~12us 往往是好起点。
4)Offload 策略(GRO/TSO/GSO 等)
# 通用主机(非路由/虚机网关)建议
ethtool -K eth0 gro on gso on tso on lro off rxvlan on txvlan on
# 若做三层转发/隧道/Overlay,务必保持 LRO=off,避免重组传给协议栈的问题
5)RPS:把 RX 软中断扇出到更多 CPU
# 增大系统级 flow entries
sysctl -w net.core.rps_sock_flow_entries=32768
echo "net.core.rps_sock_flow_entries = 32768" > /etc/sysctl.d/99-rps.conf
# 为每个 RX 队列配置 rps_flow_cnt
for q in /sys/class/net/eth0/queues/rx-*; do echo 4096 > $q/rps_flow_cnt; done
为 rps_cpus 选择合适的 CPU 集(优先使用 网卡所在 NUMA 的若干物理核,避免跨 NUMA 抖动):
# 找到 NIC 的 NUMA
cat /sys/class/net/eth0/device/numa_node # 假设为 0
numactl -H # 看 node0 拥有哪些 CPU
# 示例:为 rx-0..rx-15 选择 node0 上的 16 个物理核(跳过超线程)
mask_list=(0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30) # 示例 CPU 号
i=0
for q in /sys/class/net/eth0/queues/rx-*; do
cpu=${mask_list[$i]}; ((i++))
# 计算 bitmask(1<<cpu)
mask=$(printf "%x\n" $((1<<cpu)))
echo $mask > $q/rps_cpus
done
如果 CPU 号很大(>63),需要写 位图字符串,多核组合请按位或合并生成掩码。线上我通常写个小脚本从 CPU 列表自动生成掩码,避免出错。
6)XPS:让发包亲和合适的队列
# 与上面 RX 的核相呼应,TX 也固化到同一批核
i=0
for q in /sys/class/net/eth0/queues/tx-*; do
cpu=${mask_list[$i]}; ((i++))
mask=$(printf "%x\n" $((1<<cpu)))
echo $mask > $q/xps_cpus
done
收益:减少 TX 锁争用,提高单机并发发送时的稳定性与可预期性。
7)IRQ 亲和:别把中断都打在 CPU0
# 看真正的中断号(ixgbe 通常每队列一条)
egrep 'ixgbe|eth0' /proc/interrupts
# 关闭 irqbalance(或配置它别乱迁移我们的网卡 IRQ)
systemctl stop irqbalance
systemctl disable irqbalance
# 或者保留 irqbalance,但把对应 CPU 屏蔽出它的管辖范围(按需)
# 逐个 IRQ 绑定
# 假设中断号列表在 irqs 数组里
idx=0
for irq in ${irqs[@]}; do
cpu=${mask_list[$idx]}; ((idx++))
mask=$(printf "%x\n" $((1<<cpu)))
echo $mask > /proc/irq/$irq/smp_affinity
done
i40e(X710/XL710)会有 “combined” 中断;原则同上。Mellanox 的 mlx5 驱动也支持 per-queue IRQ 绑定。
8)NUMA 亲和:进程靠近网卡
服务进程:用 numactl -N <node> -m <node> 固定在 NIC 的 NUMA 节点。
压测:iperf3 支持 -A 绑核,用 node0 的那批核压测,更贴近真实。
9)MTU 一致与 FEC/链路层
端到端 统一 MTU=9000(主机、交换机、对端一致)。否则会被中间节点碎片/丢弃,表现为吞吐打折与重传。
DAC/光模块建议对端确认 FEC/自协商 是否兼容,避免偶发 crc/symbol 错误。
五、持久化配置(CentOS 7 实操)
1)/etc/sysconfig/network-scripts/ifcfg-eth0
DEVICE=eth0
ONBOOT=yes
MTU=9000
# ethtool 一次性设置(注意某些旧版 ifup 仅识别单行)
ETHTOOL_OPTS="-G rx 4096 tx 4096"
某些发行版对多条 ETHTOOL_OPTS 支持不好。稳定做法:系统化到 systemd 脚本。
2)/etc/sysctl.d/99-net-tuning.conf
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.core.netdev_max_backlog = 250000
net.core.rps_sock_flow_entries = 32768
net.ipv4.tcp_rmem = 4096 87380 67108864
net.ipv4.tcp_wmem = 4096 65536 67108864
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_sack = 1
跨境/高 RTT 场景上限适当再放大(见“加餐”)。
3)systemd 单元:/etc/systemd/system/netqueue-tune.service
[Unit]
Description=NetQueue/RPS/XPS/Coalesce Tuning for eth0
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/netqueue-tune.sh
[Install]
WantedBy=multi-user.target
脚本 /usr/local/sbin/netqueue-tune.sh(关键命令集中到一处,避免 ifup 兼容问题):
#!/usr/bin/env bash
set -euo pipefail
DEV=eth0
# channels & ring
ethtool -L $DEV combined 16 || true
ethtool -G $DEV rx 4096 tx 4096 || true
# coalescing
ethtool -C $DEV rx-usecs 8 tx-usecs 16 || true
# offloads
ethtool -K $DEV gro on gso on tso on lro off rxvlan on txvlan on || true
# RSS hash
ethtool -N $DEV rx-flow-hash tcp4 sdfn || true
ethtool -N $DEV rx-flow-hash tcp6 sdfn || true
# RPS 基本
for q in /sys/class/net/$DEV/queues/rx-*; do echo 4096 > $q/rps_flow_cnt; done
# 按 NUMA 选择 CPU(示例:node0 的 16 个物理核 ID)
cpus=(0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30)
for i in "${!cpus[@]}"; do
cpu=${cpus[$i]}
mask=$(printf "%x" $((1<<cpu)))
echo $mask > /sys/class/net/$DEV/queues/rx-$i/rps_cpus
echo $mask > /sys/class/net/$DEV/queues/tx-$i/xps_cpus
done
# IRQ 亲和(可选:如果保留 irqbalance 则无需固化)
# irqs=($(egrep -h "$DEV|ixgbe|i40e|mlx5" /proc/interrupts | awk -F: '{print $1}'))
# for i in "${!irqs[@]}"; do
# irq=${irqs[$i]}; cpu=${cpus[$i]}
# mask=$(printf "%x" $((1<<cpu)))
# echo $mask > /proc/irq/$irq/smp_affinity
# done
启用:
chmod +x /usr/local/sbin/netqueue-tune.sh
systemctl daemon-reload
systemctl enable --now netqueue-tune.service
六、复测与对比(调优后)
统一条件:同机柜、MTU 9000、iperf3 3.10、-P 并发、绑定 node0 核。
| 指标 | 单流 | 4 流 | 8 流 |
|---|---|---|---|
| 吞吐(Gbps) | 7.8 | 9.4 | 9.7 |
| 发送端 CPU(softirq) | 5~8 个核均衡 | 8~12 个核均衡 | 12~16 个核均衡 |
rx_no_buffer/missed |
近零 | 近零 | 近零 |
| 时延抖动(p99, μs) | 170 | 185 | 190 |
| 驱动统计异常 | 无 | 无 | 无 |
观察点:
- cat /proc/interrupts:与网卡相关的中断在 node0 多个核平均分布。
- ethtool -S eth0:rx_no_buffer 不再持续增长,crc/dropped 正常。
- perf top:napi_gro_receive、ixgbe_clean_rx_irq 占比下降,总软中断更均衡。
七、常见坑 & 现场处理笔记
irqbalance 抢回中断
症状:重启后中断再次集中;
处理:要么禁用 irqbalance,要么配置它的 banned cpus,避免迁走我们指定的亲和核。
i40e 上的队列/中断命名不同
X710/XL710 用 i40e,某些版本将队列标为 TxRx-0 等;命令一致,路径名略不同,照样能绑定。
ring 设置失败
部分固件/驱动限制 -G 上限,或需 down 接口后设置。窗口内操作、确认维护时段。
虚拟化/Overlay
LRO 在虚机网关/路由场景要 off;否则可能出现吞吐怪异或协议栈异常。
超线程混绑
把 rx 和 tx 绑定到同一物理核的两个超线程会有竞争;建议优先物理核。
NUMA 跨节点
NIC 在 node0,而进程主要跑 node1,跨节点内存访问增大抖动;numactl 固定进程亲和。
FEC/光模块兼容
更换模块或线缆后出现误码,吞吐忽上忽下:对端开关上确认 FEC、自协商、波长/厂商兼容。
ELRepo 新内核 vs 老内核
CentOS 7 的 3.10 对 BBR/某些驱动特性有限制;如果你的变更窗口允许,上 ELRepo kernel-ml 5.x + 新版驱动,收益更稳。
八、加餐:跨境大 RTT 时的 TCP 窗口/拥塞控制
香港 ↔ 内地/海外 RTT 常在 5~40 ms(跨境/跨洋更高)。带宽时延积(BDP) 决定了单流上限。
10 Gbps × 10 ms RTT ≈ 12.5 MB 窗口;若默认 wmem/rmem 偏小,单流上不去是常态。
建议(CentOS 7 通用内核):
net.core.rmem_max = 268435456
net.core.wmem_max = 268435456
net.ipv4.tcp_rmem = 4096 87380 134217728
net.ipv4.tcp_wmem = 4096 65536 134217728
net.ipv4.tcp_window_scaling = 1
如果能上 4.9+ 内核:net.ipv4.tcp_congestion_control = bbr 往往能改善长肥管道的恢复与稳态;CentOS 7 原生 3.10 没有 BBR。
九、通用检查清单(可直接照抄执行)
- ethtool -L 开成 8/12/16 队列(与 CPU/业务匹配)。
- ethtool -G 把 RX/TX ring 提升(例如 4096/4096)。
- ethtool -N 设定 tcp4/tcp6 sdfn 流 hash。
- ethtool -C 设定 rx-usecs 8 / tx-usecs 16 起步。
- ethtool -K:gro/gso/tso on,lro off。
- rps_sock_flow_entries=32768、每队列 rps_flow_cnt=4096。
- 为每个 RX/TX 队列写入合适的 rps_cpus/xps_cpus(同 NUMA)。
- 绑定 IRQ 至对应物理核;必要时管住 irqbalance。
- 业务进程 numactl 亲和到网卡节点。
- MTU 一致、FEC/模块兼容确认。
- iperf3 -P 4/8 与业务实测复核,观察 ethtool -S、/proc/interrupts、perf。
十、结尾:从夜里到天亮
清晨 6 点,我把最后一个脚本提交到 Ansible,确认所有边缘节点都吃到了同一套 systemd 调优。Grafana 的曲线从锯齿变成了平滑的“水波线”,8 并发稳定在 9.6~9.8 Gbps,rx_no_buffer 归零。机房外的天已经泛白,我给对端同事发了条消息:“备份窗口可以缩了 30 分钟。”
说到底,这活儿不神秘:把队列开足、让中断与软中断各归其位、让数据和 CPU 靠近。ethtool、RPS、XPS 就像三枚小扳手,拧紧了,万兆就顺了。下次你在香港的机房里遇到类似的“卡脖子”,不妨按上面的清单走一遍。大概率,你也会在天亮前看到那条漂亮的 9.x Gbps 曲线。
附:Mellanox/mlx5 与 Intel i40e 小差异提示
- Mellanox(mlx5)
- 队列/中断命名不同,但 ethtool -L/-G/-C/-K 同理;
- ethtool -S 里有丰富的 per-queue 统计,定位丢包更直观。
- Intel i40e(X710/XL710)
- ethtool -l 的 combined 数量更高(常见 64+);
- 某些固件版本对 coalescing 参数更敏感,先用默认模板(如 rx-usecs 12)再细调;
- 队列目录名可能是 tx-0/rx-0 或 TxRx-0,按实际路径写入 rps_cpus/xps_cpus