使用 Btrfs 作为虚拟磁盘文件的底层存储
(2022-05-12 更新)
将虚拟磁盘文件存储在 Btrfs 文件系统中具有几个优点:
- Btrfs 的数据透明压缩功能可以节省硬盘空间;
- Btrfs 的 reflink(共享数据块) 和 subvolume snapshot(子卷快照)可用于给虚拟磁盘创建快照;
- Btrfs 的 Copy-on-Write (CoW) transaction 机制和可保证虚拟磁盘文件的一致性,在大量随机写入时可以免除频繁的硬盘 flush 操作。
Btrfs 透明压缩 compress
挂载 Btrfs 文件系统时使用 compress=zstd
或 compress-force=zstd
(使用 zstd
压缩算法)。
compress
: 自动判断是否压缩数据(写入数据时,尝试压缩最开头的数据,如果不能压缩,放弃压缩后续数据)
compress-force
: 始终压缩数据(尝试压缩每个数据块)
Btrfs 目前支持三种压缩算法:lzo
, zlib
, zstd
。
- 压缩速度:
lzo
>zstd
>>zlib
- 解压速度:
lzo
>zstd
>>>zlib
- 压缩率:
lzo
<<zstd
<zlib
这里使用 zstd
压缩算法,它具有与 lzo
相似的速度但压缩率更高。
创建虚拟磁盘文件
以使用 libvirt/QEMU 为例,可通过下列方法之一创建虚拟磁盘文件:
- 使用 virt-manager 图形界面,在对应的 storage pool 中创建格式为
raw
的虚拟磁盘文件,注意不要勾选 "Allocate entire volume now"(立刻分配所有空间)。 - 或者使用命令行
truncate -s 20G disk.img
直接创建指定大小的稀疏文件。
使用稀疏的 raw 文件,将 "thin provisioning"、透明压缩以及一致性保持的工作交给 Btrfs 处理。
QEMU 磁盘缓冲策略 cache=unsafe
添加虚拟机的磁盘设备后,可以将虚拟磁盘设备的 cache 属性设置为 unsafe 以获得最佳的写入性能。
注意:需要为 btrfs 添加 flushoncommit
挂载选项(见 man btrfs)。
在 unsafe 设置下,QEMU 将会忽略来自 guest OS 的所有 flush 请求,所有数据将在 Btrfs 文件系统进行提交/检查点(commit/checkpoint)操作时保存,若 guest OS 产生大量随机写入操作,由于 Btrfs 的 CoW 机制,可以被转化为几次连续写入,以此提高写入性能。
但要注意的是,虽然 Btrfs 保证整个文件系统的一致性,但在使用 cache=unsafe
时,如果 guest OS 是某个跨机器系统的一部分,那么这个系统一致性是不受保证的。比如下列场景:
- guest OS 接收了来自远端的信息
- guest OS 将信息 M 保存到硬盘(保存过程中通过几次 flush 安全地完成数据库事务)
- 保存完成之后,guest OS 通知远端,使远端将信息副本删除
这段时间中,任何时刻的断电都不会导致 guest OS 中文件系统或数据库的损坏(假设它们都正确地实现了事务),机器重启后数据库的状态为这两者之一:不存在信息 M 或 存在信息 M。但是整个系统可能会存在这个状态:guest OS 不存在那条消息,同时远端的信息已删除。
最终效果
在该虚拟磁盘上安装了 Windows Server 2019 (with desktop experience) 并添加了几个服务(甚至通过嵌套虚拟化技术,在虚拟机中启用了 Hyper-V,又安装了一个 Windows 7 在内)。
可以通过 compsize
工具查看文件/目录的压缩情况:
$ sudo compsize win2k19.img
Processed 1 file, 182915 regular extents (192542 refs), 0 inline.
Type Perc Disk Usage Uncompressed Referenced
TOTAL 51% 11G 21G 20G
none 100% 4.4G 4.4G 4.0G
zstd 38% 6.7G 17G 16G
如上,该虚拟磁盘文件原始数据大小为 20G,其中 16G 被 zstd 算法压缩到 6.7G,最终文件占用的物理大小为 11G,整体压缩率为 51%,即节省了一半的空间。