AIGC标识 qcow2增量卷和全量卷实现原理

结论

qcow2 全量卷是数据自包含的独立镜像;增量卷(overlay)通过头部 backing_file 指向一个只读基础卷,自身只为"被写过的 cluster"分配物理空间,未写过的部分运行时回退到 base 读,写时按 cluster 粒度 COW 把整簇拉到 overlay 再改。overlay 不是字节级差异盘,而是"整簇副本"盘。

基准:QEMU qemu-img 7.x / qcow2 v3 / Linux KVM。


一、两种卷怎么创建

全量卷

# 10G 独立 qcow2,thin provisioning,物理空间随写入增长
qemu-img create -f qcow2 base.qcow2 10G

# backing file 行为 "No backing file"
qemu-img info base.qcow2

没有任何外部依赖,可独立启动 VM。

增量卷

# 基于 base.qcow2 建一个只存差异的 overlay
# -b 指定 backing file,-F 指定 backing file 的格式
qemu-img create -f qcow2 -b base.qcow2 -F qcow2 overlay.qcow2

# 查看整条 backing chain(base <- overlay)
qemu-img info --backing-chain overlay.qcow2

# 看每个逻辑簇当前落在哪个文件(forward=overlay, backing=base)
qemu-img map overlay.qcow2

overlay.qcow2 本身只有几百 K(存 header + 空 L1/L2 表)。启动 VM 时实际打开的是 overlay,QEMU 自动把 base 挂成只读。base 不能删、不能移动路径,否则 overlay 起不来。

可多层叠加:base <- snap1 <- snap2 <- live,就是 KubeVirt/libvirt 快照和 PVC 克隆的底层机制。


二、内部结构

每个 qcow2 文件都是三层索引:header → L1 表 → L2 表 → data cluster(默认 64K)。

  • header 里有 backing_file_offset/size,存基础卷路径(相对或绝对)。
  • L1 entry → 一个 L2 表的偏移;L2 entry → 一个 data cluster 的偏移。
  • L2 entry == 0 表示这个簇在本文件未分配。

qcow2 全量卷与增量卷结构及 COW 读写流程


三、读流程

给定一个逻辑扇区 offset:

  1. 查 overlay 的 L1 → L2;
  2. L2 entry 非 0 → 直接从 overlay 的 data cluster 读;
  3. L2 entry == 0 → 沿 backing_file 跳到 base,再走一遍 base 的 L1 → L2,从 base 的 cluster 读。

读过的扇区不自动复制到 overlay。 这是 overlay 文件能保持很小的关键。


四、写流程(COW 的关键)

  1. 查 overlay 的 L1 → L2;
  2. 该簇已在 overlay 分配 → 直接覆盖写;
  3. 未分配(L2 entry == 0):
    • 从 base 把整个 64K cluster 读出来;
    • 在 overlay 新分配一个 cluster,把读出的整簇写进去;
    • 在 overlay 的 L2 表登记新簇偏移;
    • 用本次新数据覆盖这个新簇。
  4. base 里的原数据不动。

按 cluster(不是按扇区)COW,是因为 L2 表一项只能对应一个簇,做不到"同簇一半指向 base、一半指向 overlay"。整簇拉过来是为了避免部分写造成数据撕裂。


五、"只存被改过的 cluster"这句话要怎么理解

这句话有歧义,容易被误读成 bsdiff 那种字节级差异。实际机制是:

  • overlay 只为"被写过的簇"分配物理空间,没写过的不分配、回退 base;
  • 但一旦某个簇被写过哪怕 1 个扇区,整个 64K 簇都会被复制到 overlay,然后再用新数据覆盖。

overlay 里存的是"整簇副本",不是"差异块"。一个簇里 99% 的内容可能和 base 一模一样,但它已经在 overlay 里占了 64K。

由此带来两个实际影响:

  • overlay 会膨胀:VM 随机写多了(哪怕每个簇只写 1 个扇区),overlay 物理大小会很快接近 base。qemu-img info 看的 virtual size 是 10G,du -h overlay.qcow2 看到的 actual size 才是真占的空间。
  • 首次写有读放大:每个簇第一次写都要先从 base 读 64K、再写到 overlay(一次读 + 一次写)。

六、启动 VM 用全量卷还是增量卷

选型看场景,没有绝对优劣:

场景 推荐 原因
单台长期跑、对性能敏感 全量卷 没有 backing chain,读不穿透、写无 COW 开销
从同一个母盘起 N 台 VM(云桌面、CI、弹性实例) 增量卷(overlay) 共享 base,秒级启动、省空间
母盘是公版/企业标准镜像 增量卷 base 只读,VM 改不到母盘
需要频繁回滚 增量卷 + 快照 切回 base 或切 L1 指针,秒级完成

简单原则:一台 VM 用全量卷性能最好;多台 VM 共享一个母盘用 overlay 省空间。base 一旦作为 backing file 就保持只读,不要在上面直接写。


七、如何实现快照

qcow2 快照分两种,和 overlay、clone 的关系先理清:clone / overlay 是磁盘组织方式,snapshot 是"拍一个时间点"这个动作。外部快照的实现本质就是"转 base + 建 overlay"。

内部快照(internal)

快照存在同一个 qcow2 文件内部,不生成新文件:

qemu-img snapshot -c before-install disk.qcow2   # 创建
qemu-img snapshot -l disk.qcow2                   # 列表
qemu-img snapshot -a before-install disk.qcow2    # 回滚
qemu-img snapshot -d before-install disk.qcow2    # 删除

原理:qcow2 文件头的 snapshot table 里只记一个"该时刻的 L1 表指针",回滚就是把当前 L1 指针切过去,不复制数据,所以创建快、占空间小。但所有快照堆在一个文件里,快照多了性能下降,生产不推荐。

外部快照(external,= overlay)

拍快照那一刻做三件事:

  1. 当前可写盘转成只读 base(它就是那个时间点);
  2. 新建空的 snap.qcow2,backing_file 指向旧盘;
  3. VM 继续写,全部落到 snap.qcow2。
拍快照前:  VM ──写──> disk.qcow2(可写)

拍快照后:  disk.qcow2(只读,时间点)
            VM ──写──> snap.qcow2(backing → disk.qcow2)

libvirt / KubeVirt 的快照默认走外部快照,性能好、base 和快照解耦。

三种方式对比

clone(完整克隆) 内部快照 外部快照(overlay)
新建文件 完整拷贝一份 不建文件 空 overlay
创建速度 慢(全量拷贝) 快(只记元数据) 快(只建空 overlay)
空间占用 双倍 几乎为零 只有差异
能否回滚 不能(两份独立) 切 L1 指针 切回 base 即可
生产推荐 简单隔离场景 不推荐 推荐

八、常用运维命令

目的 命令 作用
增量合并回 base qemu-img commit overlay.qcow2 把 overlay 的整簇差异写回 base,之后 overlay 可删
切换 base qemu-img rebase -b new-base.qcow2 overlay.qcow2 base 升级/换盘后让 overlay 指向新 base
压成独立全量 qemu-img convert -O qcow2 overlay.qcow2 full.qcow2 整条 chain 拍平成一个自包含镜像
链完整性检查 qemu-img check overlay.qcow2 验证 backing file 可达、无损坏
内部快照 qemu-img snapshot -c name overlay.qcow2 qcow2 内置 A/B/C 状态(不推荐,性能差)

九、cluster size 调参

创建时可以改 cluster size(默认 64K):

# 大块:元数据少、顺序写好,但 COW 浪费大(写 1 个扇区也要拉 2M)
qemu-img create -f qcow2 -o cluster_size=2M base.qcow2 10G

# 小块:COW 浪费少,但 L2 表变大、元数据 IO 增多
qemu-img create -f qcow2 -o cluster_size=32K base.qcow2 10G

大块适合顺序写、数据库类负载;小块适合随机小块写、桌面类负载。


十、libvirt / KubeVirt 里的挂载方式

<disk type='file' device='disk'>
  <driver name='qemu' type='qcow2'/>
  <source file='/var/lib/libvirt/images/overlay.qcow2'/>
  <backingStore type='file' index='1'>
    <source file='/var/lib/libvirt/images/base.qcow2'/>
    <format type='qcow2'/>
  </backingStore>
</disk>

KubeVirt 给 VM 配磁盘有两种典型方式:差分盘(overlay)是把基础镜像作为只读 base,每个 VM 一个 overlay PVC,只写差异,VM 销毁只删 overlay、base 不动;完整克隆(clone)则是把基础镜像整个拷贝一份给新 VM,VM 拿到独立副本,改的是自己那份,不影响 base。

posted on 2026-10-25 07:20  王景迁  阅读(6)  评论(0)    收藏  举报

导航