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 调优

前面我们聊了不少 Linux 性能调优的方法。这次换个场景:同样的 CPU、同样的内存,为什么虚拟机跑起来,还是感觉没有裸机快?

比如,你在物理机上跑一个 I/O 密集型应用,磁盘读写挺快。把应用搬进虚拟机后,业务代码没变,磁盘也没换,性能却可能下降了。

这时候,先别急着给虚拟机加 CPU、加内存。

问题可能出在虚拟设备上,也可能出在客户机的内核配置、磁盘 I/O 路径,甚至是宿主机本身。

今天就以 RHEL 10 为例,沿着一条比较实用的调优路线走一遍:先检查虚拟设备,再调整 TuneD 和磁盘调度器,最后看看如何使用 virtio-mem 动态调整虚拟机内存。

需要说明的是,本文主要讨论 KVM/libvirt 环境。不同的存储后端、宿主机配置和业务负载,调优结果可能不一样。咱们不追求把参数改得越多越好,而是尽量找到真正影响性能的地方。

虚拟机为什么可能比裸机慢?

先别急着改参数,咱们先看看虚拟机运行时,到底多做了哪些工作。

flowchart LR
    A["应用程序"] --> B["RHEL 客户机内核"]
    B --> C["VirtIO 虚拟设备"]
    C --> D["KVM / QEMU"]
    D --> E["宿主机内核"]
    E --> F["物理 CPU、内存、磁盘、网卡"]

    style A fill:#e8f4ff,stroke:#4385be
    style C fill:#fff1d6,stroke:#d99b2b
    style D fill:#ffe4e4,stroke:#cf6666
    style F fill:#e2f5e8,stroke:#4a9b65

应用程序看到的是一套虚拟硬件,但真正提供计算、内存和 I/O 能力的,仍然是宿主机背后的物理资源。

不过,虚拟化并不是把每条指令都重新模拟一遍。现代 CPU 提供了硬件虚拟化支持,很多客户机指令可以直接在物理 CPU 上执行。

真正值得关注的,是那些需要额外处理的路径。

CPU:VM exit 不是每条指令都要经历的

VM exit,也就是虚拟机退出,是虚拟化性能分析中经常出现的一个概念。

例如,客户机执行某些需要由 KVM 处理的操作,或者触发了需要虚拟机监控器介入的事件,就可能发生 VM exit。

大致过程如下:

  1. 客户机正在执行代码。
  2. 某个操作触发虚拟机退出。
  3. CPU 转到宿主机侧的虚拟化处理路径。
  4. KVM 等组件完成相应处理,再恢复客户机执行。

如果某类操作频繁触发 VM exit,就可能产生额外开销。

但这里有个容易误解的地方:不是所有特权指令都会触发 VM exit,更不是虚拟机执行的每条指令都要经过宿主机。

所以,看到虚拟机 CPU 使用率很高时,不要马上认定是 VM exit 导致的。还要结合宿主机 CPU 使用率、vCPU 调度等待时间以及具体负载进行分析。

内存:多了一层地址转换

裸机环境下,CPU 访问内存时,需要通过页表将虚拟地址转换为物理地址。

到了虚拟机环境,情况就多了一层。

flowchart TD
    A["客户机虚拟地址 GVA"] --> B["客户机物理地址 GPA"]
    B --> C["宿主机物理地址 HPA"]

    A -.- D["客户机页表"]
    B -.- E["EPT / NPT 等硬件辅助地址转换"]

    style A fill:#e8f4ff,stroke:#4385be
    style B fill:#fff1d6,stroke:#d99b2b
    style C fill:#e2f5e8,stroke:#4a9b65

这三个概念分别是:

  • GVA:Guest Virtual Address,客户机虚拟地址。
  • GPA:Guest Physical Address,客户机物理地址。
  • HPA:Host Physical Address,宿主机物理地址。

客户机内核负责维护自己的页表,CPU 则通过 Intel EPT 或 AMD NPT 等硬件辅助机制完成第二阶段地址转换。

这意味着地址转换可能涉及更多层级的页表遍历。不过,CPU 的 TLB 可以缓存地址转换结果,并不是每次内存访问都要完整遍历所有页表。

因此,虚拟机内存性能不能只看内存容量,还需要关注 TLB 未命中、NUMA 远程访问、宿主机内存压力等因素。

存储:磁盘读写可能经过更多层

虚拟机访问磁盘时,数据通常需要经过客户机文件系统、虚拟块设备驱动、虚拟化后端以及宿主机块层,最后才到达实际存储设备。

如果使用文件形式的虚拟磁盘,还可能经过宿主机文件系统。

flowchart LR
    A["应用程序"] --> B["客户机文件系统"]
    B --> C["VirtIO 块设备"]
    C --> D["QEMU / 存储后端"]
    D --> E["宿主机块层"]
    E --> F["物理存储设备"]

    style C fill:#fff1d6,stroke:#d99b2b
    style D fill:#ffe4e4,stroke:#cf6666
    style F fill:#e2f5e8,stroke:#4a9b65

这条链路涉及多个组件,任何一层出现瓶颈,都可能影响最终的 I/O 性能。

另外,客户机和宿主机都可能缓存文件数据,但不能简单认为所有场景都会产生两份完全相同的数据。是否存在缓存浪费,还得结合虚拟磁盘格式、缓存模式和实际工作负载判断。

网络:虚拟交换也会消耗 CPU

普通虚拟网卡的数据包,可能经过客户机网络协议栈、VirtIO 网卡、宿主机虚拟交换机,再到物理网卡。

网络吞吐量上升后,软中断、数据包处理和虚拟交换等工作,都可能消耗宿主机 CPU。

不过,启用 vhost、硬件卸载、多队列或 SR-IOV 等功能后,实际数据路径可能发生明显变化。

所以,虚拟机性能下降,不一定是客户机内部的问题。如果宿主机本身已经成为瓶颈,只在 RHEL 里调整几个内核参数,效果可能非常有限。

虚拟设备怎么选?优先检查 VirtIO

假设你新建了一台 RHEL 虚拟机,发现网卡使用的是 e1000 模拟设备,磁盘使用传统模拟控制器。

这时候就值得检查一下:有没有必要改成 VirtIO?

常见的虚拟设备可以分为三类。

类型工作方式特点常见用途
模拟设备模拟传统硬件接口兼容性较好,但可能产生较多模拟开销旧系统兼容、特殊需求
VirtIO 半虚拟化设备客户机通过专用驱动与虚拟化后端协作通常具有较好的性能与兼容性平衡大多数常规虚拟机
PCI 设备直通将物理设备或其虚拟功能分配给客户机可以减少部分虚拟化数据路径开销,但管理更复杂高性能网络、GPU 等场景

对于大多数常规业务,优先考虑 VirtIO,只有在明确的性能需求下,才进一步评估设备直通。

在 libvirt XML 里怎么看?

先在 KVM 宿主机上查看虚拟机配置:

1
virsh dumpxml vm0

网卡配置可能类似这样:

1
2
3
4
<interface type='network'>
<source network='default'/>
<model type='virtio'/>
</interface>

其中,model type='virtio' 表示使用 VirtIO 网卡模型。

如果使用模拟网卡,可能看到:

1
<model type='e1000e'/>

e1000e 是一种具体的模拟网卡型号。实际支持哪些型号,还取决于虚拟机机器类型和 QEMU 配置。

磁盘设备也类似:

1
2
3
4
5
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2'/>
<source file='/var/lib/libvirt/images/vm0.qcow2'/>
<target dev='vda' bus='virtio'/>
</disk>

这里的 bus='virtio' 表示使用 VirtIO 磁盘接口。

如果使用 VirtIO SCSI 控制器,设备配置会有所不同,不能只看磁盘名称就判断完整的数据路径。

设备改成 VirtIO 就一定能提速吗?

也不一定。

VirtIO 只是让客户机和虚拟化后端使用更适合虚拟化的设备接口,并不能解决所有性能问题。

比如:

  • 虚拟磁盘所在的物理存储已经达到性能上限。
  • 宿主机 CPU 过载,vCPU 长时间等待调度。
  • 客户机内存不足,频繁发生换页。
  • 网络瓶颈实际出现在宿主机的虚拟交换或物理网卡上。

这些问题都不是单纯更换 VirtIO 就能解决的。

如果准备将现有虚拟机的磁盘或网卡从模拟设备切换为 VirtIO,还要先确认客户机操作系统具备相应驱动。对于启动磁盘,驱动缺失甚至可能导致系统无法正常启动。

新建虚拟机时优先选择合适的 VirtIO 设备,通常比事后修改启动设备更省事。

物理设备直通是不是更快?

PCI 设备直通常借助 IOMMU 等硬件机制,将物理设备或其虚拟功能分配给客户机使用。

对于某些高吞吐、低延迟场景,直通确实可以减少部分软件处理开销。

但直通也有代价:

  • 设备分配、IOMMU 分组和驱动绑定需要额外配置。
  • 设备通常不能同时被宿主机和多个客户机随意共享。
  • 迁移、快照和高可用方案可能受到限制。
  • 直通不会自动消除客户机协议栈和业务程序自身的开销。

所以,别一看到虚拟化开销就马上考虑直通。先把 VirtIO 用好,再根据测试结果决定是否需要进一步优化。

TuneD:先给 RHEL 用上合适的配置文件

设备检查完,接下来看看客户机自己的配置。

RHEL 提供了 TuneD,用来按照不同的系统用途应用一组调优设置。相比手工修改一堆内核参数,先选一个合适的配置文件,再针对业务做小范围调整,通常更容易维护。

对于 RHEL 10 虚拟机,可以先从 virtual-guest 配置文件开始。

它基于 throughput-performance,并针对虚拟机环境调整了虚拟内存相关设置。

注意,宿主机和客户机的 TuneD 配置并不是一回事。RHEL 10 宿主机可以评估 virtual-host,而运行在虚拟机中的 RHEL 通常优先考虑 virtual-guest。

查看当前使用的配置文件

先执行:

1
2
tuned-adm active
tuned-adm list

如果当前没有使用 virtual-guest,并且确认该配置适合当前业务,可以切换:

1
tuned-adm profile virtual-guest

再检查一下:

1
2
tuned-adm active
tuned-adm verify

其中,tuned-adm active 用于查看当前配置文件,tuned-adm verify 用于检查当前系统设置与配置文件预期是否一致。

如果想知道它具体调整了哪些参数,可以查看当前系统安装的配置:

1
cat /usr/lib/tuned/profiles/virtual-guest/tuned.conf

这里有个细节:TuneD 配置文件会随着软件包版本变化而变化,不要只根据网上的一份示例,就认定自己的系统一定使用相同参数。

swappiness 为什么值得关注?

查看当前值:

1
sysctl vm.swappiness

例如:

1
vm.swappiness = 30

vm.swappiness 控制 Linux 内核在内存回收时使用交换空间的倾向。

当客户机内存压力增大时,内核可能将部分匿名内存换出到交换空间,以便回收内存供其他用途使用。

如果虚拟机的交换空间位于性能较差的存储上,频繁换页就可能带来明显延迟。

而且,如果宿主机自身也存在内存压力,虚拟机进程占用的内存还可能受到宿主机回收机制的影响。

不过,降低 swappiness 不等于彻底解决内存压力。

如果把这个值压得过低,内核可能更倾向于回收文件缓存;对于某些工作负载,性能反而可能下降。

因此,可以先使用 virtual-guest 提供的默认策略,再结合内存使用情况和业务表现决定是否需要进一步调整。

dirty_bytes 是不是越大越好?

你可能还会在 TuneD 配置中看到与脏页写回有关的设置。

所谓脏页,就是已经被修改、但尚未写回存储设备的缓存页。

如果应用持续产生脏页,内核需要在合适的时机将数据写回磁盘。如果写回速度跟不上应用写入速度,脏页就可能积累,最终影响业务延迟。

这里需要区分两件事:

  • vm.dirty_bytes 和 vm.dirty_ratio 是 Linux 内核的脏页控制参数。
  • TuneD 配置文件中的 dirty_bytes 则需要按照当前 TuneD 插件的配置规则解释。

不要把 TuneD 中的百分比写法直接当成普通 sysctl 参数,更不要未经确认就将 dirty_bytes = 30% 改成 vm.dirty_ratio = 30。

对于 RHEL 10,建议先检查当前安装的 virtual-guest 配置文件,以及实际生效的内核参数。如果没有明确的写回瓶颈,不必为了追求性能而手工修改脏页阈值。

先使用系统提供的配置文件,再根据实际数据决定要不要改参数,这样更稳妥。

磁盘调度器:none 就是最优解吗?

上一篇我们聊了 Linux I/O 调度器,知道了 none、mq-deadline、bfq 和 kyber 等调度器各自的特点。

到了虚拟机里,问题又来了:客户机有一层 I/O 调度,宿主机可能还有一层,到底该怎么选?

我的建议是:先别急着改,先看当前用的是什么,再做对比测试。

查看当前调度器

先确认设备名称:

1
lsblk

VirtIO 块设备常见的名称是 /dev/vda,VirtIO SCSI 设备则可能显示为 /dev/sda。

假设当前磁盘是 vda,执行:

1
cat /sys/block/vda/queue/scheduler

如果输出如下:

1
[none] mq-deadline

方括号里的 none 就是当前选中的调度器。

为什么虚拟机值得测试 none?

对于许多现代虚拟化存储场景,none 是值得测试的选择。

它不会在客户机块层使用传统的 I/O 请求排序策略,但这不代表整个存储路径完全没有调度。宿主机块层、存储阵列以及其他后端组件,仍然可能进行请求处理和调度。

因此,客户机使用 none 并不意味着宿主机也应该使用 none。

如果要临时测试,可以执行:

1
echo none > /sys/block/vda/queue/scheduler

这会立即影响当前设备的调度行为,但通常不会自动持久化。

测试时可以比较:

  • 顺序读写吞吐量。
  • 随机读写 IOPS。
  • 平均 I/O 延迟。
  • 高分位延迟,例如 p95、p99。
  • 业务负载下的 CPU 使用率。

如果结果证明 none 更适合当前业务,再考虑通过适合本机 RHEL 和 TuneD 版本的方式持久化。

另外,切换前要确认设备支持目标调度器。不同内核、虚拟设备和块层配置,支持的选项可能不一样。

这一节的重点不是强行把调度器改成 none,而是先检查、再测试、最后持久化。

动态内存:用 virtio-mem 给虚拟机伸缩内存

前面解决的是虚拟机跑得快不快的问题。接下来,我们再解决另一个实际问题:虚拟机分配的内存,能不能随着业务需求动态调整?

假设宿主机上运行着多台虚拟机。

白天业务高峰时,虚拟机 A 需要更多内存;到了晚上,A 的业务量下降,但虚拟机 B 正好需要更多资源。

如果只能给虚拟机固定分配内存,就可能出现一边不够用、另一边闲置的情况。

这时,可以考虑 virtio-mem。

先强调一点:virtio-mem 的主要价值是动态调整内存容量、提高宿主机资源利用率,而不是直接提升虚拟机的运行速度。

内存气球和 virtio-mem 有什么区别?

传统的内存气球(ballooning)通过客户机驱动与宿主机协作,让客户机释放部分内存,供宿主机重新分配。

virtio-mem 则提供了另一种方式:它是一种半虚拟化内存设备,支持按指定粒度动态增加或减少虚拟机可使用的内存。

两种机制的工作方式不同,不能简单地认为气球机制一定造成严重碎片,而 virtio-mem 就完全没有碎片。

实际能否顺利缩容,还取决于客户机内核、内存区域分配、业务使用情况以及宿主机配置。

此外,RHEL 10 的 virtio-mem 有明确的兼容性限制,不能与内存气球膨胀、收缩机制随意混用。部署前应确认相关宿主机、客户机和 libvirt 版本都支持该功能。

先理清虚拟机的内存配置

下面假设:

  • 虚拟机名称:vm0。
  • 初始内存:2 GiB。
  • virtio-mem 设备最大容量:2 GiB。
  • 虚拟机总内存容量上限:4 GiB。

先查看当前配置:

1
virsh dumpxml vm0

对于使用 virtio-mem 的虚拟机,需要区分以下字段:

XML 字段含义
memory虚拟机初始内存配置
currentMemory当前配置的内存容量,运行时还需要结合实际状态判断
maxMemory虚拟机可使用的最大内存容量,包含初始内存和热插入内存

在本例中,初始内存为 2 GiB,最大内存为 4 GiB,可以将配置设计为类似下面的形式:

1
2
3
<memory unit='GiB'>2</memory>
<currentMemory unit='GiB'>2</currentMemory>
<maxMemory unit='GiB'>4</maxMemory>

这只是展示内存容量关系的示意片段,不是可以直接替换现有虚拟机配置的完整 XML。

实际配置时,应结合现有虚拟机的机器类型、NUMA 布局、内存插槽及 libvirt 版本检查,避免覆盖已有配置。

定义 virtio-mem 设备

接下来创建一个设备 XML 文件:

1
vim virtio-mem.xml

设备定义可以参考以下示例:

1
2
3
4
5
6
7
8
9
<memory model='virtio-mem'>
<target>
<size unit='GiB'>2</size>
<node>0</node>
<block unit='MiB'>2</block>
<requested unit='MiB'>0</requested>
</target>
<alias name='ua-virtiomem0'/>
</memory>

这里各字段的含义如下:

字段含义
model='virtio-mem'声明使用 virtio-mem 设备
size设备可提供的最大内存容量
node设备对应的虚拟 NUMA 节点
block内存调整的基本粒度
requested请求设备当前提供的内存容量
alias设备别名,方便后续指定目标设备

这里将设备最大容量设置为 2 GiB,内存块粒度设置为 2 MiB,初始请求容量为 0。

为什么使用 2 MiB?

在 x86_64 环境中,2 MiB 是常见的透明大页大小,也是 virtio-mem 常见的块大小选择。不过,实际值仍需要满足平台和设备的约束。

requested 设置为 0,意味着初始时不请求该设备向客户机提供额外内存。

注意,这个示例没有固定 PCI 地址。libvirt 在支持的情况下可以分配设备地址;如果需要手工指定,则应先检查现有 PCI 设备布局,避免地址冲突。

另外,node 也不能随便填写。应先查看虚拟机的 vCPU、基础内存和 NUMA 拓扑,再决定设备布局。

将设备添加到虚拟机

如果虚拟机正在运行,并且希望设备立即生效,同时保存到持久化配置,可以使用:

1
virsh attach-device vm0 virtio-mem.xml --live --config

其中:

  • --live:修改正在运行的虚拟机。
  • --config:修改持久化配置,使变更在后续启动时保留。

如果虚拟机已经关机,可以根据当前环境使用持久化配置方式添加设备。

执行前,应确认 XML 符合当前 libvirt 版本的要求,并且虚拟机满足设备热插拔条件。

设备添加成功,不等于客户机已经获得了额外内存。 客户机内核还需要识别设备,并将新加入的内存上线,才能正常使用。

客户机端:为什么要关注 online_movable?

使用 virtio-mem 前,需要配置客户机内核如何处理热插入内存。

RHEL 默认可以通过 udev 规则管理内存上线,但使用 virtio-mem 时,应按照 RHEL 10 的相关文档,将内存上线策略配置到内核。

常见策略包括:

策略特点
online_movable将热插入内存放入可移动内存区域,更有利于后续热拔
online_kernel热插入内存可以供内核和其他分配使用,但不保证后续能够全部热拔
auto-movable根据相关配置,在可移动与普通内存区域之间分配

如果希望后续能够比较可靠地缩容,可以优先测试 online_movable。

在客户机中执行:

1
2
3
grubby --update-kernel=ALL \
--remove-args=memhp_default_state \
--args=memhp_default_state=online_movable

然后重启客户机,使内核启动参数生效。

重启后检查:

1
2
cat /proc/cmdline
cat /sys/devices/system/memory/auto_online_blocks

重点确认内核启动参数已经包含:

1
memhp_default_state=online_movable

并检查系统报告的内存上线策略。

如果实际输出与预期不一致,应进一步检查 RHEL 版本、内核启动参数以及相关 udev 规则,不要仅凭某一个文件的输出就认定配置已经全部生效。

还要注意,online_movable 并不保证任何情况下都能立即热拔。如果内存区域分布、NUMA 布局或客户机内存使用情况不满足要求,缩容仍然可能失败。

动态扩容:请求增加 512 MiB

设备添加完成后,就可以尝试调整 virtio-mem 的请求容量。

先检查命令支持的参数:

1
virsh update-memory-device --help

如果虚拟机只有一个 virtio-mem 设备,可以执行:

1
virsh update-memory-device vm0 --alias ua-virtiomem0 --requested-size 512MiB

这里将设备请求容量设置为 512 MiB。

如果虚拟机有多个 virtio-mem 设备,应使用对应的设备别名或 NUMA 节点,避免修改错设备。

另外,请求容量必须满足设备的块大小要求,不能超过设备的最大容量。

执行后,先在宿主机查看虚拟机的实际配置:

1
virsh dumpxml vm0

重点关注 virtio-mem 设备中的 requested 和 current 字段。

其中:

  • requested 表示请求提供的内存容量。
  • current 表示设备当前实际提供的内存容量。

这两个值可能暂时不同。

然后进入客户机检查:

1
2
free -h
lsmem

free -h 可以查看客户机看到的内存总量、已用内存和可用内存;lsmem 则可以帮助观察内存块范围和在线状态。

如果请求容量已经增加,但客户机没有看到相应的可用内存,就需要进一步检查设备状态和内存上线配置。

动态缩容:把请求容量调回 0

当业务不再需要额外内存时,可以将请求容量调回零:

1
2
3
virsh update-memory-device vm0 \
--alias ua-virtiomem0 \
--requested-size 0

这表示请求 virtio-mem 不再向客户机提供额外内存。

但这里有个关键点:请求缩容不等于缩容一定成功。

宿主机和客户机需要完成相应的内存热拔流程。只有当客户机能够释放相应内存块,设备才能真正减少当前提供的内存容量。

如果客户机中的内存无法移动,或者系统配置不满足要求,就可能无法完成缩容。

因此,执行命令后不要只看命令是否返回成功,还应该再次检查:

1
virsh dumpxml vm0

以及客户机中的:

1
2
free -h
lsmem

确认设备的 current 容量是否已经下降,客户机看到的内存是否发生相应变化。

如果缩容失败,应先排查内存上线策略、内存使用情况和设备状态,而不是反复执行缩容命令。

最后还有一点:如果要从正在运行的虚拟机中完全移除 virtio-mem 设备,通常需要先将该设备的请求容量调为零,再根据当前环境支持的方式执行设备移除操作。减少内存容量与移除设备,是两件不同的事。

最后做一次体检

前面讲了设备、TuneD、磁盘调度器和动态内存。真正落地时,可以先从下面这组命令开始。

客户机中执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 查看当前 TuneD 配置
tuned-adm active
tuned-adm verify

# 查看内存回收倾向
sysctl vm.swappiness

# 确认磁盘名称
lsblk

# 查看磁盘调度器
cat /sys/block/vda/queue/scheduler

# 查看当前内存使用情况
free -h
lsmem

# 查看内核启动参数
cat /proc/cmdline

# 检查内存上线策略
cat /sys/devices/system/memory/auto_online_blocks

如果你的虚拟磁盘是 /dev/sda,请将上面的 /sys/block/vda/ 路径替换为实际设备路径。

其中,lsmem 和内存上线策略检查主要用于配置了热插入内存或 virtio-mem 的场景,不是所有虚拟机都必须执行。

宿主机中执行

1
2
3
4
5
6
7
8
# 查看虚拟机状态
virsh domstate vm0

# 查看虚拟机设备配置
virsh dumpxml vm0

# 查看 virtio-mem 设备及其状态
virsh dumpxml vm0 | grep -A12 -B2 "virtio-mem"

如果配置了设备别名,也可以直接在 XML 中搜索对应的别名。

这些命令可以帮助你确认配置,但还不能代替性能测试。

调优前后怎么比较?

如果要判断调优有没有效果,最好在修改前后使用相同的测试条件。

调优方向建议观察的指标
CPUCPU 使用率、vCPU 调度等待、业务响应时间
内存可用内存、换页活动、NUMA 访问情况
磁盘IOPS、吞吐量、平均延迟、p95/p99 延迟
网络吞吐量、丢包、重传、软中断和宿主机 CPU
virtio-mem请求容量、实际容量、客户机可用内存、缩容是否完成

如果测试磁盘性能,可以使用 fio 构造符合业务特点的测试负载;如果测试网络,可以使用 iperf3。

需要注意,测试参数必须与实际业务场景匹配。顺序读写性能好,不代表随机读写一定好;平均延迟下降,也不代表高分位延迟一定下降。

涉及生产环境时,应先在测试虚拟机上验证,避免为了测试性能影响正在运行的业务。

总结

虚拟机性能优化,并不是找到一个参数,把它改成某个值就结束了。

如果设备使用了不合适的模拟模型,可以优先评估 VirtIO;如果客户机采用了不合适的系统配置,可以先尝试 virtual-guest;如果怀疑磁盘 I/O 调度带来了额外开销,可以测试 none,但不要忽略宿主机后端。

如果业务对内存容量的需求变化很大,则可以进一步评估 virtio-mem,让虚拟机在满足自身内存需求的同时,更灵活地利用宿主机资源。

最后再强调一句:先确认瓶颈,再改配置,最后用数据判断是否真的有效。

这套思路不仅适用于 RHEL 虚拟机,也适用于日常的 Linux 性能排查。