注意
本文档适用于 Ceph 的开发版本。
快照
概述
Rados 支持两种相关的快照机制:
池快照(pool snaps):快照隐式应用于池中的所有对象
自管理快照(self managed snaps):用户必须在每次写入时提供当前的 SnapContext。
这两种机制是互斥的,一个特定的池只能使用其中一种。
SnapContext 是为对象当前定义的一组快照,以及为排序目的从 mon 请求的最新快照(seq)(具有较新的 seq 的 SnapContext 被认为是更新的)。
从 OSD 的角度来看,池快照 和 自管理快照 之间的区别在于 SnapContext 是通过客户端的 MOSDOp 到达 OSD,还是通过最新的 OSDMap 到达 OSD。
有关详细信息,请参阅 manifest.rst。
磁盘上的结构
PG 集合中的每个对象都有一个 head 对象,并且可能有一组 clone 对象。每个 hobject_t 都有一个 snap 字段。对于 head(对象唯一可写的版本),snap 字段设置为 CEPH_NOSNAP。对于 clones,snap 字段设置为创建时 SnapContext 的 seq。当 OSD 处理写入时,它首先检查最新的 clone 是否使用早于 SnapContext 中表示的最新快照的 snapid 标记。如果是,则在写入时间和上次克隆时间之间至少发生了一次快照。因此,在执行突变之前,OSD 会创建一个新的克隆,用于处理上次克隆的 snapid 和最新 snapid 之间的快照读取。
head 对象包含编码在属性中的 SnapSet,用于跟踪:
为对象定义的快照的完整集合
当前存在的所有克隆的完整集合
用于跟踪空间使用的克隆之间的重叠区间
克隆大小
当仍有克隆存在时,head 无法被删除。相反,它被标记为白化(object_info_t::FLAG_WHITEOUT)以容纳其中包含的 SnapSet。在这种情况下,head 对象在逻辑上不再存在。
参阅:should_whiteout()
此外,每个克隆上的 object_info_t 都包含一个向量,其中包含定义该克隆的快照。
快照移除
要删除快照,需要向 Monitor 集群发出请求,将快照 ID 添加到已清除快照列表(或者在 池快照 的情况下将其从池快照集中删除)。无论哪种情况,PG 都会将快照添加到其 snap_trimq 以进行修剪。
当其所有快照都被移除时,克隆可以被移除。为了确定在移除快照时可能需要移除哪些克隆,我们使用 SnapMapper 维护从快照到 hobject_t 的映射。
参阅 PrimaryLogPG::SnapTrimmer, SnapMapper
当 PG 清洁且未进行擦洗时,此修剪由 snap_trim_wq 异步执行。
选择 PG::snap_trimq 中的下一个快照进行修剪
我们从 PG::snap_mapper 中确定下一个要修剪的对象。对于每个对象,我们创建一个日志条目并 repop 更新对象信息和快照集(包括调整重叠)。如果对象是不再属于任何活动快照的克隆,则在此处将其移除。(参阅 PrimaryLogPG::trim_object(),当 new_snaps 为空时。)
我们还使用对象的新快照本地更新我们的 SnapMapper 实例。
包含对象修改的日志条目还包含新的快照集,副本使用该快照集更新其自己的 SnapMapper 实例。
主节点与副本共享信息,副本将新的 purged_snaps 集与其余信息一起持久化。
恢复
因为修剪操作是使用 repops 和日志条目实现的,所以正常的 PG 对等和恢复会维护快照修剪器操作,但需要注意的是,推送和移除操作需要更新本地 SnapMapper 实例。如果 purged_snaps 更新丢失,我们只会重新修剪一个现在为空的快照。
SnapMapper
SnapMapper 是在 map_cacher<string, bufferlist> 之上实现的,后者通过文件系统等后端存储提供接口,支持异步事务。当事务未完成时,map_cacher 实例会缓冲不稳定的键,从而实现一致访问,而无需刷新 filestore。SnapMapper 提供两个映射:
hobject_t -> set<snapid_t>:存储每个克隆对象的快照集
snapid_t -> hobject_t:存储具有该快照作为其快照之一的 hobjects 集
假设:有许多 hobjects 和相对较少的快照。第一个编码将对象的字符串化作为键,将快照集的编码作为值。第二个映射,因为一个快照可能有许多 hobjects,被存储为形式为 stringify(snap)_stringify(object) 的键集合,其中 stringify(snap) 是固定长度。这些键具有 bufferlist 编码对<snapid, hobject_t> 作为值。因此,创建或修剪单个对象不涉及读取任何快照的所有对象。此外,在构造时,SnapMapper 被提供一个掩码,用于过滤属于该 PG 的单个 SnapMapper 键空间中的对象。
拆分
snapid_t -> hobject_t 键条目被安排成这样:对于任何 PG,最多需要检查 8 个前缀才能确定特定 PG 中特定快照中的所有 hobjects。拆分时,父节点上要检查的前缀会进行调整,以便只显示保留在 PG 中的对象。子节点将立即拥有正确的映射。
clone_overlap
附加到 head 对象的每个 SnapSet 都包含克隆对象之间的重叠区间,以优化空间。重叠区间存储在 clone_overlap 映射中,映射中的每个元素都存储快照 ID 和与下一个最新克隆的相应重叠。
参阅以下使用 4 字节对象的示例
object |
内容 |
|---|---|
head |
[AAAA] |
listsnaps 输出如下
cloneid |
snaps |
大小 |
overlap |
|---|---|---|---|
head |
4 |
在拍摄快照(ID 1)并重新写入对象的前 2 个字节后,创建的克隆将在其最后 2 个字节与新的 head 对象重叠。
object |
内容 |
|---|---|
head |
[BBAA] |
克隆 ID 1 |
[AAAA] |
cloneid |
snaps |
大小 |
overlap |
|---|---|---|---|
1 |
1 |
4 |
[2~2] |
head |
4 |
通过拍摄另一个快照(ID 2),这次只重新写入对象的前 1 个字节,创建的克隆(ID 2)将在其最后 3 个字节与新的 head 对象重叠。而最旧的克隆(ID 1)将在其最后 2 个字节与最新的克隆重叠。
object |
内容 |
|---|---|
head |
[CBAA] |
克隆 ID 2 |
[BBAA] |
克隆 ID 1 |
[AAAA] |
cloneid |
snaps |
大小 |
overlap |
|---|---|---|---|
1 |
1 |
4 |
[2~2] |
2 |
2 |
4 |
[1~3] |
head |
4 |
如果通过重新写入 4 个字节完全重新写入 head 对象,唯一保留的重叠将是两个克隆之间的重叠。
object |
内容 |
|---|---|
head |
[DDDD] |
克隆 ID 2 |
[BBAA] |
克隆 ID 1 |
[AAAA] |
cloneid |
snaps |
大小 |
overlap |
|---|---|---|---|
1 |
1 |
4 |
[2~2] |
2 |
2 |
4 |
|
head |
4 |
最后,在移除最后一个快照(ID 2)并且 snaptrim 启动后,将不再有重叠区间
object |
内容 |
|---|---|
head |
[DDDD] |
克隆 ID 1 |
[AAAA] |
cloneid |
snaps |
大小 |
overlap |
|---|---|---|---|
1 |
1 |
4 |
|
head |
4 |