外观
磁盘 IO 调优:IO thread、缓存模式与 aio
创建第一个虚拟机里已经给过磁盘选项的入门建议:IO thread 勾上、Cache 保持默认。这篇把这几个选项拆开讲清楚它们各自做什么、什么时候值得改,以及缓存模式里最容易踩的坑。
先确认瓶颈真的在磁盘 IO
sh
# 宿主机 shell
iostat -x 2 5看 %util 是否接近 100%、await 是否明显偏高。如果磁盘本身不忙,问题通常在别处:客户机没装 VirtIO 驱动(还在用模拟 IDE/SATA)、存储池写满导致性能骤降、多个备份任务同时在跑。这些排除之后,才轮到这篇的参数。
收益排序:先做这几件事
- 磁盘总线用 SCSI,控制器选 VirtIO SCSI single。 这是模拟设备和半虚拟化设备的差距,数量级的影响,比下面任何一个参数都重要。
- 客户机确认装了 VirtIO 驱动(Linux 内置,Windows 需要装 virtio-win 驱动包)。
- 存储池别写太满。 ZFS 超过约 80% 使用率后写入性能明显下降,LVM-Thin 写满会直接导致客户机写入失败。
- 做完以上三条,再看 IO thread、cache、aio 这些细项——它们的收益比前三条小得多。
IO thread:把磁盘 IO 挪出主线程
默认情况下,虚拟机的磁盘 IO 由 QEMU 的主线程或 vCPU 线程处理,和 CPU 计算抢时间片。勾选 IO thread 后,QEMU 为这块磁盘单独开一个线程处理 IO,主线程和 vCPU 不再被磁盘操作阻塞。
| 适用条件 | 代价 |
|---|---|
| 磁盘总线是 SCSI 且控制器是 VirtIO SCSI single,或者总线是 VirtIO Block | 几乎没有代价,官方从 PVE 7.3 起对新建 Linux 虚拟机默认勾选 |
为什么必须搭配 VirtIO SCSI single
用共享控制器(VirtIO SCSI,不带 single)时,一个控制器上挂的所有磁盘共用一个 IO 线程,IO thread 的隔离效果打折扣。VirtIO SCSI single 给每块盘一个独立控制器,IO thread 才能真正做到磁盘之间互不阻塞。
aio:宿主机怎么发起磁盘 IO 请求
aio 决定 QEMU 向宿主机内核提交 IO 请求用哪种机制,在磁盘的高级选项里设置。
| 值 | 说明 | 适用条件 |
|---|---|---|
io_uring | 较新的 Linux 异步 IO 接口,开销更低 | PVE 9 默认使用,内核较新(PVE 8/9 的默认内核都支持) |
native | Linux AIO(较老的异步 IO 接口) | 部分存储后端或旧内核的兼容选项 |
threads | 用线程池模拟异步 IO | 兼容性最好,某些存储类型(如挂载的网络文件系统)只能用这个 |
大多数情况下保持 PVE 给出的默认值即可。只有在排查具体的 IO 延迟或吞吐问题、并且已经通过基准测试对比过时,才考虑手动切换,而且要在测试环境先验证客户机能正常启动——不是所有存储后端都支持每一种 aio 模式。
缓存模式:这是唯一需要认真读警示块的一项
cache 决定虚拟机的写入什么时候被认为"完成",以及宿主机页缓存参与到什么程度。
| 模式 | 宿主机页缓存 | 写入语义 | 掉电风险 |
|---|---|---|---|
| No cache(默认) | 不使用宿主机缓存,直连存储 | 客户机收到"写完成"时,数据已经交给了后端存储的写入路径 | 低,是官方推荐的默认平衡点 |
| Writethrough | 使用宿主机读缓存 | 每次写入都等待落盘确认才返回 | 低,但性能通常不如 No cache |
| Writeback | 使用宿主机读写缓存 | 客户机收到"写完成"时,数据可能还停留在宿主机内存里没有落盘 | 高 |
| Directsync | 不使用宿主机缓存 | 类似 Writethrough,但绕过宿主机页缓存直接同步 | 低,性能通常最差 |
| Unsafe | 使用宿主机读写缓存,且忽略客户机的同步请求 | 客户机主动要求"把这批数据刷盘"时,宿主机可能直接忽略 | 极高 |
writeback 和 unsafe 用性能换掉电时的数据完整性
Writeback 和 Unsafe 更快的原理是:告诉客户机"写完成了",但数据实际还在宿主机内存的缓存里,稍后才真正落盘。如果这时候宿主机异常断电、内核崩溃或被强制关机(而不是正常关机),这部分还没落盘的数据会丢失——对客户机来说,这表现为文件系统损坏、数据库文件不一致,轻则某些最近的写入消失,重则整个文件系统需要修复甚至无法挂载。
Unsafe 更进一步:它会忽略客户机文件系统主动发出的刷盘请求(fsync/flush),这些请求本来是数据库、日志类应用用来确保关键数据落盘的最后手段,在 Unsafe 模式下也不可靠。它的合理使用场景几乎只有:一次性的、坏了可以重新生成的场景(例如从头批量转换镜像格式的临时虚拟机),不要在保存真实数据的虚拟机上使用。
影响范围:整块虚拟机磁盘上未落盘的写入,无法预知具体丢失哪些数据。 前置条件:已经有对应的 UPS 并验证过自动关机脚本有效,或者能接受这块盘上数据在异常断电时損坏。 回退方式:把 Cache 改回 No cache,虚拟机需要重启一次生效;如果已经发生过异常断电,回退之前应先检查客户机文件系统(fsck 或对应文件系统的检查工具)。
改缓存模式:虚拟机 → Hardware → 硬盘 → Edit → Cache,或者宿主机 shell:
sh
# <vmid> 是虚拟机编号,<diskbus><n> 例如 scsi0
qm set <vmid> --scsi0 <storage>:vm-<vmid>-disk-0,cache=writebackdiscard 和 SSD 仿真不属于缓存问题
Discard(把客户机删除文件后的 TRIM 请求传给底层存储,依赖精简置备存储和支持 TRIM 的客户机)和 SSD emulation(让客户机认为自己在 SSD 上)都不影响掉电时的数据完整性,和 cache 模式是两回事,可以按需单独开启。
检查结果
sh
# 宿主机:确认配置已生效
qm config <vmid> | grep -E 'scsi|virtio|ide'输出里能看到 cache=、aio=、iothread=1 等字段(未显示 cache= 表示使用默认的 No cache)。
用基准测试(见基准测试方法)在改动前后各跑一次,对比延迟和吞吐——没有对比数据,不知道改动到底有没有用。
常见问题
改了 cache 之后没生效。 磁盘的 cache/aio/iothread 是 QEMU 进程启动时决定的,需要关机再开机(Shutdown 后 Start),重启操作系统(Reboot)不够。
用了 Writeback,现在担心已有虚拟机的数据完整性。 先确认这段时间宿主机没有发生过异常断电或崩溃。如果发生过,进入客户机检查文件系统状态,必要时从备份恢复而不是相信"看起来还能用"。
某些存储类型改 aio 后虚拟机起不来。 换回默认值,不是所有 aio 模式都被所有存储后端支持,尤其是网络文件系统类存储。