坚持思考,就会很酷。
大家好,这是进入大厂面试准备 第2篇文章,
”走暗路、耕瘦田、进窄门、见微光” 告诉我 面试关键就 项目 这个才是最考察基本功的地方。
阅读本文你获得如何收益
在传统分布式文件系统(GFS)中,元数据管理集集中存储,一旦元数据服务器故障也会导致整个文件系统不可用。
画外音:为什么采用单Master存储
设计原则
CephFS 的设计初衷就是要打破这一限制,通过将元数据与数据路径分离, 让客户端直接通过 CRUSH 算法访问 OSD 存储, 而由独立的 MDS 集群专注于元数据管理, 来源:Ceph: A Scalable, High-Performance Distributed File System
画外音:
在CephFS中,元数据(如文件/目录的权限、大小、路径结构等)由多个MDS节点共同管理。
以下场景会导致数据不一致或性能问题:
通过上面描述 你猜测到 分布式锁是这样一个锁
扮演功能
画外音:
高度抽象,业务参与很少。
MDS中锁状态自动评估和变更由以下因素综合驱动:
来源:Ceph: A Scalable, High-Performance Distributed File System
画外音:
扩展思考:为什么单节点这么慢,读写差不多?
ceph 分布式锁机制通过结合
这种设计特别适合包括高
参考:
目的:
不同的元数据(inode 和 dentry 中的)在不同情况下的行为不同,MDS 会使用不同类型的锁
锁类定义了处理分布式锁所需的相关锁类型的锁定行为。MDS 定义了4个锁类:
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.
定义:
特点:
CEPH_LOCK_DN - 目录项锁 CEPH_LOCK_IAUTH - inode权限锁 CEPH_LOCK_ILINK - 硬链接锁 CEPH_LOCK_IXATTR - 扩展属性锁 CEPH_LOCK_ISNAP - 快照锁 CEPH_LOCK_IFLOCK - 文件锁管理锁 CEPH_LOCK_IPOLICY - 策略锁
举例: 当多个客户端读取同一目录内容,但只有一个客户端进行写入时:
日常比喻:这像是办公室的会议室预订表。
SimpleLock和sm_state_t的关系是:
这种设计模式让Ceph能够用相同的代码基础实现多种不同行为的锁,同时保持代码清晰和行为一致。
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.
定义:
特点:
举例1 : 当多个客户端同时操作不同目录项时:
举例2
这像是管理多个部门的项目,每个部门可以独立更新自己负责的部分。
ScatterLock是Ceph MDS中的一种特殊锁类型,允许数据的分散管理。它的核心特点是:
class ScatterLock : public SimpleLock
背景场景
假设有一个大型目录/data,包含上万个文件,由多个MDS和客户端共同访问:
定义:
它是一种更复杂的锁类型,结合了SimpleLock和ScatterLock的特性,专门处理文件I/O权限。
特点:
struct LockType {
int type;
const sm_t *sm;
explicit LockType(int t) : type(t) {
switch (type) {
case CEPH_LOCK_DN: // 管理dentry
case CEPH_LOCK_IAUTH: // 管理mod,uid,gid等信息
case CEPH_LOCK_ILINK: // 管理link属性
case CEPH_LOCK_IXATTR: // 管理扩展属性
case CEPH_LOCK_ISNAP: // 管理快照信息
case CEPH_LOCK_IFLOCK: // 文件锁相关
case CEPH_LOCK_IPOLICY: // 管理layout、quota等信息
sm = &sm_simplelock;
break;
case CEPH_LOCK_IDFT: // 管理分片信息
case CEPH_LOCK_INEST: // 管理目录递归统计信息,如文件个数等
sm = &sm_scatterlock;
break;
case CEPH_LOCK_IFILE: //对于目录则是管理本层目录的统计信息,对于普通文件则是管理文件大小等
sm = &sm_filelock;
break;
case CEPH_LOCK_DVERSION:
case CEPH_LOCK_IVERSION:
sm = &sm_locallock;
break;
default:
sm = 0;
}
}
};
解决问题:多写 状态定义:(复杂跳过)
根据状态机可以确定:
复杂程度而言,可以根据sm_state_t结构体里面定义的条数判
struct sm_state_t {
int next; // 0表示稳定状态,非0表示应转换到的下一个状态
bool loner; // 是否支持单客户端模式(独占优化)
int replica_state; // 副本MDS应该处于的状态
char can_read; // 谁可以读取内容:ANY=所有人,AUTH=权威MDS,XCL=独占客户端
char can_read_projected; // 谁可以读取投影数据(尚未完全提交的数据)
char can_rdlock; // 谁可以获取读锁:表示共享访问权限
char can_wrlock; // 谁可以获取写锁:表示独占写入权限
char can_force_wrlock; // 谁可以强制获取写锁(即使有冲突)
char can_lease; // 谁可以获取租约:允许客户端缓存元数据
char can_xlock; // 谁可以获取独占锁:完全限制任何其他访问
int caps; // 通用能力位掩码:定义了客户端可获得的能力
int loner_caps; // 单客户端模式下可授予的额外能力
int xlocker_caps; // 持有独占锁时可授予的额外能力
int replica_caps; // 副本MDS可授予客户端的能力
};
#define ANY 1 // auth or replica
#define AUTH 2 // auth only
#define XCL 3 // auth or exclusive client
//#define FW 4 // fw to auth, if replica
#define REQ 5 // req state change from auth, if replica
状态分析
SYNC:一种任意人都可读,可加读锁的状态;
LOCK:在无副本的情况下,可多写的状态;
MIX: 在有副本的情况下,需要多写的状态; 主本、副本都是MIX态; 锁从MIX迁移到其他状态时,会自动汇总副本inode上的数
Ceph 文件系统 ( CephFS ) 它构建于 Ceph 的分布式对象存储 RADOS 之上
MDS 不会在本地存储任何元数据状态
锁状态机定义(sm_state_t)本身不需要持久化,它是代码中的静态结构。
但锁的当前状态需要持久化,这是通过以下机制实现的:
-CInode::store
函数的主要目的是将当前的 inode(文件元数据)编码并写入到底层的 RADOS 对象存储中。
// 将内存中的 CInode 元数据持久化到 RADOS 存储池
void CInode::store(MDSContext *fin)
{
/********************* 数据序列化阶段 *********************/
bufferlist bl; // 存储序列化后的二进制数据
string magic = CEPH_FS_ONDISK_MAGIC;
encode(magic, bl); // 写入魔数(校验用)
// 将 CInode 的元数据(如 inode 号、权限、时间戳等)序列化到 bl 中
encode_store(bl, mdcache->mds->mdsmap->get_up_features());
/********************* 对象操作定义 *********************/
SnapContext snapc; // 快照上下文(此处未显式关联快照)
ObjectOperation m; // RADOS 对象操作指令
m.write_full(bl); // 全量覆盖写入(替换整个对象内容)
// 构造元数据对象的唯一标识符(格式如 {ino}_head/.inode)
object_t oid = CInode::get_object_name(ino(), frag_t(), ".inode");
// 指定元数据存储池(metadata_pool,与数据池分离以优化性能
object_locator_t oloc(mdcache->mds->get_metadata_pool());
/********************* 异步写入与回调 *********************/
// 创建回调链:
Context *newfin = new C_OnFinisher(
new C_IO_Inode_Stored(this, get_version(), fin),
mdcache->mds->finisher
);
// 通过 Objecter 发起异步写入请求(底层调用 librados)
mdcache->mds->objecter->mutate(
oid, oloc, m, snapc,
ceph::real_clock::now(), 0, newfin
);
}
MDS重启或集群故障转移时,锁状态会从持久化存储恢复:
锁类型与锁状态的区别
重要的是区分锁类型(不可变)和锁状态(可变):
在Ceph中,锁状态转换具有自我驱动的特性,这是分布式系统中的一个关键设计。让我用简单的方式解释这个概念:
当说"锁机制依据各种条件自我驱动到合适的状态,无需业务主动干预"时,意思是:
假设一个场景,文件先被单客户端写入,后来变成多客户端读取:
阶段1: 客户端A独占写入文件
- FileLock自动转为EXCL状态(独占) - 提供完整的写入权限
阶段2: 客户端A写完,多个客户端开始读取
- 系统检测到访问模式变化
- FileLock自动评估并转为SYNC状态(共享)
- 优化为多读取场景
整个过程中,业务层只请求了"我需要读"或"我需要写",而不需关心锁处于什么状态。
看一个简化的file_eval函数,它负责评估文件锁状态:
https://github.com/ceph/ceph/blob/main/src/mds/Locker.cc
void Locker::file_eval(ScatterLock *lock, bool *need_issue) {
// 分析当前需求
int wanted = in->get_caps_wanted(&loner_wanted, &other_wanted);
// 当前是EXCL但不再需要独占
if (lock->get_state() == LOCK_EXCL) {
if (!((loner_wanted) & (CEPH_CAP_ANY_FILE_WR)) ||
(other_wanted & (CEPH_CAP_GEXCL|CEPH_CAP_GRD))) {
// 自动转到更合适的状态
if (other_wanted & CEPH_CAP_GWR)
scatter_mix(lock, need_issue); // 转到MIX
else
simple_sync(lock, need_issue); // 转到SYNC
}
}
// 当前不是EXCL但需要独占
else if (lock->get_state() != LOCK_EXCL &&
in->get_target_loner() >= 0 &&
(wanted & (CEPH_CAP_ANY_FILE_WR))) {
file_excl(lock, need_issue); // 转到EXCL
}
// 自动优化其他状态...
}
这种设计的优势
)
问题背景
特性 | CephFS 分布式锁 | Redis(Redlock) |
---|---|---|
集成度 | 与 RADOS 对象存储深度耦合,锁信息作为对象属性由 MDS 内部 mds.locker 模块全权管理 | 依赖外部 Redis 实例,通过客户端执行原子命令(SET NX PX 等)实现,无需底层存储集成 |
锁粒度 | 细粒度:支持多种锁类型,包括目录项锁(SimpleLock)、目录统计锁(ScatterLock)、文件锁(FileLock)等 | 粗粒度:按单个 key 加锁,通常一个资源对应一个 Redis key;Redlock 通过在多数实例上加锁来实现互斥 |
故障模型 | 支持 active–standby(基于日志重放秒级接管)与 active–active(目录子树隔离故障影响范围最小)两种模式,依靠 RADOS 多副本保障一致性 | 基于租约(TTL)与多数派原则:客户端需在多数 Redis 实例上在租约时间内成功加锁,部分实例故障时仍能保证锁安全 |
性能(延迟) | 中等:每次加解锁需通过 MDS RPC 并写入 per-MDS 日志到 RADOS,读多写少时可依靠 capability 缓存显著降低延迟 | 极低:锁操作在内存中完成,单实例场景下通常只需一次网络往返,多实例 Redlock 也仅需少量命令 |
可扩展性 | 通过动态子树分区(Dynamic Subtree Partitioning)实现 MDS 水平线性扩展与元数据负载均衡 | 受限于 Redis 实例数量和分片拓扑,跨分片锁定需额外协调,大规模场景中扩展性有限 |
复杂度 | 高:涉及多种锁类型与状态机、cap 回收逻辑、MDS 与 RADOS 之间的日志同步与故障切换,运维和调优成本较大 | 低:实现逻辑简单,主要由客户端库负责,但需注意时钟漂移和网络抖动带来的租约安全问题 |
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。