注意

本文档适用于 Ceph 的开发版本。

MDS 静默协议

MDS 静默协议是一种用于“静默”(安静)文件系统中的树,停止所有写入(有时也包括读取)I/O 的机制。

此 API 的目的是防止多个客户端在最终一致的快照边界上交错读取和写入,其中客户端之间存在带外通信。这种通信可能导致客户端错误地认为它们已经达到了一个可以通过快照相互恢复的检查点。

注意

这是关于 MDS 中用于静默文件树的底层机制的文档。更高层次的 QuiesceDb 是客户端实现静默的预期 API。

机制

MDS 使用一个新的 quiesce_path 内部请求来静默 I/O,该请求获取树根的适当锁,然后为锁定树中的其他 inode 启动一系列子请求。获取的锁将强制客户端释放 caps 并完成正在进行的客户端/MDS 请求。

启动的子请求是 quiesce_inode 内部请求。这些请求将获取“cap 相关”锁,这些锁控制 capability 状态,包括 filelockauthlocklinklockxattrlock。此外,还会获取新的本地锁 quiescelock。有关该锁的更多信息,请参阅下一节。

非 cap 相关的锁会被跳过,因为它们不控制典型且持久的元数据状态。此外,只有 Capabilities 才能授予客户端对文件的元数据或数据的本地控制权。

一旦所有锁都已获取,cap 相关的锁就会被释放,并且依赖 quiescelock 来防止向客户端颁发 cap 相关锁的 Capabilities。这主要由 CInode:get_caps_* 方法控制。释放这些锁是必要的,以便允许具有复制 inode 的其他 rank 静默,而不会因锁状态转换导致死锁。例如,客户端想要 inode 上的 Xx 将触发 xattrlockLOCK_SYNC 状态转换为 LOCK_SYNC_EXCL。该状态将不允许另一个 rank 获取 xattrlock 进行读取,从而造成死锁,但受静默超时/过期限制。(静默必须等到所有 rank 都静默该树才能完成。)

最后,如果 inode 是一个目录,quiesce_inode 操作会遍历所有目录片段并为任何子 inode 发出新的 quiesce_inode 请求。

Inode Quiescelock

quiescelock 是用于支持静默 I/O 的 inode 新本地锁。它是一种超级锁,其中需要对“cap 相关”inode 锁进行 wrlock 或 xlock 的每个客户端或 MDS 操作也将隐式获取 quiescelock 上的 wrlock。

注意

本地锁支持多个写入者和一个独占锁。没有读取锁。

在 MDS 的正常操作期间,quiescelock 永远不会被持有,除非用于写入。然而,当一个子树被静默时,quiesce_inode 内部操作将独占持有 quiescelock,持续整个 quiesce_inode 操作的生命周期。这将阻止获取任何其他 cap 相关的 inode 锁。quiescelock 必须排在所有其他锁之前(参见 src/include/ceph_fs.h 中的排序),才能充当这个超级锁。

使用这个 quiescelock 的一个主要原因是防止客户端请求在获取 quiesce_inode 持有的锁(例如 filelockquiescelock)时被阻塞,同时仍然持有在正常路径遍历期间获取的锁。值得注意的是,重要的锁是在请求的根 inode(filepath 结构中的 ino)的所有父节点上通过 Locker::try_rdlock_snap_layout 获取的 snaplockpolicylock。如果该操作在持有这些锁的情况下等待,那么将来在根 inode 上进行 mksnap 将是不可能的。

注意

mksnap RPC 只会获取要创建快照的 inode 的 snaplock 上的 wrlock(写入锁)。

quiescelock 帮助防止这种情况发生的方式是,当获取 cap 相关锁的 wrlock 或 xlock 时,它是第一个强制获取的锁。此外,当无法获取时还有特殊处理:操作持有的所有锁都会被释放,操作等待 quiescelock 可用。该锁是强制性的,因为对 Locker::acquire_locks 的调用(带有 cap 相关锁的 wrlock/xlock)将自动包含(添加)quiescelock

因此,预期的正常流程是,像 mkdir 这样的操作将执行其路径遍历,获取父节点和 dentry 锁,然后尝试获取父 inode 上创建 dentry 所必需的锁。操作将无法获取自动包含的 quiescelock 上的 wrlock,将自身添加到 quiescelock 等待列表,然后释放所有持有的锁。

查找和导出

静默一棵树会导致树下每个 inode 的多个 quiesce_inode 操作。这些操作的共享生命周期与父 quiesce_path 操作相关联。因此,一旦操作完成静默(但未完成并释放锁),操作将保持锁被持有的状态,并且不监视树的状态。这意味着我们需要处理导入新元数据的情况。

如果通过目录 lookupreaddir 获取 inode,MDS 将检查其父节点是否已静默(即父目录的 quiescelock 是否被 xlocked?)。如果是,MDS 将立即发出并分派针对该 inode 的 quiesce_inode 操作。由于它是一个新的 inode,该操作将立即成功并防止向客户端颁发不适当的 capabilities。

第二种情况是处理从另一个 rank 导入子树。这很有问题,因为子树导入的 inode 可能具有不适当的状态,从而使声称“已静默”的树的保证失效。为了避免这种情况,如果导入器 MDS 遇到一个已静默的目录 inode,它将跳过对导入的根 inode 的发现。如果跳过,该 rank 将向导出器发送 NAK 消息,这将中止导出。

由 Ceph 基金会为您呈现

Ceph 文档是由非营利性 Ceph 基金会 资助和托管的社区资源。如果您希望支持这项工作和我们的其他努力,请考虑 立即加入