1
2
3
4
5
6
7
作者:李晓辉

联系方式:

1. 微信:Lxh_Chat

2. 邮箱:939958092@qq.com
  1. Linux 性能调优系列(一) 建立科学的性能分析思路
  2. Linux 性能调优系列(二) 掌握基础性能监控命令
  3. Linux 性能调优系列(三) sysstat实现性能数据持久采集
  4. Linux 性能调优系列(四) 别只会用 sysstat,PCP 才是 Linux 性能监控的进阶玩法
  5. Linux 性能调优系列(五) 给你的服务器做一次全面体检
  6. Linux 性能调优系列(六) 想调内核参数?先搞懂 /proc、sysctl 和 sysfs
  7. Linux 性能调优系列(七) 还在手动改 sysctl?TuneD 智能性能调优实战来了
  8. Linux 性能调优系列(八) 进程太能吃资源怎么办?用 ulimit 和 systemd 给它加个"上限"
  9. Linux 性能调优系列(九) cgroup v2实战:防止一个烂进程干崩整台服务器
  10. Linux 性能调优系列(十) CPU 100% 到底是谁在忙?用 perf 把"元凶"揪出来
  11. Linux 性能调优系列(十一) 程序为什么卡住了?strace 和 ltrace 带你找到答案
  12. Linux 性能调优系列(十二) 服务器又卡了?SystemTap 带你杀到内核看看为啥
  13. Linux 性能调优系列(十三) 从 SystemTap 到 eBPF,Linux 性能追踪进入新时代
  14. Linux 性能调优系列(十四) CPU 这么忙,到底是谁决定“让谁先跑”?一文搞懂 Linux 调度器
  15. Linux 性能调优系列(十五) CPU 到底该先给谁?搞懂 Linux 实时调度
  16. Linux 性能调优系列(十六) Linux CPU 隔离实战,为什么实时线程绑核后延迟还会抖?
  17. Linux 性能调优系列(十七) CPU 占用不高却跑的慢?深挖 CPU 缓存性能瓶颈
  18. Linux 性能调优系列(十八) 程序申请了1GB内存,为什么系统却说它没用?
  19. Linux 性能调优系列(十九) 服务器硬盘慢得像蜗牛?先别急着换硬盘,这四招先把瓶颈找出来
  20. Linux 性能调优系列(二十) 磁盘 I/O 调度器怎么选?一文搞懂 mq-deadline、kyber、bfq 和 none
  21. Linux 性能调优系列(二十一) 磁盘为什么慢?从文件系统到 I/O 调优,一次讲透
  22. Linux 性能调优系列(二十二) Linux 网络调优别乱改 sysctl!先找瓶颈再调 TCP 缓冲区
  23. Linux 性能调优系列(二十三) MTU 从 1500 到 9000,巨帧真的能提升性能吗?
  24. Linux 性能调优系列(二十四) 从 CPU 省电到网卡卸载,聊聊 Linux 网络调优
  25. Linux 性能调优系列(二十五) 聊聊VirtIO、TuneD、virtio-mem 调优

前面两篇,我们分别聊了 TCP 缓冲区和 BDP,以及 MTU 从 1500 提升到 9000 的巨帧优化。

但网络性能调优还有两个容易被忽略的方向:CPU 为了省电,可能增加网络处理的唤醒延迟;网卡驱动和卸载功能的配置,也可能影响吞吐量、CPU 占用和延迟抖动。

今天我们就从这两个方向入手,看看有哪些值得关注的调优手段。

一、CPU 为了省电,为什么反而可能拖慢网络?

先想象一个场景:一台服务器平时负载不高,网络请求也不多,但偶尔会出现几毫秒甚至更高的延迟尖峰。

你检查了网络带宽、TCP 缓冲区和 MTU,似乎都没什么问题。这时候,不妨把目光转向 CPU 的空闲状态管理。

什么是 CPU C-state?

CPU 不仅有运行频率的高低,还有不同的工作状态和空闲状态。

C-state 就是 CPU 的空闲状态管理机制。CPU 暂时没有任务执行时,可以进入不同深度的空闲状态,以降低功耗。

可以简单理解为:

状态大致含义
C0CPU 正在执行指令
C1浅度空闲,通常能够较快恢复执行
C3更深的空闲状态,可能关闭更多内部单元
C6 及更深状态通常具有更高的节能效果,但退出延迟也可能更大

注意,具体有哪些状态、每个状态的功耗和退出延迟,都取决于 CPU 型号及平台实现,并不是每台机器都有相同的 C-state。

CPU 进入深度空闲状态后,能够节省更多电力。但当网卡收到数据,需要 CPU 处理软中断、协议栈和应用任务时,CPU 可能需要先退出空闲状态。

这段退出延迟,在高性能网络和低延迟业务中就可能成为一个影响因素。

例如,高频交易、实时通信、低延迟 RPC 等业务,不仅关注平均延迟,还关注 P99、P99.9 这样的尾延迟指标。即使平均延迟变化不大,偶发的唤醒延迟也可能影响业务体验。

不过,不要一看到延迟尖峰就认定是 C-state 导致的。网卡中断合并、CPU 调度、软中断积压、NUMA 跨节点访问等,同样可能造成延迟抖动。

怎么检查 C-state?

先查看 CPU 空闲状态统计:

安装一下工具

1
[root@host1 ~]# dnf install kernel-tools -y
1
2
[root@host1 ~]# cpupower monitor
WARNING: at least one cpu is offline

不同 CPU 平台和内核版本显示的监控项可能不同。执行后重点观察各个空闲状态的驻留时间,而不是只盯着某一个数字。

例如,如果 CPU 大部分时间都处于较深的空闲状态,并且业务又对唤醒延迟非常敏感,就值得进一步测试限制 C-state 后的变化。

如何限制 CPU 的空闲状态?

Linux 可以通过内核参数限制某些空闲驱动允许使用的状态。

但这里要注意:intel_idle 和 acpi_idle 是不同的 CPU 空闲驱动,不能把它们的参数混为一谈。

先查看当前使用的驱动:

1
2
[root@host1 ~]# cat /sys/devices/system/cpu/cpuidle/current_driver
none

我这里的none这表示当前内核没有报告正在使用的 CPU idle 驱动。

如果输出是 intel_idle,说明当前使用 Intel 的专用空闲驱动;如果输出是 acpi_idle,则使用 ACPI 空闲驱动。

对于使用 intel_idle 的系统,可以考虑通过 intel_idle.max_cstate 限制可使用的空闲状态。例如:

1
2
[root@host1 ~]# grubby --update-kernel=ALL --args="intel_idle.max_cstate=1"

对于使用 acpi_idle 的系统,可以考虑:

1
[root@host1 ~]# grubby --update-kernel=ALL --args="processor.max_cstate=1"

这两组参数应根据实际驱动选择,不要不加判断地同时配置。

这里设置为 1,主要是限制空闲状态的深度,并不意味着 CPU 永远处于 C0,也不代表所有平台都会表现为完全相同的 C-state。

修改后需要重启,并验证内核启动参数及实际空闲状态:

1
2
3
cat /proc/cmdline
cat /sys/devices/system/cpu/cpuidle/current_driver
cpupower monitor

生产环境操作前,建议记录原始参数并准备回滚方案。

另外,限制 C-state 往往会增加功耗和发热。对于普通业务服务器,不建议为了追求更低延迟就一律禁用深度空闲状态。

使用 tuned 控制空闲状态延迟

如果希望更灵活地控制 CPU 空闲状态,可以研究 RHEL 提供的 tuned。

先查看当前配置:

1
2
[root@host1 ~]# tuned-adm active
[root@host1 ~]# tuned-adm list

创建一个独立的调优配置:

1
2
3
4
5
6
7
8
9
10
mkdir -p /etc/tuned/profiles/network-latency-custom

cat > /etc/tuned/profiles/network-latency-custom/tuned.conf <<'EOF'
[main]
summary=Customized network latency tuning
include=network-latency

[cpu]
force_latency=cstate.id:1|2
EOF

这里的 force_latency 用于通过 CPU 调优插件约束空闲状态的退出延迟。cstate.id:1 是一种按状态索引指定的方式,但索引对应的具体状态取决于平台,不能直接认定所有机器上的 1 都代表同一个 C-state。

应用之前,先确认当前 RHEL 10 的 tuned 版本支持该配置,并结合实际 CPU 状态验证:

1
2
3
[root@host1 ~]# tuned-adm profile network-latency-custom
[root@host1 ~]# tuned-adm active
[root@host1 ~]# cpupower monitor

如果配置不被支持,或者状态变化与预期不符,就不要直接将其投入生产。

需要注意,force_latency 不是简单地把 CPU 锁定在某个状态,也不是完全禁止 C-state。它的目标是约束空闲状态退出延迟,最终行为取决于平台和内核实现。

NIC 驱动参数怎么改,而且重启不丢?

CPU 状态看完了,再来看网卡。

同样一张网卡,在不同驱动参数下,可能表现出不同的中断延迟、功耗、吞吐量和 CPU 占用。

先确定网卡使用什么驱动

使用 ethtool -i:

1
2
3
4
5
6
7
8
9
10
11
12
[root@host1 ~]# ethtool -i ens160
driver: vmxnet3
version: 1.9.0.0-k-NAPI
firmware-version:
expansion-rom-version:
bus-info: 0000:03:00.0
supports-statistics: yes
supports-test: no
supports-eeprom-access: no
supports-register-dump: yes
supports-priv-flags: no

这里的 vmxnet3 是网卡驱动名称。实际生产环境也可能是 ixgbe、i40e、ice、bnxt_en 或虚拟网卡驱动 virtio_net、vmxnet3 等。

不同驱动支持的参数完全不同。

接下来可以使用 modinfo 查看模块参数:

1
modinfo -p vmxnet3

例如,某些驱动会提供中断延迟、调试或节能相关参数。但不要直接照搬其他驱动的配置:参数名称、取值范围、默认值和适用硬件都必须以当前驱动为准。

对于虚拟机,还需要注意:来宾操作系统看到的可能是虚拟网卡,物理网卡驱动参数则由宿主机或云平台管理。

如何持久化驱动模块参数?

假设当前驱动确实支持某个名为 ExampleParam 的参数,可以在 /etc/modprobe.d/ 中建立配置文件:

1
2
3
cat > /etc/modprobe.d/example-net.conf <<'EOF'
options example_driver ExampleParam=1
EOF

上面是演示语法,example_driver 和 ExampleParam 需要替换为真实驱动及其支持的参数,不能原样照抄执行。

这种方式适用于通过模块加载机制传入的参数。并不是所有网卡设置都属于模块参数,也不是所有参数都能在模块加载后修改。

修改后,应该先确认参数的生效方式。某些参数需要重新加载驱动,甚至需要重启系统。

特别提醒:不要在远程 SSH 会话中贸然执行 modprobe -r 卸载正在使用的网卡驱动。 如果 SSH 就是通过这张网卡连接的,卸载驱动可能直接切断连接;生产服务器应使用带外管理、云控制台等可靠的恢复途径。

如果确实需要重新加载驱动,必须先确认没有其他网卡或关键业务依赖该模块,并安排维护窗口。

网卡卸载功能:让硬件替 CPU 干活

最后来看一个更常见、也更容易实测的优化方向:网卡卸载。

现代网卡可以替 CPU 完成部分网络处理工作,例如 TCP 分段、校验和计算以及接收数据包合并。

这些功能可以减少 CPU 在逐包处理上花费的时间,尤其是在高吞吐量场景下。

先查看当前卸载状态

使用 ethtool -k:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
[root@host1 ~]# ethtool -k ens160
Features for ens160:
rx-checksumming: on
tx-checksumming: on
tx-checksum-ipv4: off [fixed]
tx-checksum-ip-generic: on
tx-checksum-ipv6: off [fixed]
tx-checksum-fcoe-crc: off [fixed]
tx-checksum-sctp: off [fixed]
scatter-gather: on
tx-scatter-gather: on
tx-scatter-gather-fraglist: off [fixed]
tcp-segmentation-offload: on
tx-tcp-segmentation: on
tx-tcp-ecn-segmentation: off [fixed]
tx-tcp-mangleid-segmentation: off
tx-tcp6-segmentation: on
generic-segmentation-offload: on
generic-receive-offload: on
large-receive-offload: on
rx-vlan-offload: on
tx-vlan-offload: on
ntuple-filters: off [fixed]
receive-hashing: on
highdma: on
rx-vlan-filter: on [fixed]
vlan-challenged: off [fixed]
tx-gso-robust: off [fixed]
tx-fcoe-segmentation: off [fixed]
tx-gre-segmentation: off [fixed]
tx-gre-csum-segmentation: off [fixed]
tx-ipxip4-segmentation: off [fixed]
tx-ipxip6-segmentation: off [fixed]
tx-udp_tnl-segmentation: off [fixed]
tx-udp_tnl-csum-segmentation: off [fixed]
tx-gso-partial: off [fixed]
tx-tunnel-remcsum-segmentation: off [fixed]
tx-sctp-segmentation: off [fixed]
tx-esp-segmentation: off [fixed]
tx-udp-segmentation: off [fixed]
tx-gso-list: off [fixed]
tx-nocache-copy: off
loopback: off [fixed]
rx-fcs: off [fixed]
rx-all: off [fixed]
tx-vlan-stag-hw-insert: off [fixed]
rx-vlan-stag-hw-parse: off [fixed]
rx-vlan-stag-filter: off [fixed]
l2-fwd-offload: off [fixed]
hw-tc-offload: off [fixed]
esp-hw-offload: off [fixed]
esp-tx-csum-hw-offload: off [fixed]
rx-udp_tunnel-port-offload: off [fixed]
tls-hw-tx-offload: off [fixed]
tls-hw-rx-offload: off [fixed]
rx-gro-hw: off [fixed]
tls-hw-record: off [fixed]
rx-gro-list: off
macsec-hw-offload: off [fixed]
rx-udp-gro-forwarding: off
hsr-tag-ins-offload: off [fixed]
hsr-tag-rm-offload: off [fixed]
hsr-fwd-offload: off [fixed]
hsr-dup-offload: off [fixed]
[root@host1 ~]#

重点关注这些项目:

1
2
3
4
tcp-segmentation-offload: on
generic-receive-offload: on
tx-checksumming: on
rx-checksumming: on

这里的输出仅用于说明常见项目,实际结果取决于网卡和驱动。

几个常见功能的作用如下:

功能作用
TSO发送端将较大的 TCP 数据交给网卡分段
GRO接收端合并符合条件的数据包,减少协议栈处理次数
TX checksum offload由网卡协助计算发送方向的校验和
RX checksum offload由网卡协助验证接收方向的校验和

举个例子:如果应用要发送大量 TCP 数据,启用 TSO 后,内核可以把较大的数据块交给网卡,由网卡完成后续分段。

这样,CPU 不必亲自处理每一个线上 TCP 段,有机会降低处理开销。

但要注意,TSO、GRO 等机制与 MTU 是不同维度的优化。即使 MTU 仍然是 1500,网卡也可能通过卸载功能减少 CPU 负担。

如何临时修改卸载功能?

例如,临时关闭 TSO:

1
[root@host1 ~]# ethtool -K ens160 tso off

重新开启:

1
[root@host1 ~]# ethtool -K ens160 tso on

查看当前状态:

1
2
3
4
5
6
[root@host1 ~]# ethtool -k ens160 | grep -E 'tcp-segmentation-offload|generic-receive-offload|tx-checksumming|rx-checksumming'
rx-checksumming: on
tx-checksumming: on
tcp-segmentation-offload: off
generic-receive-offload: on

这些设置通常用于临时测试或故障排查。

例如,如果怀疑某个驱动的卸载功能存在兼容性问题,可以在可控环境中关闭对应功能,比较修改前后的吞吐量、CPU 使用率和错误计数。

但不要为了“优化”就把所有卸载功能全部关闭。很多情况下,这样反而会增加 CPU 开销、降低吞吐量。

如何让卸载配置持久生效?

通过 ethtool -K 修改的功能状态通常不会自动成为永久配置。要持久化,可以使用 NetworkManager 支持的连接配置属性。

先查找网卡对应的连接配置名称:

1
2
3
4
[root@host1 ~]# nmcli connection show
NAME UUID TYPE DEVICE
ens160 08864eb2-7927-3a89-b954-4dbabc5db128 ethernet ens160
lo 483ae556-41bc-42a8-baa0-df0031b3ab88 loopback lo

假设连接配置名称确实是 ens160,并且当前 NetworkManager 版本支持 TSO 对应的 ethtool 属性,可以尝试:

1
[root@host1 ~]# nmcli connection modify ens160 ethtool.feature-tso on

然后重新激活连接:

1
[root@host1 ~]# nmcli connection down ens160 && nmcli connection up ens160

最后验证:

1
2
[root@host1 ~]# ethtool -k ens160 | grep tcp-segmentation-offload
tcp-segmentation-offload: on

这里有个细节:连接配置名称不一定等于网卡设备名,而且 NetworkManager 并非在所有版本中都支持相同的 ethtool.feature-* 属性。

如果命令提示属性不支持,应先检查本机版本的 nmcli 帮助和 NetworkManager 文档,不要直接认定这是 RHEL 10 所有环境通用的语法。

此外,持久化配置最终能否生效,还取决于驱动是否支持该功能,以及 NetworkManager 是否成功应用了连接配置。

怎么判断调优真的有效?

这篇文章涉及的三个方向,最终都应该通过测试验证,而不是仅仅看配置是否修改成功。

建议每次只调整一个变量,并在相同的负载、网卡速率和测试时长下进行对比。

可以重点观察:

  • 吞吐量: 使用 iperf3 对比网络传输能力。
  • CPU 使用率: 观察完成相同传输任务所需的 CPU 资源。
  • 延迟分布: 关注平均延迟以及 P95、P99 等尾延迟。
  • 中断与软中断: 结合 /proc/interrupts、/proc/softirqs 观察变化。
  • 功耗与温度: 特别是限制 C-state 后,要确认额外的能耗是否值得。

例如:

1
2
3
4
5
6
7
8
# 查看网卡中断分布
cat /proc/interrupts

# 查看网络软中断统计
cat /proc/softirqs

# 查看网卡统计信息
ip -s link show ens160

如果要验证 CPU 空闲状态对延迟的影响,可以在业务负载尽量一致的情况下,比较调优前后的延迟分布,而不是只执行一次 ping 就下结论。

对于 NIC 卸载功能,也应优先在测试环境中验证,确认没有吞吐量下降、丢包或兼容性问题后,再考虑推广到生产环境。

总结

这篇我们主要关注三个方向:

  1. CPU C-state: 深度空闲状态可能增加唤醒延迟,但是否影响业务,需要结合 CPU 平台和延迟测试判断。
  2. NIC 驱动参数: 使用 ethtool -i 和 modinfo -p 了解驱动及其参数,再根据驱动支持情况选择持久化方式。
  3. 网卡卸载功能: 使用 ethtool -k 查看状态、ethtool -K 进行临时测试,确认 NetworkManager 支持后再配置持久化。

网络调优不是把所有参数都调到最大,也不是把所有省电功能都关掉。

先找到瓶颈,再一次调整一个参数,最后用吞吐量、CPU 占用和尾延迟验证效果。 这才是生产环境中更可靠的调优方法。