注意
本文档适用于 Ceph 开发版本。
Ceph MDS锁
为什么使用锁?
MDS 中的锁定基础设施(显然)是为了保护各种元数据的状态。MDS 拥有不同类型的锁,覆盖了 inode 和 dentry 的不同部分。此外,MDS 使用不同类型的锁,因为不同的元数据(inode 和 dentry)在不同的情况下有不同的行为。MDS 缓存分布在多个 MDS 等级和所有客户端上。锁定基础设施用于确保所有等级和客户端对文件系统的视图一致。
由 MDS 管理的数据可以非常大,以至于实际上整个数据集都存储在单个元数据服务器的内存中。这也导致了一个单点故障。因此,MDS 拥有分布式子树分区的概念。目录树可以分成更小的子树。这是通过记录目录树中每个节点的热度(访问频率)来完成的。当一个子树的热度达到配置的阈值时,MDS 通过拆分目录片段来划分子树。每个片段负责原始目录的一部分,但是这些片段将有一个单一的主权节点。每个 MDS 都可以处理分片后的读写请求。如果一个文件被非常频繁地访问,MDS 将生成多个副本,分布在活跃的 MDS 上,以满足并发 I/O。通常,有多个客户端读取和写入文件。MDS 为相关的元数据定义锁定规则,例如很少并发修改的元数据,如 inode 的 UID/GID,一个共享读取和排他性写入的规则就足够了。然而,目录的统计信息可能需要多个客户端同时更新。这个大目录可能已经被分成(拆分)成多个分片,不同的客户端可以写入不同的分片。这些分片可以共享读取,并支持同时写入。
因此,除了覆盖不同 inode 元数据的不同锁类型之外,MDS 还具有锁类,用于定义特定锁类型的访问规则。锁类型和类将在本文档中进一步解释。
锁类型
MDS 定义了一些与 inode 或 dentry 的不同元数据相关的锁类型。保护 inode 和 dentry 元数据的锁类型如下:
CEPH_LOCK_DN - dentry
CEPH_LOCK_DVERSION - dentry version
CEPH_LOCK_IQUIESCE - inode quiesce lock (a type of superlock)
CEPH_LOCK_IVERSION - inode version
CEPH_LOCK_IAUTH - mode, uid, gid
CEPH_LOCK_ILINK - nlink
CEPH_LOCK_IDFT - dirfragtree, frags
CEPH_LOCK_IFILE - mtime, atime, size, truncate_seq, truncate_size, client_ranges, inline_data
CEPH_LOCK_INEST - rstats
CEPH_LOCK_IXATTR - xattrs
CEPH_LOCK_ISNAP - snaps
CEPH_LOCK_IFLOCK - file locks
CEPH_LOCK_IPOLICY - layout, quota, export_pin, ephemeral_*
Note
修改时的锁定规则ctime略有不同 - 要么在versionlock下,要么在没有任何特定锁的情况下(即,它可以在持有其他锁的情况下被修改,例如,在CEPH_LOCK_IAUTH).
锁类
下修改(say)uid/gid
LocalLock - Used for data that does not require distributed locking such as inode or dentry version information. Local locks are versioned locks.
SimpleLock - Used for data that requires shared read and mutually exclusive write. This lock class is also the base class for other lock classes and specifies most of the locking behaviour for implementing distributed locks.
ScatterLock - Used for data that requires shared read and shared write. Typical use is where an MDS can delegate some authority to other MDS replicas, e.g., replica MDSs can satisfy read capabilities for clients.
Note
此外,MDS 定义了 FileLock,它是 ScatterLock 的一个特殊情况,用于需要共享读取和共享写入的数据,但也用于保护需要共享读取和互斥写入的其他元数据。
锁类型的分类如下:
SimpleLock
CEPH_LOCK_DN
CEPH_LOCK_IAUTH
CEPH_LOCK_ILINK
CEPH_LOCK_IXATTR
CEPH_LOCK_ISNAP
CEPH_LOCK_IFLOCK
CEPH_LOCK_IPOLICY
ScatterLock
CEPH_LOCK_INEST
CEPH_LOCK_IDFT
FileLock
CEPH_LOCK_IFILE
LocalLock
CEPH_LOCK_DVERSION
CEPH_LOCK_IVERSION
读写和独占锁
锁可以以三种模式获取:
rdlock - shared read lock
wrlock - shared write lock
xlock - exclusive lock
rdlock和xlock是不言自明的。
wrlock是特殊的,因为它允许并发写入者,并且对ScatterLock和FileLock类有效。从上一节可以看出INEST和IDFT是ScatterLock类中设置了wrlock允许多个写入者同时写入,例如,当一个(大)目录被拆分成多个分片(在拆分后),并且每个分片被“分配”给一个活跃的 MDS。在这些目录下创建新文件时,递归统计将在活跃的 MDS 上独立更新。稍后,为了获取更新后的统计信息,将在 inode 的授权 MDS(auth MDS)上聚合(收集)“分散”的数据;这通常发生在请求此锁类型的rdlock时。
Note
MDS 还定义了remote_wrlock,它主要用于重命名操作期间,当目标 dentry 位于与源 MDS 不同的(活跃的)MDS 上时。
锁状态和锁状态机
MDS 定义了各种锁状态(定义在src/mds/locks.h源文件中)。并非所有锁状态对给定的锁类都有效。每个锁类定义自己的锁转换规则,并组织为锁状态机。锁状态 (LOCK_*) 本身不是锁,而是控制是否允许获取锁。每个状态遵循LOCK_<STATE>或LOCK_<FROM_STATE>_<TO_STATE>命名术语,可以总结如下:
LOCK_SYNC - anybody (ANY) can read lock, no one can write lock and exclusive lock
LOCK_LOCK - no one can read lock, only primary (AUTH) mds can write lock or exclusive lock
LOCK_MIX - anybody (ANY) can write lock, no one can read lock or exclusive lock
LOCK_XLOCK - someone (client) is holding a exclusive lock
锁转换表(部分)使用以下概念:
ANY - Auth or Replica MDS
AUTH - Auth MDS
XCL - Auth MDS or Exclusive client
其他锁状态(如LOCK_XSYN, LOCK_TSYN等)是额外的状态,定义为客户行为(LOCK_XSYN允许客户端保留缓冲的写入,并且不会将其刷新到 OSD,并暂时暂停写入)的优化。
中间锁状态 (LOCK_<FROM_STATE>_<TO_STATE>) 表示锁从一个状态 (<FROM_STATE>) 转换到另一个状态 (<TO_STATE>).
每个锁类定义自己的锁状态机,可以在src/mds/locks.c源文件中找到。状态机在讨论下文中的锁转换时解释。
锁转换
锁从一个状态转换到另一个状态主要是由于(客户端)请求或 MDS 正在经历的变化,例如树迁移。让我们考虑两个客户端的简单情况:一个客户端执行stat() (getattr()或lookup()来获取 inode 的 UID/GID,另一个客户端执行setattr()来更改同一个 inode 的 UID/GID。第一个客户端(很可能)拥有As(iauth shared) caps 由 MDS 发送给它。现在,当另一个客户端执行setattr()调用 MDS 时,MDS 向 inode 的xlock注意,MDS 为此 inode 添加了其他许多锁,但在此我们只关注 IAUTH。现在,authlock (CEPH_LOCK_IAUTH):
Server::handle_client_setattr()
if (mask & (CEPH_SETATTR_MODE|CEPH_SETATTR_UID|CEPH_SETATTR_GID|CEPH_SETATTR_BTIME|CEPH_SETATTR_KILL_SGUID))
lov.add_xlock(&cur->authlock);
添加一个CEPH_LOCK_IAUTH。例如,要将新卷的MDS守护进程放置在标记为SimpleLock类,其锁转换状态机是:
// stable loner rep state r rp rd wr fwr l x caps,other
[LOCK_SYNC] = { 0, false, LOCK_SYNC, ANY, 0, ANY, 0, 0, ANY, 0, CEPH_CAP_GSHARED,0,0,CEPH_CAP_GSHARED },
[LOCK_LOCK_SYNC] = { LOCK_SYNC, false, LOCK_LOCK, AUTH, XCL, XCL, 0, 0, XCL, 0, 0,0,0,0 },
[LOCK_EXCL_SYNC] = { LOCK_SYNC, true, LOCK_LOCK, 0, 0, 0, 0, XCL, 0, 0, 0,CEPH_CAP_GSHARED,0,0 },
[LOCK_SNAP_SYNC] = { LOCK_SYNC, false, LOCK_LOCK, 0, 0, 0, 0, AUTH,0, 0, 0,0,0,0 },
[LOCK_LOCK] = { 0, false, LOCK_LOCK, AUTH, 0, REQ, 0, 0, 0, 0, 0,0,0,0 },
[LOCK_SYNC_LOCK] = { LOCK_LOCK, false, LOCK_LOCK, ANY, 0, 0, 0, 0, 0, 0, 0,0,0,0 },
[LOCK_EXCL_LOCK] = { LOCK_LOCK, false, LOCK_LOCK, 0, 0, 0, 0, XCL, 0, 0, 0,0,0,0 },
[LOCK_PREXLOCK] = { LOCK_LOCK, false, LOCK_LOCK, 0, XCL, 0, 0, 0, 0, ANY, 0,0,0,0 },
[LOCK_XLOCK] = { LOCK_SYNC, false, LOCK_LOCK, 0, XCL, 0, 0, 0, 0, 0, 0,0,0,0 },
[LOCK_XLOCKDONE] = { LOCK_SYNC, false, LOCK_LOCK, XCL, XCL, XCL, 0, 0, XCL, 0, 0,0,CEPH_CAP_GSHARED,0 },
[LOCK_LOCK_XLOCK]= { LOCK_PREXLOCK,false,LOCK_LOCK,0, XCL, 0, 0, 0, 0, XCL, 0,0,0,0 },
[LOCK_EXCL] = { 0, true, LOCK_LOCK, 0, 0, REQ, XCL, 0, 0, 0, 0,CEPH_CAP_GEXCL|CEPH_CAP_GSHARED,0,0 },
[LOCK_SYNC_EXCL] = { LOCK_EXCL, true, LOCK_LOCK, ANY, 0, 0, 0, 0, 0, 0, 0,CEPH_CAP_GSHARED,0,0 },
[LOCK_LOCK_EXCL] = { LOCK_EXCL, false, LOCK_LOCK, AUTH, 0, 0, 0, 0, 0, 0, CEPH_CAP_GSHARED,0,0,0 },
[LOCK_REMOTEXLOCK]={ LOCK_LOCK, false, LOCK_LOCK, 0, 0, 0, 0, 0, 0, 0, 0,0,0,0 },
状态转换条目是sm_state_tfromsrc/mds/locks.h源文件中的类型。TODO:详细描述这些。
我们达到一个 MDS 填充LockOpVec并调用Locker::acquire_locks(),根据锁类型和模式rdlock等)尝试获取该特定锁。锁的起始状态是LOCK_SYNC(这可能不总是这样,但为了简单起见,考虑这种情况)。为了获取xlockforiauth,MDS 查阅状态转换表。如果当前状态允许获取锁,MDS 将获取锁(这只是增加一个计数器)。当前状态LOCK_SYNC不允许xlock获取(列x in LOCK_SYNC状态),因此需要切换锁状态。此时,MDS 切换到中间状态LOCK_SYNC_LOCK- 表示从LOCK_SYNCtoLOCK_LOCK状态转换。中间状态有几个目的 - a. 中间状态定义了客户端允许持有的 caps,从而撤销在此状态下不允许持有的 caps,以及 b. 防止获取新的锁。此时,MDS 向客户端发送 cap 撤销消息:
2021-11-22T07:18:20.040-0500 7fa66a3bd700 7 mds.0.locker: issue_caps allowed=pLsXsFscrl, xlocker allowed=pLsXsFscrl on [inode 0x10000000003 [2,head] /testfile auth v142 ap=1 DIRTYPARENT s=0 n(v0 rc2021-11-22T06:21:45.015746-0500 1=1+0) (iauth sync->lock) (iversion lock) caps={94134=pAsLsXsFscr/-@1,94138=pLsXsFscr/-@1} | request=1 lock=1 caps=1 dirtyparent=1 dirty=1 authpin=1 0x5633ffdac000]
2021-11-22T07:18:20.040-0500 7fa66a3bd700 20 mds.0.locker: client.94134 pending pAsLsXsFscr allowed pLsXsFscrl wanted -
2021-11-22T07:18:20.040-0500 7fa66a3bd700 7 mds.0.locker: sending MClientCaps to client.94134 seq 2 new pending pLsXsFscr was pAsLsXsFscr
如上所示,客户端.94134拥有Ascaps,这些 caps 正在被 MDS 撤销。撤销 caps 后,MDS 可以继续转换到进一步的状态:LOCK_SYNC_LOCKtoLOCK_LOCK. 由于目标是获取xlock,状态转换继续(根据锁转换状态机):
LOCK_LOCK -> LOCK_LOCK_XLOCK
LOCK_LOCK_XLOCK -> LOCK_PREXLOCK
LOCK_PREXLOCK -> LOCK_XLOCK
最后,获取xlockoniauth.
TODO:解释锁定顺序和路径遍历锁定。
由 Ceph 基金会带给您
Ceph 文档是一个社区资源,由非盈利的 Ceph 基金会资助和托管Ceph Foundation. 如果您想支持这一点和我们的其他工作,请考虑加入现在加入.