---
url: /courses/virtualization-tech/13-performance-monitoring-tuning/index.md
---
# 第13课：虚拟化性能监控与单变量调优

![AI生成示意图：工程师在物理宿主机旁审计双侧指标并实施受控单变量虚拟化性能调优](./assets/12/performance-tuning-illustration.png)

> **图片说明**：AI生成原创教育插画。图片直观呈现“物理宿主机与虚拟机双侧性能观测、指标对比与受控调优”的实训情境；画面中的仪表数值为教学场景示意，准确测试数据以宿主机与来宾机命令实时采样输出为准。

::: tip 项目目标
在虚拟化数据中心日常运维中，工程师经常遭遇看似反常的性能瓶颈：“为什么给虚拟机加配了 4 个甚至 8 个 vCPU，虚拟机内部反而变得更卡？”、“为什么宿主机内存还有余量，虚拟机却频繁发生 I/O 假死甚至心跳失联？”、“为什么来宾机显示 CPU 使用率不高，而业务响应延迟却呈数倍激增？”。许多初学者在遇到性能劣化时，往往习惯盲目加大配置参数或随意调优，缺乏科学的物理基线支撑与控制变量思维，反而引发更为严重的资源争用与性能雪崩。

本课将带你深入虚拟化底层性能调度与资源映射的核心世界：

1. **CPU 调度与亲和性**：深度剖析 KVM 虚拟化下 vCPU 线程化本质与 Linux 完全公平调度器（CFS）的协同机制，解构 CPU 超分（Overcommit）争用背后的上下文切换开销与 CPU Steal Time（%st）物理本质，掌握 vCPU 亲和性绑核（`virsh vcpupin`）与 NUMA 架构缓存局部性调优；
2. **内存二重映射与气球伸缩**：洞悉硬件辅助二级页表映射（EPT/NPT）与 VirtIO 内存气球（virtio\_balloon）动态伸缩规律，辨析内核内置驱动机理，严防宿主机 Swap 换页灾难；
3. **磁盘缓存模式与真实性能**：客观评测 QEMU 磁盘四大缓存模式（none、writethrough、writeback、directsync）的落盘安全性与吞吐权衡，揭示 Direct I/O 跑分与企业生产选型的底层真相；
4. **单变量受控压测与证据闭环**：建立“空闲基线 -> 受控定额施压 -> 双侧指标采集 -> 孤立单变量变更 -> 同负载复测比对 -> 决策固化”的闭环工程方法论，通过标准 `sysbench` 测试套件对比默认浮动调度、亲和性绑核与单核算力瓶颈，具备独立排查高并发虚拟化性能瓶颈的实战能力。
   :::

## 一、虚拟化 CPU 调度机理与拓扑基线探针

在物理服务器上，操作系统直接调度物理处理器执行机器指令。而在硬件辅助虚拟化环境下，虚拟机的计算资源抽象与调度经历了两层映射：来宾操作系统的内核调度器将虚拟机内的进程调度到 vCPU 上，而宿主机的 Linux 内核则将各个 vCPU 作为普通的系统线程调度到物理核心（pCPU）上。

### 1.1 KVM vCPU 线程化本质与 Linux CFS 调度器协作机理

要理解虚拟化 CPU 性能的瓶颈根源，首先必须确立以下核心底层物理事实：

```mermaid
flowchart TB
    subgraph GUEST ["虚拟机内部 (Guest OS 视角)"]
        P1["应用进程 A (PID 1024)"] -->|调度| V0["vCPU 0"]
        P2["应用进程 B (PID 1025)"] -->|调度| V1["vCPU 1"]
        V0 -.->|共享内核锁 / 自旋锁| V1
    end

    subgraph HOST ["物理宿主机内部 (Host OS 视角)"]
        T0["QEMU vCPU-0 线程 (PID 5081)"]
        T1["QEMU vCPU-1 线程 (PID 5082)"]
        IO["QEMU I/O 辅助线程 (PID 5080)"]

        T0 -->|加入运行队列| CFS["Linux 完全公平调度器 (CFS)"]
        T1 -->|加入运行队列| CFS
        IO -->|加入运行队列| CFS

        CFS --> PCORE0["物理核心 pCPU 0"]
        CFS --> PCORE1["物理核心 pCPU 1"]
        CFS --> PCORE2["物理核心 pCPU 2"]
        CFS --> PCORE3["物理核心 pCPU 3"]
    end

    V0 ==> T0
    V1 ==> T1
```

1. **vCPU 的真实身份是宿主机进程树上的普通 POSIX 线程**：在 KVM/QEMU 架构中，每一个虚拟 CPU（vCPU）在宿主机操作系统中都严格对应一个由主进程 `qemu-system-x86_64` 派生出的系统线程。
2. **CPU 指令执行依靠硬件辅助直接执行**：当该线程被调度到物理核心上执行时，KVM 通过 Intel VT-x 或 AMD-V 指令集发起 `VMENTRY`，物理 CPU 切换为 Non-Root 模式，物理硬件直接以原生速度执行来宾机指令。
3. **VM-Exit 模式切换惩罚**：一旦来宾机执行敏感特权指令（如修改控制寄存器 CR3、响应物理硬件中断或访问未映射的 I/O 端口），硬件将强制触发 `VM-Exit` 中断，物理 CPU 剥夺运行权并切回宿主机 Root 模式，由宿主机内核处理后再发起 `VMENTRY`。每一次模式切换都伴随着寄存器上下文保存、TLB 快表刷新与流水线清空，产生数百甚至上千个物理时钟周期的性能开销。
4. **CFS 调度器的竞争视角**：宿主机的完全公平调度器（CFS）在分配物理 CPU 时间片时，**并不知道某个线程是虚拟机的虚拟核心，而是将其等同于普通的后台服务进程**。若宿主机上存在其他密集型任务，CFS 会根据虚拟运行时间（`vruntime`）公平抢占物理核心。

### 1.2 CPU 超分模型、协同调度锁竞争与 CPU Steal Time（%st）物理本质

虚拟化的一大核心经济价值在于**资源超分（Overcommit）**，即分配给所有虚拟机的总 vCPU 数量超过物理宿主机实际拥有的物理核心（pCPU）总数。然而，超分是一把双刃剑：

```mermaid
flowchart LR
    A["盲目增加虚拟机 vCPU<br>(例如 4 核物理机分配 8 vCPU)"] --> B["宿主机 CFS 运行队列严重积压<br>(Run Queue 深度倍增)"]
    B --> C["物理核心频繁发生线程抢占<br>(上下文切换 Context Switch 激增)"]
    C --> D["多核协同调度自旋锁等待<br>(Co-scheduling Lock Stall)"]
    D --> E["CPU Steal Time (%st) 剧烈飙升<br>来宾应用感知严重卡死延时"]
```

1. **CPU 超分比的工程健康水位**：
   * 生产核心数据库与高吞吐服务：建议超分比 `<= 1:1`（1 个 pCPU 分配 1 个 vCPU），甚至物理绑核独占；
   * 通用企业办公与 Web 集群：建议超分比在 `1.5:1 ~ 2:1` 之间；
   * 实验教学与开发测试沙箱：超分比可容忍 `2:1 ~ 3:1`，但严禁超过 `4:1`。
2. **多核协同调度（Co-scheduling）锁竞争灾难**：
   * 在多核虚拟机内部，应用进程或操作系统内核为了保障共享数据结构的一致性，频繁使用自旋锁（Spinlock）。
   * 假设虚拟机配置了 4 个 vCPU，当 vCPU 0 获取了自旋锁准备写入数据时，宿主机 CFS 调度器因时间片耗尽将 vCPU 0 对应的物理线程切出挂起。
   * 此时，正在其他物理核上运行的 vCPU 1、vCPU 2 尝试获取同一把自旋锁，但由于锁的持有者（vCPU 0）已被挂起，vCPU 1 和 2 只能持续在物理核上做无效的**空转自旋（Spinning Stall）**，白白消耗物理 CPU 算力。
   * 这正是\*\*“盲目给虚拟机增加 vCPU，性能反而断崖式下跌”\*\*的底层物理机理。
3. **CPU Steal Time（%st）的权威定义**：
   * 在 Linux `top` 或 `vmstat` 命令的 CPU 状态行中，最后一项 `%st` 代表 **CPU Steal Time（被偷走的 CPU 时间）**。
   * **物理定义**：虚拟机内部的 vCPU 已经处于 Runnable（就绪态），随时准备执行计算指令，但宿主机的 Hypervisor 却因物理核心被其他虚拟机或宿主进程争用，**无法为该 vCPU 分配物理时间片所导致的强制等待时间占比**。
   * **判定水线**：当 `%st` 持续低于 1% 时属于极健康状态；当 `%st` 处于 3%~8% 时说明存在中度资源争用；当 `%st > 10%` 时，说明宿主机 CPU 已严重过载超分，业务系统将出现极度明显的响应迟钝与连接超时。

### 1.3 vCPU 亲和性绑核与 NUMA 拓扑缓存局部性优化

在现代多物理核心与 NUMA（Non-Uniform Memory Access，非一致性内存访问）架构服务器中，物理 CPU 对不同内存槽位的访问延迟存在显著差异：

```mermaid
flowchart TD
    subgraph NODE0 ["NUMA 节点 0 (Node 0)"]
        p0["pCPU 0"] --- L20["L2/L3 本地缓存"]
        p1["pCPU 1"] --- L20
        RAM0[("本地物理内存 (Local RAM)<br>访问延迟: ~60 ns")]
        L20 --- RAM0
    end

    subgraph NODE1 ["NUMA 节点 1 (Node 1)"]
        p4["pCPU 4"] --- L21["L2/L3 本地缓存"]
        p5["pCPU 5"] --- L21
        RAM1[("远端物理内存 (Remote RAM)<br>访问延迟: ~120 ns")]
        L21 --- RAM1
    end

    RAM0 <-->|跨插槽 UPI / QPI 高速互联总线<br>传输延迟额外增加 100%| RAM1
```

1. **默认浮动调度的缓存击穿**：
   * 若未配置绑核亲和性，Linux CFS 会将虚拟机的 vCPU 线程在宿主机各个物理核心之间频繁迁移。
   * 每次迁移都会导致原物理核心的 L1/L2 高速硬件缓存（Cache）彻底失效，新的核心必须重新从低速物理内存中拉取指令与数据（冷缓存颠簸），导致 CPU 吞吐量衰减 15%~30%。
2. **跨 NUMA 远端内存访问惩罚**：
   * 若虚拟机的 vCPU 运行在 Node 0 的物理核上，而其分配的内存页却驻留在 Node 1 的内存插槽中，每一次内存读写都必须穿透主板上的 UPI/QPI 互联总线，内存访问延迟从 60 纳秒激增至 120 纳秒以上。
3. **vCPU 严格绑核与模拟器线程隔离（vcpupin / emulatorpin）**：
   * 通过 `virsh vcpupin` 将虚拟机的 vCPU 1:1 独占绑定到固定的物理核心，牢牢锁死 L1/L2 缓存局部性；
   * 通过 `virsh emulatorpin` 将 QEMU 的主事件循环与设备模拟辅助线程隔离到独立的辅助物理核心上，彻底杜绝 I/O 线程争抢计算核心。

### 1.4 实训任务一：宿主机与虚拟机 CPU 拓扑探针与空闲性能基线审计

在实施任何虚拟化性能调优前，必须对物理宿主机的硬件架构、CPU 核心数、NUMA 拓扑进行全面探查，并通过标准 SSH 网络通道与虚拟机 `svr01` 建立交互式管理会话。在零外部负载的前提下，同步采集宿主侧与来宾侧的静默空闲基线数据，确立可信的对比基准点。

::: tip 实验工作台推荐布局：双终端协同操作
为方便开展宿主机外侧与虚拟机内侧的实时双侧指标协同审计，建议在操作机上打开两个终端窗口（或分屏）：

* **终端 1（宿主机终端 Host）**：保持在物理宿主机命令行，负责运行宿主侧生命周期管理、设备配置与全局资源采样；
* **终端 2（虚拟机终端 svr01）**：通过标准 SSH 交互式登录虚拟机 `svr01`（`ssh cloudstudent@<实际IP>`）。登录后保留该会话，后续所有来宾机内部的系统检查、驱动验证、基准施压与 I/O 测试均在此窗口中连贯执行。交互式会话自带标准伪终端（TTY），可正常处理 `sudo` 密码输入。
  :::

#### 步骤 1：宿主机物理 CPU 核心拓扑探查

在宿主机终端上，原生执行底层拓扑命令，探查物理主机的硬件架构、Socket 插槽数、物理核心总数、线程数及 NUMA 节点分布。

```bash
# 查看宿主机 CPU 架构与硬件拓扑全貌
lscpu

# 查询 Libvirt 视角下的计算节点物理资源基准
virsh nodeinfo

# 查询宿主机的物理拓扑与 NUMA 节点分布能力
virsh capabilities
```

执行后，终端将返回详细的硬件拓扑信息。参考回显如下：

```text
# lscpu 输出参考
Architecture:             x86_64
  CPU op-mode(s):         32-bit, 64-bit
  Address sizes:          39 bits physical, 48 bits virtual
  Byte Order:             Little Endian
CPU(s):                   4
  On-line CPU(s) list:    0-3
Vendor ID:                GenuineIntel
  Model name:             Intel(R) Core(TM) i5-G4
    CPU family:           6
    Model:                140
    Thread(s) per core:   1
    Core(s) per socket:   4
    Socket(s):            1
    NUMA node(s):         1
NUMA node0 CPU(s):        0-3

# virsh nodeinfo 输出参考
CPU model:           x86_64
CPUs:                4
CPU frequency:       2400 MHz
CPU socket(s):       1
Core(s) per socket:  4
Thread(s) per core:  1
NUMA cell(s):        1
Memory size:         16384000 KiB
```

**命令输出核心字段深度解析**：

1. `CPU(s): 4` 与 `On-line CPU(s) list: 0-3`：表示宿主机操作系统当前在线可调度的逻辑 CPU 编号范围为 0 到 3，总计 4 颗核心；
2. `Thread(s) per core: 1` 与 `Core(s) per socket: 4`：表示每个物理核心仅有 1 个硬件线程（未开启超线程），单个 CPU 插槽拥有 4 个独立的物理核心。在生产容量规划中，必须以物理核心数（pCPU）作为算力基准，避免将共享执行单元的超线程逻辑核心等同于完整独立的算力单元；
3. `Socket(s): 1` 与 `NUMA node(s): 1`：表示当前主机为单路 CPU，所有 4 个核心同属于 `node 0`，共享同一块本地物理内存，不存在跨插槽访问延迟；
4. `virsh nodeinfo` 的 `Memory size: 16384000 KiB`：表示宿主机总物理内存约为 16GB。

#### 步骤 2：虚拟机 IP 探测与 SSH 交互式会话建立

在进行性能测试与管理时，严禁使用 `virsh console` 串口终端，因为低速字符模拟设备会在高并发下产生严重的中断风暴并导致测量失真。通过标准 SSH 网络协议建立独立的交互式管理通道，既消除了中断干扰，又具备完整的 TTY 交互能力。

```bash
# 【终端 1：宿主机终端】确认虚拟机 svr01 运行状态
virsh list --all

# 【终端 1：宿主机终端】查询虚拟机 svr01 动态分配到的 IPv4 地址
virsh domifaddr svr01
```

```text
# virsh domifaddr svr01 输出参考
 Name       MAC address          Protocol     Address
-------------------------------------------------------------------------------
 vnet0      52:54:00:fa:12:34    ipv4         192.168.122.100/24
```

获取到 IP 地址后，在终端 2 建立交互式 SSH 会话：

```bash
# 【终端 2：虚拟机终端】使用 SSH 交互式登录虚拟机 svr01
# 说明：请将 192.168.122.100 替换为上方实际查询出的 IPv4 地址
ssh -o StrictHostKeyChecking=no cloudstudent@192.168.122.100

# 登录成功后核验会话与基础运行时间
uptime
```

```text
# uptime 输出参考
 10:15:30 up 12 min,  1 user,  load average: 0.00, 0.01, 0.00
```

`uptime` 成功输出系统运行时间与平均负载，终端提示符显示 `cloudstudent@svr01:~$`，验证了纯净的数据通信链路已完全建立。

#### 步骤 3：宿主机与虚拟机双侧静默空闲基线数据采集

在未运行任何压测负载时，同步采集宿主侧与来宾侧的 CPU 使用率、物理内存余量与负载均值，确认系统处于纯净的静默空闲态（Idle Baseline），排除背景干扰项。

```bash
# 【终端 1：宿主机终端】采集宿主机全局空闲指标与负载均值
uptime
free -h

# 【终端 1：宿主机终端】采集虚拟机 svr01 在宿主机侧的物理 CPU 累积时间与初始状态
virsh domstats svr01 --cpu-total --balloon
```

```text
# virsh domstats svr01 --cpu-total --balloon 输出参考
Domain: 'svr01'
  cpu.time=18452309112
  cpu.user=12301928000
  cpu.system=6150381112
  balloon.current=2097152
  balloon.maximum=2097152
  balloon.last-update=1723850130
```

```bash
# 【终端 2：虚拟机终端】采集虚拟机内部静默空闲状态
uptime
free -m
vmstat 1 2
```

```text
# vmstat 1 2 输出参考
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0      0   1620     28    245    0    0     2     5   45   78  0  0 100  0  0
 0  0      0   1620     28    245    0    0     0     0   32   55  0  0 100  0  0
```

**双侧指标字段物理意义深度解析**：

1. `vmstat` 核心字段拆解：
   * `procs`：`r`（Run Queue 就绪运行队列）当前为 `0`，表示无排队等待 CPU 的进程；`b`（Blocked 阻塞队列）为 `0`，表示无等待 I/O 资源的不可中断进程；
   * `memory`：`swpd`（已使用 Swap 容量）严格为 `0`，`free` 显示有约 1620MB 空闲内存；
   * `swap`：`si`（Swap In 换入）与 `so`（Swap Out 换出）严格为 `0`，证明无任何内存交换发生；
   * `cpu`：`id`（Idle 空闲百分比）为 `100%`，`st`（Steal Time 抢占百分比）严格为 `0%`，证明宿主机未发生任何算力争用；
2. `virsh domstats` 核心字段拆解：
   * `cpu.time=18452309112`：表示自虚拟机启动以来，QEMU 进程累计占用的宿主机物理 CPU 时间为 18,452,309,112 纳秒（约 18.45 秒）；
   * `balloon.current=2097152` 与 `balloon.maximum=2097152`：表示当前虚拟机分配并持有的内存配额为 2,097,152 KiB（即 2048 MB）。

## 二、内存二重映射、气球驱动动态调节与宿主水线联动

虚拟化环境下的内存管理经历了两层地址转换与映射。理解物理内存到虚拟内存的二重映射路径，以及 VirtIO 气球驱动如何在两层操作系统之间动态腾挪内存，是保障系统稳定、杜绝内存溢出雪崩的核心基本功。

### 2.1 硬件辅助二级页表映射（EPT/NPT 两维页表遍历）与 HugePages 大页内存

在非虚拟化操作系统中，内存管理仅需完成一层地址转换：**虚拟地址（VA）-> 物理地址（PA）**。但在硬件辅助虚拟化架构中，内存被划分为了四类地址空间，经历了两级转换：

```mermaid
flowchart LR
    GVA["来宾虚拟地址<br>(GVA)"] -->|来宾操作系统页表<br>(Guest Page Table)| GPA["来宾物理地址<br>(GPA)"]
    GPA -->|硬件扩展页表<br>Intel EPT / AMD NPT| HPA["宿主机物理地址<br>(HPA)"]
    HVA["宿主机虚拟地址<br>(HVA, QEMU 进程空间)"] -.->|宿主内核直接映射| HPA
```

1. **二重地址翻译路径**：
   * **GVA（Guest Virtual Address）**：来宾操作系统中用户态进程访问的逻辑地址；
   * **GPA（Guest Physical Address）**：来宾操作系统内核所“认为”的物理内存地址；
   * **HVA（Host Virtual Address）**：宿主机操作系统为 QEMU 进程分配的虚拟内存映射空间（通过 `mmap` 分配）；
   * **HPA（Host Physical Address）**：物理服务器主板内存插槽上的真实硬件物理页帧。
2. **两维页表遍历（Two-Dimensional Page Walk）开销**：
   * 当虚拟机发生 TLB 快表缺失时，硬件 MMU 必须同时遍历来宾页表（4 级）与宿主机 EPT 页表（4 级）。在最坏情况下，一次内存访问可能引发 $(4 + 1) \times (4 + 1) - 1 = 24$ 次物理内存寻址操作。
3. **大页内存（HugePages）压制寻址开销**：
   * Linux 默认内存物理页大小为 4KB。在分配 16GB 内存的虚拟机上，系统需要维护超过 400 万个页表项，极易造成硬件 TLB 缓存溢出。
   * 通过配置 **2MB 或 1GB 大页内存（Transparent HugePages 或 Static HugePages）**，页表项数量可缩减 500 至 250,000 倍，大幅降低两维页表遍历深度，可使内存密集型应用（如 Redis、PostgreSQL）性能直接提升 10%~25%。

### 2.2 VirtIO 内存气球驱动（virtio\_balloon）动态弹性调节机制与 Swap 换页灾难

内存是物理服务器上成本最高、最容易吃紧的硬件资源。\*\*VirtIO 内存气球（Memory Ballooning）\*\*是 KVM 平台实现内存动态超分与无损回收的核心技术：

```mermaid
sequenceDiagram
    autonumber
    participant Host as 宿主机 (Host / Libvirt)
    participant QEMU as QEMU 虚拟机管理进程
    participant Balloon as 来宾内核 virtio_balloon 驱动
    participant GuestKernel as 来宾操作系统内核 (Guest OS)

    Note over Host,GuestKernel: 场景：宿主机物理内存告急，需回收 svr01 的 1024MB 内存
    Host->>QEMU: 下发指令：virsh setmem svr01 1048576 (缩至 1GB)
    QEMU->>Balloon: 通过 Virtqueue 队列通知气球驱动“充气膨胀”
    Balloon->>GuestKernel: 向来宾内核申请 1024MB 连续空闲内存页并 Pin 锁死
    GuestKernel-->>Balloon: 分配成功，来宾内可用空闲内存骤降
    Balloon->>QEMU: 将已锁定的 GPA 物理页号清单回传给 QEMU
    QEMU->>Host: 调用 madvise(MADV_DONTNEED) 释放对应的真实 HPA 物理页
    Note over Host: 宿主机物理内存水线瞬间提升 1024MB！
```

1. **气球膨胀（Balloon Inflating，宿主机回收内存）**：
   * 宿主机物理内存紧张时，调用 `virsh setmem` 下发收缩指令；
   * 虚拟机内部的 `virtio_balloon` 驱动向来宾机内核申请分配指定大小的物理内存页面并锁定；
   * 驱动将这批 GPA 页面清单通知宿主机 QEMU，QEMU 调用系统调用 `madvise(MADV_DONTNEED)` 将对应的宿主物理 HPA 页帧交还给宿主机物理内存池。
   * 来宾机内部看到 `free -m` 的空闲内存减少，但无需关机即可腾挪宿主物理空间。
2. **气球放气（Balloon Deflating，宿主机返还内存）**：
   * 当宿主机内存宽裕，或虚拟机业务繁忙需要更多内存时，宿主机下调气球配额；
   * 气球驱动将此前锁定的内存页面释放归还给来宾机内核伙伴系统；
   * 来宾机的可用空闲内存水线恢复。
3. **宿主机物理 Swap 换页的致命灾难**：
   * **延迟差距**：物理内存（RAM）读写延迟为 `50~70 纳秒`，而物理磁盘 I/O 延迟为 `5~10 毫秒`（差距达到 10 万倍）；
   * **灾难后果**：若过度超分导致宿主机物理内存彻底耗尽，宿主内核 `kswapd` 守护进程将被迫介入，**将 QEMU 虚拟机的部分匿名内存页面强行换出（Swap Out）到磁盘 Swap 分区上**。一旦虚拟机稍有内存访问，就会触发宿主机的剧烈缺页中断（Page Fault），虚拟机将发生严重假死、网络心跳断连，系统日志输出 `rcu_sched detected stalls on CPUs/tasks`，甚至引发集群 HA 误判宕机。
   * **工业准则**：**企业级虚拟化宿主机必须严格监控 Swap 换页指标（`si/so` 必须为 0），严禁依赖 Swap 分区来维系虚拟机内存超分。**
4. **来宾内核气球驱动（virtio\_balloon）形态辨析：内置（Built-in）与模块（Module）**：
   * 气球能够生效的前提是来宾 Linux 内核激活了 `virtio_balloon` 驱动；
   * **内置形态（Built-in，`CONFIG_VIRTIO_BALLOON=y`）**：在 Ubuntu Server 官方内核中，VirtIO 气球驱动被**直接静态编译在内核二进制镜像中**。**特别注意：内置驱动天然常驻生效，但绝对不会出现在 `lsmod` 输出中（`lsmod` 仅展示动态加载的外部 `.ko` 模块）**；
   * **权威判定基准**：只要系统总线路径 `/sys/bus/virtio/drivers/virtio_balloon` 存在，或 `/lib/modules/$(uname -r)/modules.builtin` 记录了该驱动，即代表气球驱动已完全就绪。

### 2.3 实训任务二：VirtIO 内存气球动态伸缩与宿主机内存水线核验

在虚拟机 `svr01` 运行期间，在不中断业务、不重启虚拟机的在线状态下，利用 VirtIO Balloon 机制将虚拟机内存从 2048MB 在线动态收缩为 1024MB，观察来宾机内部可用内存变化与宿主机物理可用内存的实时返还；随后执行在线放气扩容，完全恢复初始配额，验证内存弹性伸缩闭环。

#### 步骤 1：核查虚拟机 VirtIO Balloon 驱动状态与当前内存配置

在宿主机上核验虚拟机 XML 定义中的气球设备模型，并在虚拟机内部审计 `virtio_balloon` 驱动形态。

```bash
# 【终端 1：宿主机终端】检查虚拟机 svr01 的内存气球硬件定义
virsh dumpxml svr01

# 【终端 1：宿主机终端】开启虚拟机内存气球周期性统计上报（每 5 秒刷新），并采集当前内存基线
virsh dommemstat svr01 --period 5
virsh dommemstat svr01
```

```text
# virsh dumpxml svr01 片段参考
    <memballoon model='virtio'>
      <address type='pci' domain='0x0000' bus='0x05' slot='0x00' function='0x0'/>
    </memballoon>

# virsh dommemstat svr01 输出参考
actual 2097152
swap_in 0
swap_out 0
major_fault 0
minor_fault 12450
unused 1658880
available 1986560
usable 1853440
last_update 1723850200
```

```bash
# 【终端 2：虚拟机终端】在来宾机交互终端核查气球驱动总线路径
ls -d /sys/bus/virtio/drivers/virtio_balloon

# 【终端 2：虚拟机终端】采集当前虚拟机系统内存基线
free -m
```

```text
# 终端 2 输出参考
/sys/bus/virtio/drivers/virtio_balloon

               total        used        free      shared  buff/cache   available
Mem:            1940         125        1620           1         195        1815
Swap:              0           0           0
```

**状态解读与驱动机理解析**：

1. 宿主机 XML 确认包含 `<memballoon model='virtio'>`，表明虚拟硬件已配置 VirtIO 气球设备；
2. 开启 `--period 5` 后，`virsh dommemstat` 能够正常采集到 `actual: 2097152`（2048 MB）以及 `unused` 等来宾机内部内存指标；
3. 虚拟机终端成功输出 `/sys/bus/virtio/drivers/virtio_balloon`，确认内核已激活气球驱动；`free -m` 报告初始 `total` 约为 1940MB（2048MB 扣除内核预留开销后的实际容量）。

#### 步骤 2：在线动态膨胀气球回收 1GB 内存

执行 `virsh setmem` 命令将当前运行内存动态设为 1048576 KiB（即 1024 MB），迫使气球膨胀 1GB 并释放物理页帧。

::: warning 关键避坑指南：严格遵循 KiB 参数单位与命令生效作用域规范

1. **参数单位规范**：`virsh setmem` 在未指定单位时默认接收的是 **KiB**（$1024 \times 1024 = 1048576\text{ KiB}$）。若误输入 `virsh setmem svr01 1024`，实际只分配了 1024 KiB = 1 MB 内存，会导致虚拟机瞬间内存枯竭并触发 Kernel Panic 崩溃；
2. **生效作用域辨析**：
   * `--live`：仅对当前正在运行的虚拟机实例实时生效，虚拟机冷重启后内存自动恢复为原配置；
   * `--config`：仅将修改持久化写入 Libvirt XML 配置文件，对当前运行中的实例不产生影响，待下次冷重启开机后生效；
   * `--live --config`：同时兼顾当前运行实例在线即时生效与持久化落盘配置。本实训为观察即时弹性伸缩，采用 `--live` 方式执行动态调整。
     :::

```bash
# 【终端 1：宿主机终端】记录调整前宿主机可用物理内存容量
free -h

# 【终端 1：宿主机终端】在线收缩虚拟机内存至 1024 MB (1048576 KiB)
virsh setmem svr01 1048576 --live

# 稍等数秒后，核验虚拟机在宿主机的分配统计与宿主可用内存变化
sleep 3
virsh dommemstat svr01
free -h
```

```bash
# 【终端 2：虚拟机终端】在来宾机终端核验可用内存变化
free -m
```

```text
# 终端 2 free -m 输出参考
               total        used        free      shared  buff/cache   available
Mem:             916         128         600           1         188         788
Swap:              0           0           0
```

**双侧数据联动解析**：

* 宿主机侧 `virsh dommemstat` 中 `actual` 调整为 `1048576`，宿主机 `free -h` 的 `available` 较调整前相应提升约 1GB；
* 虚拟机内部 `free -m` 中 `total` 自动收敛至约 916 MB，证明气球已成功在来宾机内膨胀并把物理页帧退还给了宿主机。

#### 步骤 3：动态收缩气球释放内存配额并恢复业务可用空间

通过再次调用 `virsh setmem` 将内存配额无损恢复至初始的 2048 MB（2097152 KiB），使气球驱动放气，验证弹性伸缩的双向可逆性。

```bash
# 【终端 1：宿主机终端】在线将虚拟机内存恢复至 2048 MB (2097152 KiB)
virsh setmem svr01 2097152 --live

# 稍等数秒后核验宿主机侧分配配额
sleep 3
virsh dommemstat svr01
```

```bash
# 【终端 2：虚拟机终端】在来宾机终端确认内存已恢复充裕
free -m
```

```text
# 终端 2 free -m 输出参考
               total        used        free      shared  buff/cache   available
Mem:            1940         128        1618           1         194        1812
Swap:              0           0           0
```

虚拟机内部 `free -m` 再次显示 `total` 回升至约 1940 MB，业务应用恢复充裕内存空间，全程无需重启系统。

## 三、磁盘 I/O 虚拟化路径与 QEMU 缓存模式实测

持久存储 I/O 是虚拟化系统中最容易遭受物理硬件时延制约的环节。深入理解 Linux 宿主机 PageCache 与来宾机 PageCache 的双重缓存机理，掌握 QEMU 4 种缓存模式在可靠性与吞吐性能之间的科学权衡，是保障虚拟化集群 I/O 吞吐的核心基本功。

### 3.1 QEMU 磁盘四大缓存模式语义与落盘安全性权衡矩阵

QEMU 提供了 4 种主流的磁盘缓存模式（Cache Mode），每种模式在“写入吞吐量”与“突发掉电安全性”之间做出了截然不同的工程取舍：

| 缓存模式 (`cache=`) | 宿主 PageCache | 来宾 PageCache | Direct I/O (`O_DIRECT`) | 写确认 (ACK) 时机 | 突发掉电安全性 | 典型吞吐量 | 推荐工业生产场景 |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
| **`none`** | **关闭 (绕过)** | 开启 | **开启** | 写入物理硬件控制器写缓存（支持 fsync 屏障） | **高** (合规安全) | **高且极其平稳** (避免双重缓存开销) | **企业生产首选推荐**：生产数据库、大容量存储池、SAN/NVMe 块存储 |
| **`writethrough`** | 开启 (读加速) | 开启 | 关闭 | **物理介质完全写入落盘后才向应用返回** | **极高** (零数据丢失风险) | **较低** (受单次物理落盘硬时延限制) | 金融交易核心账本、防篡改审计日志节点 |
| **`writeback`** | **开启** | 开启 | 关闭 | **写入宿主 PageCache 即立即向应用返回** | **中等** (宿主掉电可能丢失最后几秒脏页) | **极高** (突发爆发性能强) | 临时测试沙盒、CI/CD 自动化代码构建节点、带多节点热备集群 |
| **`directsync`** | **关闭 (绕过)** | 开启 | **开启** | 绕过宿主缓存并同步落盘确认 | **极高** (直接物理同步落盘) | **极低** (无任何软件缓冲平滑) | 专用硬件存储验证、要求 100% 同步落盘的特殊归档系统 |

### 3.2 磁盘缓存实测性能真相与生产选型机理

在基准性能测试中，初学者常常会遇到一个认知困惑：在小数据量或常规 `dd` / `fio` 压测中，`cache='none'` 的跑分数据往往**明显低于** `cache='writeback'` 甚至低于带有宿主内存加速的某些读写工况。

**实测跑分真相与生产选型底层机理解构**：

1. **跑分差异真相**：`cache='none'` 启用了 `O_DIRECT` 标志，所有写请求完全绕过了宿主机的物理 RAM 缓存，直接穿透到物理磁盘控制器；而 `writeback` 模式下，写操作仅写入宿主高速物理内存（RAM 带宽达数十 GB/s）即刻返回确认，测出的实际上是宿主机的内存写入速度。
2. **为什么企业生产依然首选 `cache='none'`**：
   * **消除双重缓存（Double Caching）与内存耗尽风险**：在 `writeback` 或 `writethrough` 模式下，同一个数据块在来宾内核被缓存一次（Guest PageCache），在宿主机内核又被缓存一次（Host PageCache）。这会导致宿主机宝贵的物理内存被大量重复数据占满，压低宿主可用内存水线并诱发致命的 Swap 换页；
   * **消除 I/O 延迟毛刺（Latency Jitter）**：当宿主机 PageCache 积聚大量脏页后，Linux 内核会触发同步刷盘，导致后续 I/O 出现长时间的阻塞卡顿；`cache='none'` 保证了 I/O 延迟的高度确定性与平稳性；
   * **保障数据库事务一致性与集群热迁移安全**：在跨宿主机热迁移（Live Migration）场景中，若使用宿主机缓存，源宿主与目标宿主的 PageCache 状态不一致会导致磁盘数据损坏；而 `cache='none'` 确保数据直接落盘到底层共享存储，是集群高可用与迁移的必备前提。

### 3.3 实训任务三：QEMU 磁盘缓存模式变更与 I/O 吞吐/延迟双侧实测

通过受控的定额 I/O 块写入（使用 `dd` 结合 `conv=fdatasync` 强制落盘屏障），在虚拟机内部观测写入吞吐速率，同时在宿主机侧使用 `virsh domblkstat` 提取底层块设备的物理请求数与读写耗时，实测对比直接 I/O（`cache='none'`）与写穿（`cache='writethrough'`）的真实性能差异。

#### 步骤 1：默认直接 I/O 模式（cache='none'）定额块写入实测

在直接 I/O 模式下，虚拟机通过 Direct I/O 绕过宿主机页面缓存直接提交写请求，并携带 `fdatasync` 同步落盘屏障，记录完成 500MB 定额连续数据块写入的耗时与吞吐表现。

::: warning 关键避坑指南：写入路径须选择 /var/tmp 避开 tmpfs 内存盘
在 Ubuntu Server 中，`/tmp` 目录通常被系统默认挂载为基于内存的临时文件系统（`tmpfs`）。若向 `/tmp/io-test.dat` 写入，测试的实际上是虚拟机分配给系统的 RAM 内存读写带宽，**根本不会产生真实的虚拟磁盘 vda I/O 活动**。因此测试文件必须存放在根分区所在的物理磁盘目录 `/var/tmp/` 下。
:::

```bash
# 【终端 1：宿主机终端】检查虚拟机当前虚拟磁盘驱动的 XML 定义
virsh dumpxml svr01

# 【终端 1：宿主机终端】记录压测前宿主机侧虚拟磁盘 I/O 累积统计基线
virsh domblkstat svr01 vda
```

```bash
# 【终端 2：虚拟机终端】在来宾机执行 500MB 定额数据块落盘写入测试，测试后清理
dd if=/dev/zero of=/var/tmp/io-test.dat bs=1M count=500 conv=fdatasync
rm -f /var/tmp/io-test.dat
```

```bash
# 【终端 1：宿主机终端】压测完成后，立即提取宿主机侧结算 I/O 统计
virsh domblkstat svr01 vda
```

```text
# 终端 2 dd 输出参考
500+0 records in
500+0 records out
524288000 bytes (524 MB, 500 MiB) copied, 2.1542 s, 243 MB/s

# 终端 1 virsh domblkstat svr01 vda 输出参考
vda rd_req 1420
vda rd_bytes 38510592
vda wr_req 1890
vda wr_bytes 562810880
vda wr_total_times 1845120980
vda flush_req 520
vda flush_total_times 312045000
```

**命令输出核心字段解析**：

1. `dd` 中的 `conv=fdatasync`：强制要求物理数据块与元数据在写入完成前全部刷入物理存储介质，确保测出的是真实的磁盘 I/O 性能，而非内存拷贝；
2. `domblkstat` 中的 `wr_bytes`：从基线值增长了约 524MB（$524,288,000\text{ 字节}$），精准对应 500MB 测试文件，证明写入请求已全量穿透至虚拟磁盘；
3. `wr_total_times` 与 `flush_total_times`：记录了块设备执行写入与刷新操作所消耗的总纳秒数。

#### 步骤 2：关机变更磁盘缓存模式为写穿（cache='writethrough'）

安全关闭虚拟机，通过官方工具 `virt-xml` 精确修改虚拟磁盘驱动属性为 `cache='writethrough'`，重新开机以构建对比工况。

```bash
# 【终端 1：宿主机终端】正常下发关机指令
virsh shutdown svr01

# 检查虚拟机状态，确认已完全处于 shut off 状态
virsh domstate svr01

# 使用 virt-xml 安全修改虚拟磁盘缓存模式为 writethrough
virt-xml svr01 --edit --disk target=vda,driver.cache=writethrough

# 核验持久化 XML 中的 driver 配置
virsh dumpxml svr01 --inactive

# 重新启动虚拟机并查询 IP
virsh start svr01
virsh domifaddr svr01
```

```bash
# 【终端 2：虚拟机终端】虚拟机重启后重新建立交互式 SSH 会话
ssh -o StrictHostKeyChecking=no cloudstudent@192.168.122.100
```

#### 步骤 3：相同定额写入复测与真实性能比对

在终端 2 内下发与步骤 1 **完全相同、参数一字不差**的 500MB 定额写入指令，观察写穿模式下的吞吐速率与延迟变化趋势。

```bash
# 【终端 1：宿主机终端】采集压测前宿主机块设备统计
virsh domblkstat svr01 vda
```

```bash
# 【终端 2：虚拟机终端】执行与步骤 1 完全相同的定额 I/O 压测
dd if=/dev/zero of=/var/tmp/io-test.dat bs=1M count=500 conv=fdatasync
rm -f /var/tmp/io-test.dat
```

```bash
# 【终端 1：宿主机终端】采集压测后宿主机块设备统计
virsh domblkstat svr01 vda
```

```text
# 终端 2 writethrough 模式下 dd 输出参考
500+0 records in
500+0 records out
524288000 bytes (524 MB, 500 MiB) copied, 3.8215 s, 137 MB/s
```

**实测数据对比与机理剖析**：

* 对比步骤 1 的数据，在 `writethrough` 模式下，`dd` 输出的耗时从约 2.15 秒延长至 3.82 秒，写入吞吐速率从 243 MB/s 下降至 137 MB/s；
* **机理原因**：宿主机内核在维护自身页面缓存的同时还必须等待硬件同步刷盘确认，双重缓存开销与同步落盘锁显著加剧了小数据连续写入的 I/O 延迟。

#### 步骤 4：恢复并固化生产首选配置（cache='none'）

实验验证完毕后，遵循变更管理闭环原则，将虚拟机磁盘缓存恢复为生产标准的 `cache='none'`。

```bash
# 【终端 1：宿主机终端】安全关机
virsh shutdown svr01

# 确认处于关机状态
virsh domstate svr01

# 恢复磁盘缓存模式为 cache='none'
virt-xml svr01 --edit --disk target=vda,driver.cache=none

# 重新启动虚拟机并核验持久化配置
virsh start svr01
virsh dumpxml svr01 --inactive
```

```bash
# 【终端 2：虚拟机终端】重新建立交互终端连接
ssh -o StrictHostKeyChecking=no cloudstudent@192.168.122.100
```

`virsh dumpxml svr01 --inactive` 确认已恢复为 `cache='none'`，系统恢复健康生产配置。

## 四、双侧性能指标体系与 vCPU 绑核单变量调优实战

性能调优是一门基于客观测量数据的实验科学。必须严格遵守\*\*“单变量控制（Single-Variable Control）”\*\*原则：每次实验有且仅改变一个变量，并保持其他一切软硬件条件完全对等。

### 4.1 宿主机与来宾机双侧监控指标对照与时间窗口对齐法则

在实施性能审计时，必须构建覆盖宿主宏观侧与来宾微观侧的对照坐标系，并在**完全重合的时间窗口**内提取数据：

| 监测维度 | 宿主机外侧观测 (Host 视角) | 虚拟机内侧观测 (Guest 视角) | 物理映射本质与深层关系 |
| :--- | :--- | :--- | :--- |
| **CPU 算力与调度** | `virsh domstats svr01 --cpu-total``top``mpstat -P ALL 1` | `top -b -n 1``vmstat 1``mpstat 1` | 宿主测量的是 QEMU 进程所消耗的物理 CPU 时间纳秒数；来宾测量的是时钟中断周期分配与被抢占的 `%st`（Steal Time）。 |
| **物理内存与气球** | `virsh dommemstat svr01``free -h` | `free -m``cat /proc/meminfo` | 宿主反映实际占用的物理 HPA 页（RSS）；来宾反映由气球膨胀调节后的系统可用 GPA 空间。 |
| **持久存储 I/O** | `virsh domblkstat svr01 vda``iostat -xz 1` | `iostat -xz 1``vmstat -d` | 宿主反映物理硬盘承载的真实扇区读写与硬件 `await`；来宾反映 VirtIO 驱动向虚拟控制器提交的逻辑请求量。 |
| **网络二层吞吐** | `virsh domifstat svr01 vnet0``ip -s link show vnet0` | `sar -n DEV 1``ip -s link show enp1s0` | 宿主反映内核虚拟交换网桥（virbr0）的二层转发；来宾反映协议栈处理与 VirtIO 环形队列丢包。 |

**时间窗口对齐法则（Time Window Alignment）**：
严禁将宿主机 15 分钟的 Load Average 与虚拟机内 1 秒钟的 `top` 瞬时峰值进行比较。必须在施加受控定额压力的同一时间段内（例如从开始执行压测到结束的几十秒内），同时在双侧执行采样，以相同的采样频率记录指标均值。

### 4.2 性能读数误区辨析：Load Average 物理意义与单位陷阱

在性能指标解读中，初学者极易陷入以下典型误区：

1. **误区一：将 Load Average 直接等同于 CPU 利用率**
   * **底层机理**：Linux 的 Load Average（平均负载）统计的是处于 **R（Running 可运行态）** 与 **D（Uninterruptible Sleep 不可中断休眠态）** 的任务在 1/5/15 分钟内的加权均值。
   * **真实案例**：一台 4 核宿主机上，如果有 4 个进程在全力跑 CPU 计算，Load Average 为 4.0，此时物理 CPU 利用率接近 100%，所有核心饱和且无队列积压，属于健康高效负载；但如果物理存储硬盘出现故障或严重 I/O 阻塞，导致 30 个进程卡死在等待磁盘读写的 **D 状态**，此时物理 CPU 使用率可能只有 2%，但 Load Average 却会飙高到 30.0。
   * **诊断原则**：见到高 Load 时，必须先看 `vmstat 1` 中的 `b` 列（等待 I/O 的阻塞进程数）。若 `b` 很大且 `%wa`（iowait）飙高，说明是磁盘 I/O 瓶颈，绝非 CPU 算力不足。
2. **误区二：混淆 1000 进制与 1024 进制单位**
   * 硬盘厂商通常使用 1000 进制（$1\text{ KB} = 1000\text{ B}$，$1\text{ MB} = 1000\text{ KB}$）；
   * 操作系统内存与虚拟化工具严格使用 1024 进制（$1\text{ KiB} = 1024\text{ B}$，$1\text{ MiB} = 1024\text{ KiB}$）。在编写 Libvirt 运维命令与脚本时，参数单位必须严格核准。

### 4.3 实训任务四：vCPU 亲和性绑核与单变量受控压测实战

在虚拟机 `svr01` 上部署标准基准测试套件 `sysbench`，执行参数严格一致（2 线程、定额 20000 个素数计算上限）的受控计算负载。首先在**基准工况 A（2 vCPU 默认浮动调度）**下记录双侧指标与实际完成总耗时；随后实施**进阶调优工况 B（vCPU 亲和性绑核）**，验证缓存局部性带来的性能增益；最后实施**孤立单变量对比工况 C（缩减为 1 vCPU）**，观察单核算力瓶颈与耗时倍增，建立完整的量化调优证据链。

#### 步骤 1：在虚拟机内安装并配置标准 sysbench 基准测试套件

在来宾机交互终端内通过系统包管理器安装标准性能基准工具 `sysbench`，并核验虚拟机 CPU 规格。

::: tip 实用操作技巧：实验网络环境与软件包安装指引

* **外网在线安装**：虚拟机已在第 9 课打通了基于 `default` 虚拟网桥的 NAT 外网连接，可直接通过 `apt` 在线拉取并安装 `sysbench`；
* **离线内网安装**：若实验室处于完全隔离的离线内网环境，可直接使用镜像源预置的本地 deb 缓存包（例如 `/var/cache/apt/archives/` 目录下）或通过 `sudo dpkg -i` 快速完成离线安装。
  :::

```bash
# 【终端 2：虚拟机终端】更新软件源并安装 sysbench
sudo apt update && sudo apt install -y sysbench

# 检查 sysbench 版本与 CPU 压测模块
sysbench cpu --version

# 验证虚拟机当前处于 2 vCPU 初始配置
lscpu
```

```text
# sysbench cpu --version 输出参考
sysbench 1.0.20

# lscpu 关键拓扑输出参考
Architecture:             x86_64
CPU(s):                   2
On-line CPU(s) list:      0,1
Model name:               QEMU Virtual CPU version 2.5+
Thread(s) per core:       1
Core(s) per socket:       2
Socket(s):                1
```

`sysbench` 成功就绪；`lscpu` 确认当前处于 2 vCPU 基准双核状态。

**压测参数物理意义解析**：

* `--threads=2`：启动 2 个独立的并发计算工作线程，与虚拟机当前的 2 个 vCPU 核心 1:1 对应；
* `--cpu-max-prime=20000`：指定每个计算事件需寻找计算 1 到 20000 范围内的所有质数，作为恒定不可拆卸的定额计算量，确保多轮测试的计算总量绝对一致。

#### 步骤 2：基准工况 A（2 vCPU 默认浮动调度）受控定额施压与双侧指标记录

在虚拟机内部启动定额 CPU 施压命令；在压测前后，宿主机通过 `virsh domstats` 记录 QEMU 进程消耗的物理 CPU 时间纳秒数，记录虚拟机完成任务的**真实总耗时（Total Time）**、**每秒事件数（Events per second）**与**延迟分布（Latency）**。

```bash
# 【终端 1：宿主机终端】记录工况 A 压测前 svr01 在宿主机侧的初始物理 CPU 消耗纳秒数
virsh domstats svr01 --cpu-total
```

```bash
# 【终端 2：虚拟机终端】发起工况 A (2 vCPU 默认浮动调度) 受控基准压测
sysbench cpu --threads=2 --cpu-max-prime=20000 run
```

```bash
# 【终端 1：宿主机终端】压测完成后，立即采集宿主机侧结算 CPU 消耗指标
virsh domstats svr01 --cpu-total
```

```text
# 终端 2 sysbench 工况 A 输出参考
sysbench 1.0.20 (using system OpenSSL)

Running the test with following options:
Number of threads: 2
Initializing random number generator from current time

Prime numbers limit: 20000

Initializing worker threads...

Threads started!

CPU speed:
    events per second:  1745.32

General statistics:
    total time:                          11.4582s
    total number of events:              20000

Latency (ms):
         min:                                    0.98
         avg:                                    1.14
         max:                                   16.21
         95th percentile:                        1.28
         sum:                                22890.12

Threads fairness:
    events (avg/stddev):           10000.0000/12.00
    execution time (avg/stddev):      11.4451/0.01
```

**工况 A 核心指标深度拆解**：

1. `events per second: 1745.32`：在 2 vCPU 默认调度下，系统每秒可处理约 1745 个素数计算事件；
2. `total time: 11.4582s`：完成 20000 个素数计算定额工作量耗时 11.4582 秒；
3. `Latency (ms)`：平均单次事件延迟为 1.14 毫秒，95 分位延迟为 1.28 毫秒；
4. 宿主机侧 `virsh domstats` 显示 `cpu.time` 增加了约 22.8 秒对应的纳秒数（约 $2 \times \text{耗时}$），证明 2 个 vCPU 线程在宿主机物理核心上得到了并发调度。

#### 步骤 3：进阶调优工况 B：实施 vCPU 亲和性绑核并复测

在默认浮动调度下，宿主机 CFS 会将 2 个 vCPU 线程在宿主机的 4 个物理核心之间轮转迁移，引发 L1/L2 缓存颠簸。现在实施 vCPU 亲和性绑核（CPU Pinning），将 vCPU 0 独占锁定在物理核 0，vCPU 1 独占锁定在物理核 1。

```bash
# 【终端 1：宿主机终端】实施 vCPU 严格亲和性绑核 (vCPU 0 -> pCPU 0, vCPU 1 -> pCPU 1)
virsh vcpupin svr01 0 0
virsh vcpupin svr01 1 1

# 查询并核验当前亲和性掩码与绑定关系
virsh vcpupin svr01

# 查看 vCPU 详细运行状态与当前所在的物理核心
virsh vcpuinfo svr01

# 记录工况 B 压测前宿主机 CPU 纳秒基线
virsh domstats svr01 --cpu-total
```

```text
# virsh vcpupin svr01 输出参考
 VCPU   CPU Affinity
----------------------
 0      0
 1      1

# virsh vcpuinfo svr01 输出参考
VCPU:           0
CPU:            0
State:          running
CPU time:       23.4s
CPU Affinity:   y---

VCPU:           1
CPU:            1
State:          running
CPU time:       22.8s
CPU Affinity:   -y--
```

在终端 2 内重新运行完全相同的 `sysbench` 压测：

```bash
# 【终端 2：虚拟机终端】发起工况 B (2 vCPU 严格绑核) 复测
sysbench cpu --threads=2 --cpu-max-prime=20000 run
```

```bash
# 【终端 1：宿主机终端】采集压测后宿主机 CPU 统计
virsh domstats svr01 --cpu-total
```

```text
# 终端 2 sysbench 工况 B 输出参考
CPU speed:
    events per second:  1912.80

General statistics:
    total time:                          10.4552s
    total number of events:              20000

Latency (ms):
         min:                                    0.92
         avg:                                    1.04
         max:                                    5.41
         95th percentile:                        1.12
```

**绑核调优性能增益深度解析**：

* `total time` 从 11.4582 秒缩减至 10.4552 秒（耗时降低约 8.7%），`events per second` 提升至 1912.80（吞吐量提升约 9.6%）；
* `max latency`（最大毛刺延迟）从 16.21 毫秒大幅压低至 5.41 毫秒；
* **机理原因**：绑核彻底消除了 CFS 跨核调度带来的 L1/L2 高速缓存失效与冷数据重新加载开销，硬件流水线与缓存局部性得到最大化发挥。

#### 步骤 4：孤立单变量对比工况 C：缩减为 1 vCPU 对等负载复测

遵循单变量控制原则，保持虚拟机的内存容量（2048MB）、磁盘镜像、缓存模式与网络拓扑完全不变，**仅将虚拟 CPU 核心数从 2 变更缩减为 1**，观察单核状态下的计算瓶颈。

```bash
# 【终端 1：宿主机终端】安全关机
virsh shutdown svr01

# 确认处于 shut off 状态
virsh domstate svr01

# 调整虚拟机持久化配置为 1 个 vCPU
virsh setvcpus svr01 1 --config

# 重新启动虚拟机并查询 IP
virsh start svr01
virsh domifaddr svr01

# 记录工况 C 压测前宿主机 CPU 纳秒基线
virsh domstats svr01 --cpu-total
```

```bash
# 【终端 2：虚拟机终端】重新建立 SSH 交互连接
ssh -o StrictHostKeyChecking=no cloudstudent@192.168.122.100

# 核验单变量变更生效（CPU 变为 1 核，内存仍为 2048MB）
lscpu
free -m

# 执行与工况 A/B 完全相同的受控基准命令 (保持 2 线程竞争 1 个 vCPU)
sysbench cpu --threads=2 --cpu-max-prime=20000 run
```

```bash
# 【终端 1：宿主机终端】采集压测后的宿主机 CPU 统计
virsh domstats svr01 --cpu-total
```

```text
# 终端 2 sysbench 工况 C 输出参考
CPU speed:
    events per second:  948.15

General statistics:
    total time:                          21.0934s
    total number of events:              20000

Latency (ms):
         min:                                    1.85
         avg:                                    2.10
         max:                                   22.45
         95th percentile:                        2.35
```

**单核瓶颈指标解析**：

* 在 1 vCPU 状态下，2 个计算线程无法并行执行，必须在单个虚拟核心上轮流抢占时间片；
* `total time` 剧烈拉长至 21.0934 秒（相比双核工况接近翻倍），`events per second` 骤降至 948.15，平均延迟翻倍至 2.10 毫秒；
* 宿主机侧 `virsh domstats` 的 `cpu.time` 增量与 `total time` 基本呈 1:1 关系（约 21 秒），清晰证明了单核硬件算力瓶颈。

#### 步骤 5：汇总形成证据化对比分析报告并恢复 2 vCPU 生产配置

将工况 A、工况 B 与工况 C 实测数据整理为标准证据对比表，完成工程决策分析，并将虚拟机恢复并固化为 2 vCPU 生产推荐配置。

::: table title="虚拟化 CPU 单变量调优与多工况性能比对矩阵" align="center" copy="md"
| 指标维度 | 工况 A（2 vCPU 默认浮动） | 工况 B（2 vCPU 绑核调优） | 工况 C（1 vCPU 单核受限） | 物理机理与工程决策分析 |
| :--- | :--- | :--- | :--- | :--- |
| **完成计算总耗时 (Total Time)** | 11.45 s | **10.45 s**（缩短 ~8.7%） | 21.09 s（耗时翻倍） | 绑核锁定 L1/L2 缓存局部性；单核产生严重时间片串行争用。 |
| **每秒事件吞吐量 (EPS)** | 1745.32 ev/s | **1912.80 ev/s**（提升 ~9.6%） | 948.15 ev/s（衰减 ~45.7%） | 证明并发多线程计算高度依赖物理核心并发度。 |
| **平均事件延迟 (Avg Latency)** | 1.14 ms | **1.04 ms** | 2.10 ms | 亲和性绑核显著降低线程调度抖动与跨核延迟。 |
| **最大延迟毛刺 (Max Latency)** | 16.21 ms | **5.41 ms**（下降 ~66.6%） | 22.45 ms | 绑核彻底消除跨物理核迁移时的冷缓存停顿。 |
| **宿主机 CPU 消耗增量 (cpu.time)** | ~22.8 s | ~20.9 s | ~21.1 s | 工况 A/B 充分利用双核并发，工况 C 独占单核串行推进。 |
:::

实验完成后，按照变更管理闭环原则，恢复虚拟机为 2 vCPU 生产推荐配置：

```bash
# 【终端 1：宿主机终端】安全关机并恢复 2 vCPU
virsh shutdown svr01
virsh domstate svr01
virsh setvcpus svr01 2 --config
virsh start svr01

# 检查最终固化的 XML 配置
virsh dumpxml svr01 --inactive
```

`virsh dumpxml svr01 --inactive` 验证 `<vcpu placement='static'>2</vcpu>`，调优全流程完整闭环。

## 五、常见问题排错矩阵

在虚拟化性能运维实战中，面对复杂的性能劣化，请对照下表进行快速定位排查：

| 故障现象 | 优先排查层级 | 定位检查命令 | 根本原因分析 | 规范解决措施 |
| :--- | :--- | :--- | :--- | :--- |
| **CPU Steal Time (%st) 异常飙高 (> 10%)** | 宿主机 CPU 调度与超分层 | `top`（观察 %st）`virsh domstats svr01 --vcpu` | 宿主机超分过度，多个虚拟机的 vCPU 线程在宿主 CFS 就绪队列剧烈排队争用 | 降低虚拟机 vCPU 规格；实施 `virsh vcpupin` 绑核；迁移部分高负载虚拟机至其他计算节点 |
| **宿主机发生 Swap 导致虚机全面假死卡顿** | 宿主机内存管理与水线层 | `free -h`（看 Swap 使用量）`vmstat 1`（看 si/so 换入换出与 b 阻塞） | 宿主机物理内存彻底耗尽，`kswapd` 将 QEMU 进程匿名内存页换出至磁盘 Swap 分区 | 通过 `virsh setmem` 膨胀气球回收虚拟机空闲内存；关闭宿主 Swap；为核心虚机配置静态大页 |
| **执行 virsh setmem 提示气球设备不存在或内存未释放** | 虚拟硬件驱动与配置层 | `virsh dumpxml svr01`虚拟机内 `ls -d /sys/bus/virtio/drivers/virtio_balloon` | 1. 虚机 XML 中未定义 `memballoon` 驱动模型；2. 误判驱动未加载（Ubuntu 默认 Built-in 不会在 `lsmod` 显示）；3. 外部模块系统确实未装载驱动 | 1. 检查 XML 补齐 `<memballoon model='virtio'/>`；2. 检查 `/sys/bus/virtio/drivers/virtio_balloon` 确认内置驱动已常驻；3. 若为外部模块构建则执行 `sudo modprobe virtio_balloon` |
| **虚拟机突发断电后文件系统元数据损坏** | 虚拟存储控制器缓存层 | `virsh dumpxml svr01` | 磁盘缓存错误配置为 `cache='writeback'`，且宿主机掉电时未刷盘的脏页彻底丢失 | 生产环境全面修改为 `cache='none'`（直接 I/O）或配置带电池保护（BBU）的硬件 RAID 卡 |
| **盲目增加 vCPU 后业务吞吐量反而大幅下降** | 多核协同调度与内核自旋锁层 | 虚拟机内 `vmstat 1``top` | 虚拟机多核之间频繁发生跨核自旋锁空转（Spinlock Stall），且持有锁的 vCPU 被切出引发调度等待 | 坚决缩减虚机 vCPU 数量至合理配额（如 8 核降为 2~4 核），匹配真实物理并行能力 |
| **I/O 压测数据异常偏高（达数 GB/s）** | 测试路径与文件系统层 | 虚拟机内 `df -h /tmp` | 测试文件写入了挂载为 `tmpfs` 的 `/tmp` 内存目录，测出的是 RAM 内存拷贝而非磁盘 I/O | 将测试路径更改为根分区真实磁盘目录 `/var/tmp/`，并携带 `conv=fdatasync` 强制落盘屏障 |

## 六、交付与验收标准（极简 4 张必交截图）

完成本课实训后，提交以下 4 张关键证据截图，作为 CPU 拓扑审计、内存气球动态伸缩、磁盘缓存模式实测与单变量调优闭环的验收证明：

1. **截图一：宿主机 CPU 拓扑探针与空闲基线审计截屏**
   * **执行命令**：在宿主机终端执行 `lscpu`、`uptime` 与 `virsh domstats svr01 --cpu-total`。
   * **验证要点**：完整展现物理处理器的核心与线程拓扑，且显示当前宿主机负载均值处于低位空闲状态（load average <= 0.5）。
2. **截图二：VirtIO 内存气球动态伸缩双侧水线核验截屏**
   * **执行命令**：在宿主机终端执行 `virsh setmem svr01 1048576 --live` 并查看 `virsh dommemstat svr01`，同时在虚拟机终端交互执行 `free -m` 显示总内存缩减至 1024MB 档位。
   * **验证要点**：清晰展示宿主机指令与来宾机 `free -m` 的联动响应，证明无需重启即可在线动态回收物理内存。
3. **截图三：QEMU 磁盘缓存模式双侧 I/O 吞吐实测与 domblkstat 截屏**
   * **执行命令**：展示在虚拟机终端执行 500MB `dd` 落盘写入完成输出，并在宿主机终端执行 `virsh domblkstat svr01 vda` 与 `virsh dumpxml svr01`。
   * **验证要点**：展示 `cache='none'` 模式下真实的写请求字节数与写入速率，证明 Direct I/O 有效绕过宿主机页面缓存。
4. **截图四：vCPU 亲和性绑核与单变量变更对比分析与最终配置固化截屏**
   * **执行命令**：展示包含工况 A、工况 B 与工况 C 的对比数据表格，并在宿主机终端执行 `virsh vcpupin svr01` 与 `virsh dumpxml svr01 --inactive`。
   * **验证要点**：展示亲和性绑核掩码以及最终固化回生产黄金配额（2 vCPU）的持久化 XML 配置证据。

## 本章小结

本课带领你穿越了虚拟化性能调优的迷雾，建立了建立在客观物理机理之上的性能度量与调优方法论：

1. **破除了“加配即变快”的唯硬件论误区**：深刻理解了 KVM 虚拟化中 vCPU 线程化本质与宿主机 CFS 调度规律；掌握了 CPU 超分争用、上下文切换开销与 CPU Steal Time（%st）的物理成因，认识到盲目加核反而会导致严重的自旋锁空转与性能雪崩。
2. **掌握了内存二重映射与气球弹性平衡术**：透视了 GVA -> GPA -> HPA 的两维页表寻址路径，实操演练了 VirtIO Balloon 在线膨胀回收与放气释放的全生命周期；确立了“严禁依赖宿主机 Swap 维系超分”的工业级红线。
3. **实测掌握了 QEMU 磁盘四大缓存模式的权衡法则**：系统剖析并实测对比了 `none`、`writethrough`、`writeback` 与 `directsync` 在宿主 PageCache、Direct I/O 与落盘安全性上的底层差异，用实测数据揭示了 Direct I/O 跑分与企业生产首选 `cache='none'` 消除双重缓存与延迟波动的绝对优势。
4. **树立了严谨的单变量证据化科学调优范式**：学会了在完全对等的时间窗口内采集宿主外侧与来宾内侧的双侧指标；掌握了“空闲基线 -> 受控施压 -> 孤立单变量 -> 同负载复测 -> 决策固化”的闭环工程规范，通过 `sysbench` 实测验证了 vCPU 绑核对 L1/L2 缓存局部性的优化价值以及单核并发瓶颈，具备了依据客观证据进行性能容量规划与瓶颈排错的高级实战能力。
