dm-clone¶
引言¶
dm-clone 是一个设备映射器目标,它将现有只读源设备一对一复制到可写目标设备:它呈现一个虚拟块设备,使所有数据立即可见,并相应地重定向读写操作。
dm-clone 的主要用例是将一个可能远程、高延迟、只读的归档型块设备克隆到一个可写、快速、主用型设备中,以实现快速、低延迟的 I/O。克隆设备会立即可见/可挂载,并且源设备到目标设备的复制会在后台与用户 I/O 并行进行。
例如,可以从通过网络存储协议(NBD、光纤通道、iSCSI、AoE 等)访问的只读副本中恢复应用程序备份到本地 SSD 或 NVMe 设备,并立即开始使用该设备,而无需等待恢复完成。
克隆完成后,可以完全移除 dm-clone 表,并替换为(例如)直接映射到目标设备的线性表。
dm-clone 目标复用了精简配置(thin-provisioning)目标所使用的元数据库。
术语表¶
- 数据填充(Hydration)
用源设备相同区域的数据填充目标设备某个区域的过程,即从源设备向目标设备复制该区域。
一旦某个区域被数据填充,我们就会将所有相关的 I/O 重定向到目标设备。
设计¶
子设备¶
该目标通过传入三个设备(以及稍后详述的其他参数)来构建
源设备 - 被克隆的只读设备,也是数据填充的来源。
目标设备 - 数据填充的目标,它将成为源设备的克隆。
一个小型元数据设备 - 它记录目标设备中哪些区域已有效,即哪些区域已完成数据填充,或已通过用户 I/O 直接写入。
目标设备的大小必须至少等于源设备的大小。
区域¶
dm-clone 将源设备和目标设备划分为固定大小的区域。区域是数据填充的单位,即从源设备复制到目标设备的数据的最小量。
首次创建 dm-clone 设备时,区域大小是可配置的。推荐的区域大小与文件系统块大小相同,通常为 4KB。区域大小必须介于 8 个扇区(4KB)和 2097152 个扇区(1GB)之间,并且是 2 的幂。
对已填充区域的读写操作由目标设备提供服务。
对尚未填充区域的读取操作直接由源设备提供服务。
对尚未填充区域的写入操作将被延迟,直到相应的区域已填充并且该区域的数据填充立即开始。
请注意,大小等于区域大小的写入请求将跳过从源设备复制相应区域,直接覆盖目标设备的区域。
丢弃¶
dm-clone 将针对尚未填充范围的丢弃请求解释为跳过该请求所覆盖区域数据填充的提示,即它跳过将区域数据从源设备复制到目标设备,而只更新其元数据。
如果目标设备支持丢弃,则 dm-clone 默认会将其丢弃请求向下传递给目标设备。
后台数据填充¶
dm-clone 不断地将数据从源设备复制到目标设备,直到整个设备被复制完成。
将数据从源设备复制到目标设备会占用带宽。用户可以设置一个节流阀来防止在任何时候发生超过一定量的数据复制。此外,dm-clone 会考虑流向设备的 用户 I/O 流量,并在有 I/O 正在进行时暂停后台数据填充。
消息 hydration_threshold <#regions> 可用于设置正在复制的最大区域数量,默认值为 1 个区域。
dm-clone 使用 dm-kcopyd 将源设备的部分数据复制到目标设备。默认情况下,我们发出的复制请求大小等于区域大小。消息 hydration_batch_size <#regions> 可用于调整这些复制请求的大小。增加数据填充批处理大小会导致 dm-clone 尝试将连续区域批量处理在一起,因此我们以这些区域的批次数量复制数据。
当目标设备的数据填充完成后,将向用户空间发送一个 dm 事件。
更新磁盘元数据¶
每当写入 FLUSH 或 FUA bio 时,磁盘元数据都会被提交。如果没有此类请求,则每秒钟会发生提交。这意味着 dm-clone 设备的行为类似于具有易失性写入缓存的物理磁盘。如果断电,您可能会丢失一些最近的写入。无论发生任何崩溃,元数据都应始终保持一致。
目标接口¶
构造函数¶
clone <metadata dev> <destination dev> <source dev> <region size> [<#feature args> [<feature arg>]* [<#core args> [<core arg>]*]]
元数据设备
存储持久元数据的快速设备
目标设备
目标设备,源将克隆到此设备
源设备
包含要克隆数据的只读设备
区域大小
区域大小(扇区为单位)
特性参数数量
已传递的特性参数数量
特性参数
`no_hydration` 或 `no_discard_passdown`
核心参数数量
传递给 dm-clone 的键/值对对应的偶数个参数
核心参数
传递给 dm-clone 的键/值对,例如 hydration_threshold 256
可选特性参数有
`no_hydration`
创建禁用后台数据填充的 dm-clone 实例
`no_discard_passdown`
禁用将丢弃请求传递给目标设备
可选核心参数有
`hydration_threshold <#regions>`
在后台数据填充期间,在任何给定时间从源设备复制到目标设备的最大区域数量。
`hydration_batch_size <#regions>`
在后台数据填充期间,尝试将连续区域批量处理在一起,以便我们以多个区域为一批从源设备复制数据到目标设备。
状态¶
<metadata block size> <#used metadata blocks>/<#total metadata blocks> <region size> <#hydrated regions>/<#total regions> <#hydrating regions> <#feature args> <feature args>* <#core args> <core args>* <clone metadata mode>
元数据块大小
每个元数据块的固定块大小(扇区为单位)
已使用的元数据块数量
已使用的元数据块数量
元数据块总数
元数据块总数
区域大小
设备的区域大小可配置(扇区为单位)
已填充区域数量
已完成数据填充的区域数量
区域总数
待填充区域总数
正在填充的区域数量
当前正在填充的区域数量
特性参数数量
后续特性参数数量
特性参数
特性参数,例如 no_hydration
核心参数数量
后续核心参数的偶数数量
核心参数
用于调整核心的键/值对,例如 hydration_threshold 256
克隆元数据模式
ro 表示只读,rw 表示读写
在严重情况下,即使只读模式也被认为不安全,则不允许进一步的 I/O,状态将仅包含字符串“Fail”。如果元数据模式发生更改,将向用户空间发送一个 dm 事件。
消息¶
- `disable_hydration`
禁用目标设备的后台数据填充。
- `enable_hydration`
启用目标设备的后台数据填充。
- `hydration_threshold <#regions>`
设置后台数据填充阈值。
- `hydration_batch_size <#regions>`
设置后台数据填充批处理大小。
示例¶
克隆包含文件系统的设备¶
创建 dm-clone 设备。
dmsetup create clone --table "0 1048576000 clone $metadata_dev $dest_dev \ $source_dev 8 1 no_hydration"
挂载设备并修剪文件系统。dm-clone 会解释文件系统发送的丢弃请求,并且不会对未使用的空间进行数据填充。
mount /dev/mapper/clone /mnt/cloned-fs fstrim /mnt/cloned-fs
启用目标设备的后台数据填充。
dmsetup message clone 0 enable_hydration
当数据填充完成后,我们可以用线性表替换 dm-clone 表。
dmsetup suspend clone dmsetup load clone --table "0 1048576000 linear $dest_dev 0" dmsetup resume clone
元数据设备不再需要,可以安全地丢弃或重新用于其他目的。
已知问题¶
我们将对尚未填充区域的读取操作重定向到源设备。如果读取源设备具有高延迟,并且用户反复读取相同区域,则此行为可能会降低性能。我们应该将这些读取作为提示,以更快地填充相关区域。目前,我们依赖页面缓存来缓存这些区域,因此希望我们不会多次从源设备读取它们。
在数据填充完成后,释放核心内存资源,即跟踪哪些区域已填充的位图。
在后台数据填充期间,如果我们未能读取源设备或写入目标设备,我们会打印一条错误消息,但数据填充过程会无限期地继续,直到成功。我们应该在多次失败后停止后台数据填充,并发出一个 dm 事件以供用户空间注意。
为什么不...?¶
在实现 dm-clone 之前,我们探索了以下替代方案
使用 dm-cache,其缓存大小等于源设备,并实现新的克隆策略
生成的缓存设备不是源设备的一对一镜像,因此一旦克隆完成,我们无法移除该缓存设备。
dm-cache 会写入源设备,这违反了我们关于源设备必须被视为只读的要求。
缓存与克隆在语义上有所不同。
使用 dm-snapshot,其 COW 设备等于源设备
dm-snapshot 将其元数据存储在 COW 设备中,因此生成的设备不是源设备的一对一镜像。
没有后台复制机制。
dm-snapshot 需要在待处理异常完成后提交其元数据,以确保快照一致性。在克隆的情况下,我们不需要如此严格,可以像 dm-thin 和 dm-cache 那样,每次写入 FLUSH 或 FUA bio 时,或定期提交元数据。这显著提高了性能。
使用 dm-mirror:镜像目标具有后台复制/镜像机制,但它会写入所有镜像,从而违反了我们关于源设备必须被视为只读的要求。
使用 dm-thin 的外部快照功能。这种方法是所有替代方案中最有前景的,因为精简配置的卷是源设备的一对一镜像,并且处理对未配置/尚未克隆区域的读写方式与 dm-clone 相同。
然而
目前没有后台复制机制,尽管可以实现一个。
最重要的是,我们希望支持任意块设备作为克隆过程的目标,而不是将自己限制在精简配置卷上。精简配置存在固有的元数据开销,用于维护精简卷映射,这会显著降低性能。
此外,克隆设备不应强制使用精简配置。另一方面,如果我们要使用精简配置,我们只需将精简逻辑卷(thin LV)用作 dm-clone 的目标设备。