虚拟机块设备性能瓶颈的优化,核心思路是沿着I/O链路逐层排查,从虚拟化层的驱动模式、缓存策略,到宿主机存储后端、队列深度,每一步都有对应的调优手段。多数人一遇到虚拟机磁盘慢,第一反应是换更贵的 SSD,其实在硬件不变的情况下,通过调整 virtio 驱动参数和存储后端配置,往往就能带来肉眼可见的改善。
定位瓶颈:I/O 路径上最容易被忽视的挡路石
虚拟机读写数据,走的是一条完整链条,从虚拟机内部的应用程序发起 I/O,经过 guest 内核的文件系统、块设备层,穿过虚拟化层(KVM、Xen 或 Hyper-V 的驱动),到达宿主机内核的存储栈,最后才落到底层物理磁盘,任何一个环节出现短板,都会让整个链路的性能拉胯。
排查瓶颈至少看两个层面:
- 宿主机层面:物理磁盘本身是否达到极限,RAID 卡缓存策略是否合适,存储网络带宽是否充足。
- 虚拟机层面:磁盘驱动类型、I/O 调度器、队列深度、缓存模式,这些配置是否匹配当前负载。
行业共识认为,大部分“虚拟机磁盘慢”的问题出在虚拟化层配置不当,而非底层硬件不济,先用 iostat、fio、esxtop(VMware 环境)或 virsh 命令分别测宿主机和虚拟机的 I/O,如果宿主机吞吐量远高于虚拟机,那瓶颈就在虚拟机配置上。
虚拟机磁盘性能怎么测:别靠感觉,用数据说话
fio 是 Linux 下最常用的基准测试工具,测虚拟机磁盘性能时,推荐用 --direct=1 绕过 page cache,得到最真实的块设备性能,一个典型的顺序读测试命令:
fio --name=seqread --rw=read --bs=1m --size=8g --iodepth=32 --direct=1 --numjobs=4
测试项至少覆盖四类:顺序读、顺序写、随机读、随机写,只用随机读判断全局是常见的误区,部分存储特别是机械盘阵列,顺序性能其实不差,测完才有对比依据。
- IOPS:每秒读写次数,小数据块随机场景下看这个。
- 带宽:每秒传输字节数,大数据块顺序场景下看这个。
- 延迟:每次 I/O 的等待时间,数据库类交互场景最敏感。
表格对比一下不同负载类型的关注参数:
| 负载类型 | 典型块大小 | 关键指标 |
典型业务 |
|---|---|---|---|
| 随机读写 | 4K-16K | IOPS、延迟 | 数据库 OLTP |
| 顺序读写 | 64K-1M | 带宽 | 日志归档、数据仓库 |
| 混合读写 | 8K-32K | 延迟稳定性 | 文件服务器、邮件系统 |
虚拟机io性能慢怎么办:逐项检查驱动和策略
KVM 虚拟机的磁盘控制器常见有三类:IDE、virtio-blk 和 virtio-scsi,IDE 是模拟老式硬盘的,性能惨不忍睹,仅用于老系统的兼容,virtio-blk 是半虚拟化驱动,跳过了大部分设备模拟步骤,让虚拟机的 I/O 请求直接进入宿主机的 virtio 队列,性能比 IDE 高出一个量级,virtio-scsi 比 virtio-blk 多了一层 SCSI 协议支持,能承载更多设备数量和高级 SCSI 特性,适合对设备管理有专门要求的场景。
切换方案:在虚拟机关机状态下执行 virsh edit,将 controller 或 disk bus 属性改为 virtio(部分发行版需安装 virtio-win 或 virtio-drivers 包),切换后虚拟机内出现 /dev/vda 这类设备名,属于正常现象。
虚拟机 XML 配置中 cache 参数有几种选择:
- writeback:数据先进入宿主机页缓存,立即认为写成功,性能最好,但宿主机断电时可能丢数据。
- none(direct I/O):绕过页缓存,直接写后端存储,性能中等,安全性和一致性最好。
- writethrough:每次写都同步落到宿主机存储,最慢但最安全。
数据库虚拟机通常选 none 或搭配宿主机 NVMe 盘选择 writeback 并加 UPS 保障,Web 服务器等对一致性不高的场景可用 writeback 提升响应速度。
Linux 内核 I/O 调度器对真实性能的影响:
- none(noop 的现代替代):适合高并发 NVMe SSD 和大多数虚拟化场景,由存储设备自身处理排序。
- mq-deadline:适合机械盘和混合负载,能有效降低延迟波动。
- bfq:适合桌面系统,能保证响应公平性,但吞吐量略低。
大多数云环境建议直接选 none,把排序工作交给后端阵列或 SSD 控制器更高效。
云服务器磁盘性能对比:本地盘与网络盘取舍
部署虚拟机的底层存储方案不同,优化手段也完全不同,这里把常见的两类拉出来对比:
| 维度 | 本地盘(直通) | 网络存储(分布式或 SAN) |
|---|---|---|
| 典型延迟 | 低,约几十微秒 | 中,几百微秒到几毫秒,取决于网络 |
| 吞吐量 | 物理盘自身极限 | 受网络带宽和存储节点 CPU 限制 |
| 迁移支持 | 不支持批量热迁移 | 支持在线迁移,存储可共享 |
| 典型故障场景 | 宿主机宕机后数据恢复耗时 | 网络抖动引发 I/O 延迟尖刺 |
本地盘直通适合对延迟极为敏感、但能接受迁移代价的场景,比如高性能缓存服务、实时推荐引擎,网络存储适合需要漂移高可用和弹性扩容的业务,Kubernetes 集群的块存储卷,选型时别只看顺序读的峰值带宽,随机写延迟才是多数业务的实际痛点。
多队列与 CPU 绑核:榨干 NVMe 时代的最后潜力
一块 NVMe 物理盘能提供几百万 IOPS 和几十队列深度,虚拟化的 virtio 驱动如果只开一个队列,单核 CPU 处理中断就会成为瓶颈,KVM 的 virio-blk 多队列(multi-queue) 让虚拟机的每个虚拟 CPU 都有独立的 I/O 队列,分布在不同物理核上,大幅降低锁竞争。
开启方式:
- 宿主机检查
ls /sys/module/virtio_blk/parameters/,确认内核编译时启用了virtio_blk多队列支持。 - 虚拟机 XML 的 disk 标签增加
queues='4'或virtio-blk驱动iothread配置(qemu 4.2+ 支持)。 - vCPU 的 iothread 使用
taskset绑定至非业务核心,避免与中断处理抢 CPU。
开启后观察 iostat -x 的 %util 和 avgqu-sz,如果仍有队列堆积,继续增加队列数量或升级 virtio 驱动版本。
不同负载场景与混合读写优化
数据库的每一条 SQL 都可能关联多次随机 I/O,延迟是首要指标,推荐的优化组合:
- 使用 virtio-scsi 驱动,配合
discard开启 TRIM,保持 SSD 长期性能稳定。 - 缓存策略选
none,配合宿主机全局的
写合并策略(如文件系统挂载参数nobarrier,仅限非 JBD2 逻辑卷)。 - 为虚拟机单独分配独立物理 SSD 或 LUN 分区,避免与其他虚拟机争抢。
短时间内清理数据量大的场景(如临时表批量插入),可临时将缓存改为 writeback,处理完再切回,但需明确接受短时间断电风险。
这类场景更关注顺序读写带宽和并发用户数量,将虚拟机镜像放在由多块 NVMe 组 RAID 后格式化为 xfs(块大小 4k,sunit/swidth 对齐条带大小),文件缓存内存占比调到足够高,减少宿主机级重复页面回收。
网络层的衔接同样值得关注,使用 Open vSwitch 或物理 RDMA 网卡为存储后端提高网络吞吐,据业内的多次测试报告,virtio-net 的多队列开启后,文件读写延迟平均降幅能达到 20% 以上(此数据基于常见云厂商公开的 fio 测试基准)。
监控和长期维护:再好的调优也会被写入放大拖垮
调优不是一锤子买卖,虚拟机跑了几个月后,块设备性能可能悄悄衰退,原因往往是 SSD 的保留空间耗尽或写入放大因子升高,此时要做的:
- 宿主机定期跟踪
fstrim的回收效率,确保discard在虚拟化栈里全程启用。 - 用
blkparse和btt分析 I/O 的分布,看看是不是某些实例造成了存储热点。 - 对数据重要但性能不敏感的冷备实例,主动降低 IOPS 上限,给热实例让路,利用存储 QoS 避免群居效应。
虚拟机块设备性能瓶颈优化常见问题
开启 writeback 缓存会坏数据吗?
writeback 模式性能确实最激进,但宿主机突然断电或者内核崩溃,虚拟机内已经确认写入但尚未落地的数据就可能丢失,核心奥义在于,它牺牲了“持久化的那一刻”,换来了速度,没有 UPS 断电保护和宿主机 RAID 缓存保护的场景,建议选 none。
kvm 虚拟机的磁盘性能为什么时而好时而差?
有可能是宿主机上其他“吵闹的邻居”在使坏,查看宿主机的 iostat -x 1,注意磁盘的 util 和 aqu-sz 是否周期性打满,如果是,需要给虚拟机开启存储 QoS 设置池级或卷级的上限,还有就是存储网络链路质量,如果走的是 iSCSI 或 NFS,测试一下 ping 的延迟,抖动持续超过 5ms 就意味着底层共享存储扛不住了,迁移虚拟机到低负载宿主机或者扩展节点是现实的选择。
virtio-blk 和 virtio-scsi,生产环境选哪个?
隔离性要求高、Windows 虚拟机带多个盘且需要热拔插的,virtio-scsi 更合适;简单高效的 Linux 虚拟机单盘启动,virtio-blk 就能应付,两者的性能差异在大多数场景下可以忽略,真正拉开差距的仍然是后端存储物理层的能力。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/645555.html




