KVM 存储性能调优:qcow2 vs raw、IO 缓存模式、virtio 驱动对比
深入讲解 KVM 虚拟机存储性能优化:qcow2 与 raw 格式性能对比与选型、cache=none/writeback/writethrough 缓存模式分析、virtio-blk vs virtio-scsi vs IDE 驱动性能差异、IO 多队列(iothread)配置,以及数据库场景的最优存储配置实践。
虚拟机存储性能直接影响数据库、日志系统等 IO 密集型应用。KVM 存储链路涉及多个层次:磁盘格式(qcow2/raw)→ 缓存模式(cache=)→ IO 驱动(virtio/IDE)→ 物理磁盘。每一层都有配置选项影响性能和数据安全性。
存储链路概览
VM 内应用写数据
↓
Guest OS IO(virtio 驱动接收 IO 请求)
↓
QEMU IO 层(缓存模式 cache= 控制)
↓
宿主机文件系统(ext4/XFS 的 page cache)
↓
磁盘格式层(qcow2 元数据查找 / raw 直接映射)
↓
物理磁盘(SSD/HDD)
优化关键:
- 减少中间环节(raw > qcow2)
- 用 virtio 驱动(比 IDE/SATA 快 3-5倍)
- 缓存模式匹配业务(数据库用 none,普通应用用 writeback)
- IO 多队列(大并发 IO 场景)
一、磁盘格式:qcow2 vs raw
格式对比
raw(原始格式):
优点:
- 接近裸盘性能(无格式开销)
- 简单,可直接 dd 操作
缺点:
- 预分配全部空间(100GB 镜像立即占用 100GB)
- 不支持快照、压缩、加密
适合:高性能数据库服务器
qcow2(QEMU Copy-On-Write v2):
优点:
- 精简分配(用多少占多少)
- 支持快照(可回滚)
- 支持加密和压缩
- 支持热添加/删除磁盘
缺点:
- 有格式元数据开销(随机 IO 性能略低)
- 快照多时性能下降明显
适合:开发环境、桌面虚拟化、有快照需求的场景
性能测试对比
# 安装测试工具(在宿主机上)
apt install fio
# 创建测试镜像
# raw 格式
qemu-img create -f raw /tmp/test-raw.img 10G
# qcow2 格式
qemu-img create -f qcow2 /tmp/test-qcow2.img 10G
# 测试顺序读(raw vs qcow2)
# raw
fio --name=seq-read --rw=read --bs=1M --size=4G \
--filename=/tmp/test-raw.img --direct=1 --iodepth=32 --numjobs=1
# qcow2 无法直接用 fio 测,需要通过 VM 内部测试
# 启动 VM 后,在 VM 内部:
fio --name=rand-write --rw=randwrite --bs=4k --size=2G \
--filename=/dev/vda --direct=1 --iodepth=128 --numjobs=4 \
--runtime=60 --time_based
# 典型结果(参考值):
# raw + cache=none:IOPS ~15000(接近物理磁盘)
# qcow2 + cache=writeback:IOPS ~12000(-20%)
# qcow2 + cache=none:IOPS ~8000(有格式开销)
格式转换
# qcow2 转 raw(提升性能,迁移到高 IO 需求 VM)
qemu-img convert -f qcow2 -O raw \
source.qcow2 destination.raw
# raw 转 qcow2(需要快照功能)
qemu-img convert -f raw -O qcow2 \
-o compression_type=zlib \
source.raw destination.qcow2
# 查看镜像信息
qemu-img info myvm.qcow2
# image: myvm.qcow2
# file format: qcow2
# virtual size: 100 GiB (107374182400 bytes)
# disk size: 25.3 GiB ← 实际占用空间
# cluster_size: 65536
# Snapshot list:
# ID TAG VM SIZE DATE
# 1 snap1 1.5 GiB 2026-07-10
# qcow2 碎片整理(快照多时性能下降后)
qemu-img convert -f qcow2 -O qcow2 -c myvm.qcow2 myvm-defrag.qcow2
二、IO 缓存模式
缓存模式控制 QEMU 如何处理写入:立即写到磁盘还是先缓存?
cache=none(直接 IO):
- Guest 写入 → 直接到物理磁盘(绕过宿主机 page cache)
- 优点:安全(断电不丢数据)、读性能稳定
- 缺点:写性能相对低(无缓存加速)
- 适合:数据库(MySQL/PostgreSQL)
cache=writeback(回写缓存):
- Guest 写入 → 宿主机 page cache → 稍后写到磁盘
- 优点:写性能最好
- 缺点:宿主机断电可能丢数据(数据还在内存)
- 适合:开发环境、有快照保护的场景
cache=writethrough(直写缓存):
- Guest 写入 → 同时写到磁盘和 page cache
- 优点:安全,读有缓存加速
- 缺点:写性能低(等待磁盘确认)
- 适合:对安全性要求高但读多写少的场景
cache=unsafe:
- 最激进的缓存(QEMU 可能忽略 fsync)
- 仅测试环境使用,生产禁用
配置缓存模式
<!-- virsh edit 修改磁盘缓存 -->
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2' cache='none'/> <!-- 修改这里 -->
<source file='/var/lib/libvirt/images/myvm.qcow2'/>
<target dev='vda' bus='virtio'/>
</disk>
# 命令行创建 VM 时指定
virt-install \
--name myvm \
--disk path=/var/lib/libvirt/images/myvm.qcow2,cache=none,bus=virtio \
...
# 临时修改(需重启 VM)
virsh detach-disk myvm vda --persistent
virsh attach-disk myvm /var/lib/libvirt/images/myvm.qcow2 vda \
--driver qemu --subdriver qcow2 --cache none \
--targetbus virtio --persistent
三、virtio 驱动性能对比
三种驱动对比
IDE(emulation):
- 模拟真实 IDE 控制器(完全软件仿真)
- 性能最差:每次 IO 都要陷入 KVM,再交给 QEMU 模拟
- 兼容性最好(Windows XP 都支持)
- 不推荐在生产使用
virtio-blk:
- 半虚拟化驱动(Guest 知道自己在虚拟化环境)
- 直接通过 virtio 协议通信,减少模拟开销
- 单队列设计,高并发 IO 场景有瓶颈
- 推荐:一般 Linux VM
virtio-scsi:
- 实现 SCSI 命令集(比 virtio-blk 更完整)
- 支持多队列(每个 vCPU 一个 IO 队列)
- 支持热插拔磁盘
- 支持 DISCARD(通知虚拟机内的 trim/unmap)
- 推荐:数据库 VM、多磁盘 VM、需要热插拔的场景
配置 virtio-scsi
<!-- virsh edit:使用 virtio-scsi 控制器 -->
<controller type='scsi' model='virtio-scsi' index='0'>
<driver queues='4'/> <!-- 队列数 = vCPU 数 -->
</controller>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2' cache='none' io='native'/>
<source file='/var/lib/libvirt/images/myvm.qcow2'/>
<target dev='sda' bus='scsi'/> <!-- 注意 bus='scsi' -->
<address type='drive' controller='0' bus='0' target='0' unit='0'/>
</disk>
# 在 VM 内确认驱动类型
lsblk -d -o NAME,ROTA,SCHED,MODEL
# NAME ROTA SCHED MODEL
# vda 0 none QEMU HARDDISK ← virtio-blk
# sda 0 none QEMU HARDDISK ← virtio-scsi
# 查看 IO 队列深度
cat /sys/block/vda/queue/nr_requests
# 256
cat /sys/block/sda/queue/nr_requests
# 256
四、IO 多队列(iothread)
对于高并发 IO 场景,使用 iothread 把 IO 处理移到独立线程:
<!-- virsh edit:启用 iothread -->
<!-- 1. 定义 iothread -->
<iothreads>2</iothreads>
<!-- 2. 在 emulator 中绑定 iothread 到 CPU -->
<cputune>
<vcpupin vcpu='0' cpuset='2'/>
<vcpupin vcpu='1' cpuset='3'/>
<iothreadpin iothread='1' cpuset='4'/> <!-- iothread 1 绑定到 CPU 4 -->
<iothreadpin iothread='2' cpuset='5'/> <!-- iothread 2 绑定到 CPU 5 -->
</cputune>
<!-- 3. 磁盘绑定 iothread -->
<disk type='file' device='disk'>
<driver name='qemu' type='raw' cache='none' io='native' iothread='1'/>
<source file='/var/lib/libvirt/images/myvm.raw'/>
<target dev='vda' bus='virtio'/>
</disk>
五、数据库场景最优配置
<!-- MySQL/PostgreSQL 生产 VM 存储配置 -->
<domain>
<iothreads>2</iothreads>
<devices>
<!-- 数据磁盘:raw + cache=none + iothread -->
<disk type='file' device='disk'>
<driver name='qemu' type='raw' cache='none' io='native' iothread='1'/>
<source file='/var/lib/libvirt/images/db-data.raw'/>
<target dev='vda' bus='virtio'/>
</disk>
<!-- 日志磁盘(分开,避免 IO 竞争)-->
<disk type='file' device='disk'>
<driver name='qemu' type='raw' cache='none' io='native' iothread='2'/>
<source file='/var/lib/libvirt/images/db-log.raw'/>
<target dev='vdb' bus='virtio'/>
</disk>
<!-- 系统盘:qcow2 + writeback(系统盘性能不关键,需要快照)-->
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2' cache='writeback'/>
<source file='/var/lib/libvirt/images/db-os.qcow2'/>
<target dev='vdc' bus='virtio'/>
</disk>
</devices>
</domain>
# VM 内 MySQL 配置(配合宿主机存储优化)
# /etc/mysql/mysql.conf.d/mysqld.cnf
# InnoDB 独立于 OS 缓存(宿主机已用 cache=none)
innodb_flush_method = O_DIRECT
# 减少 fsync 频率(我们已通过 iothread 提升 IO 性能)
innodb_flush_log_at_trx_commit = 1 # 数据安全优先保持 1
# 调大 buffer pool(减少磁盘读取)
innodb_buffer_pool_size = 8G
# 关闭二进制日志(如果是只读复制节点)
# skip-log-bin
性能基准测试
# 在 VM 内部执行全面 IO 基准测试
# 安装
apt install fio
# 1. 顺序读(大文件读取场景)
fio --name=seq-read \
--filename=/dev/vda \
--rw=read --bs=1M \
--size=4G --direct=1 \
--iodepth=16 --numjobs=1 \
--runtime=30 --time_based \
--output-format=json | \
python3 -c "import json,sys; d=json.load(sys.stdin); print(f'BW: {d[\"jobs\"][0][\"read\"][\"bw_bytes\"]/1024/1024:.1f} MB/s')"
# 2. 随机写 4K(数据库写日志场景)
fio --name=rand-write-4k \
--filename=/dev/vda \
--rw=randwrite --bs=4k \
--size=2G --direct=1 \
--iodepth=128 --numjobs=4 \
--runtime=60 --time_based
# 3. 混合读写(OLTP 场景)
fio --name=mixed-rw \
--filename=/dev/vda \
--rw=randrw --rwmixread=70 \
--bs=4k --size=2G --direct=1 \
--iodepth=64 --numjobs=4 \
--runtime=60 --time_based
小结
KVM 存储性能优化按优先级:驱动选型 > 缓存模式 > 磁盘格式。首先用 virtio-blk 或 virtio-scsi 替换 IDE(性能提升 3-5 倍);然后根据业务选缓存模式(数据库 cache=none,普通应用 cache=writeback);最后按需选格式(高性能数据库用 raw,需要快照管理用 qcow2)。高并发 IO 场景加上 iothread + CPU Pinning 可以进一步减少 IO 延迟抖动。
