Ceph Monitor 如何管理文件系统(FS)元数据

本文基于 Ceph 源码(mds/FSMap.hmds/MDSMap.hmon/MDSMonitor.ccmon/FSCommands.ccmon/PaxosFSMap.h)深入分析 Monitor 管理 FS 实例的核心数据结构,以及执行 ceph fs new 时的完整交互流程。


一、Ceph 中的"文件系统实例"

Ceph 里每个文件系统实例对应源码中的 Filesystem 对象。用户执行 ceph fs new 创建的,就是一个 Filesystem,它包含:

  • 一个唯一的文件系统 ID(fscid
  • 一个 MDSMap:描述该 FS 的所有配置信息和 MDS 拓扑

所有 FS 实例由 FSMap 统一管理,FSMap 是 Monitor 内部的全局唯一视图。


二、Monitor 的数据结构层次

2.1 总体层次

复制代码
Monitor
└── MDSMonitor (PaxosService + PaxosFSMap)
    ├── fsmap         → 当前已提交 epoch(只读,对外服务)
    └── pending_fsmap → 下一 epoch(正在修改,Paxos 提交前)

FSMap
├── epoch                                     全局版本号
├── next_filesystem_id                        自增 fscid 分配器
├── legacy_client_fscid                       默认 FS(兼容旧客户端)
├── enable_multiple                           是否允许多 FS 并存
├── filesystems: map<fscid, Filesystem>       所有 FS 实例
├── standby_daemons: map<gid, mds_info_t>     全局 standby 池
├── standby_epochs:  map<gid, epoch_t>
└── mds_roles: map<gid, fscid>                GID → 所属 FS 快速索引

PaxosFSMap 的核心设计是维护两个版本的 FSMap(源码 mon/PaxosFSMap.h):

cpp 复制代码
class PaxosFSMap {
    FSMap fsmap;          // 当前 epoch:已提交,对外只读
    FSMap pending_fsmap;  // 下一 epoch:正在修改,提交失败不影响 fsmap
};

这是"写时 copy-on-modify"的典型应用,保证修改过程中的隔离性。

2.2 Filesystem 结构

cpp 复制代码
class Filesystem {
    fs_cluster_id_t fscid;   // FS 唯一 ID(全局自增,永不复用)
    MDSMap mds_map;          // 该 FS 的全部配置 + MDS 拓扑
};

2.3 MDSMap:FS 实例的元数据核心

MDSMap 是 FS 实例最重要的数据结构,完整字段如下:

基础标识
字段 类型 说明
fs_name string FS 名称,ceph fs new 时指定
epoch epoch_t 该 MDSMap 的版本号,每次变更 +1
created utime_t FS 创建时间
modified utime_t 最后修改时间
Pool 绑定
字段 类型 说明
metadata_pool int64_t 元数据 pool(存 inode、dentry 等)
data_pools vector<int64_t> 数据 pool 列表,第一个为默认
MDS 拓扑配置
字段 类型 说明
max_mds mds_rank_t 目标 active MDS 数(决定 rank 0...N-1)
standby_count_wanted mds_rank_t 期望 standby 数,用于健康告警
Rank 分配表(核心)
字段 类型 说明
in set<rank> 当前定义的 rank 集合
up map<rank, gid> rank → GID(active MDS 映射)
failed set<rank> 无 daemon 持有的 failed ranks
damaged set<rank> 元数据损坏,需人工介入
stopped set<rank> 被主动停掉的 ranks
mds_info map<gid, mds_info_t> 每个 MDS daemon 的详细状态
行为开关
字段 类型 说明
flags uint32_t ALLOW_SNAPS / NOT_JOINABLE / ALLOW_STANDBY_REPLAY 等
max_file_size uint64_t 单文件大小上限,默认 1TB
session_timeout uint32_t client session 超时,默认 60s
session_autoclose uint32_t session 自动关闭时间,默认 300s
故障追踪
字段 类型 说明
last_failure epoch_t 最近一次 MDS 故障的 FSMap epoch
last_failure_osd_epoch epoch_t 关联 OSD epoch(用于 blacklist 传播)

2.4 mds_info_t:单个 MDS daemon 的状态

cpp 复制代码
struct mds_info_t {
    mds_gid_t global_id;           // 全局唯一 ID(每次启动重新生成)
    std::string name;              // daemon 名(配置中指定,重启不变)
    mds_rank_t rank;               // 持有的 rank(-1 表示 standby)
    int32_t inc;                   // incarnation(重启计数)
    DaemonState state;             // BOOT/STANDBY/REPLAY/.../ACTIVE
    version_t state_seq;           // 状态变更序号(防乱序)
    entity_addrvec_t addrs;        // 通信地址
    utime_t laggy_since;           // 开始 laggy 的时间(非零则 laggy)
    std::set<mds_rank_t> export_targets;  // 负载均衡导出目标
    uint64_t mds_features;         // 支持的特性集
};

三、FSMap 的持久化机制

MDSMonitor 继承自 PaxosService,FSMap 通过 Paxos 持久化到 Monitor Store(RocksDB):

复制代码
encode_pending()          → 将 pending_fsmap 序列化为 bufferlist
    ↓
Paxos::propose_pending()  → 多数派 Monitor 同意后提交
    ↓
update_from_paxos()       → 所有 Monitor decode 新版本,更新本地 fsmap

持久化 key:
  RocksDB prefix: "mds"
  key:   epoch(版本号)
  value: FSMap 的 bufferlist

关键设计:每个 epoch 的 FSMap 都单独保存,Monitor 可以按需回溯历史版本。 超出 mon_max_mdsmap_epochs 的旧版本会被 trim 清理。


四、ceph fs new 的完整交互流程

4.1 请求路径

复制代码
用户执行:ceph fs new myfs metadata_pool data_pool
    ↓
ceph CLI 构造 MON_COMMAND 消息,发给 Monitor leader
    ↓
Monitor.preprocess_query()
  └── MDSMonitor.preprocess_command()
        只读检查(FS 已存在?pool 存在?)
        命中缓存 → 直接回复
        需要修改 → 转 prepare_command()
    ↓
Monitor.prepare_update()
  └── MDSMonitor.prepare_command()
        遍历 handlers,找到 FsNewHandler
        调用 FsNewHandler::handle()

4.2 FsNewHandler::handle() 步骤详解

Step 1:校验 metadata pool 和 data pool 存在

cpp 复制代码
// 直接查询同进程内的 OSDMonitor,不走网络
int64_t metadata = mon->osdmon()->osdmap.lookup_pg_pool_name(metadata_name);
int64_t data     = mon->osdmon()->osdmap.lookup_pg_pool_name(data_name);
// pool 不存在 → 返回 -ENOENT,报错
// data pool id == 0 → 拒绝(CephFS 不允许)

Step 2:校验 FS 名唯一性 + 多 FS 开关

cpp 复制代码
if (fsmap.get_filesystem(fs_name)) {
    // 同名 FS 已存在(但 pool 不同)→ 返回 -EINVAL
    // 同名 FS + 相同 pool → 幂等,返回 0(no-op)
}
if (fsmap.filesystem_count() > 0 && !fsmap.get_enable_multiple()) {
    // 未开启多 FS → 拒绝
}

Step 3:检查 pool 健康状态

cpp 复制代码
// metadata pool 必须为空(objects == 0),否则拒绝
// --force 可跳过此检查
// metadata pool 不能是 EC(纠删码)
// data pool 若为 EC,必须支持 overwrites

Step 4:联动 OSDMonitor,设置 pool 应用标记(关键)

cpp 复制代码
// 等待 OSDMonitor 可写(可能重试)
if (!mon->osdmon()->is_writeable()) {
    mon->osdmon()->wait_for_writeable(op, new C_RetryMessage(...));
    return -EAGAIN;
}

// 为 pool 打上 CephFS 应用标记,防止被误用
mon->osdmon()->do_application_enable(data,     "cephfs", "data",     fs_name);
mon->osdmon()->do_application_enable(metadata, "cephfs", "metadata", fs_name);

// 为 metadata pool 优化参数(提升性能)
mon->osdmon()->do_set_pool_opt(metadata, RECOVERY_PRIORITY,  5);    // 提升恢复优先级
mon->osdmon()->do_set_pool_opt(metadata, PG_NUM_MIN,         16);   // 最小 PG 数
mon->osdmon()->do_set_pool_opt(metadata, PG_AUTOSCALE_BIAS,  4.0); // PG 自动扩缩容偏置

mon->osdmon()->propose_pending();  // OSD map 变更提交 Paxos

这里 MDSMonitor 直接调用 OSDMonitor 的内部接口(同进程内调用),同时发起两个独立的 Paxos 提议(FSMap 和 OSDMap 分属不同的 PaxosService)。

Step 5:在 FSMap 中创建 Filesystem 对象

cpp 复制代码
auto&& fs = fsmap.create_filesystem(fs_name, metadata, data,
                                    mon->get_quorum_con_features());
// 内部操作:
//   1. 分配新 fscid(next_filesystem_id++)
//   2. 初始化 MDSMap(设置 fs_name、pool、max_mds=1、created=now)
//   3. 加入 filesystems 表
//   4. 若是第一个 FS → 更新 legacy_client_fscid

Step 6:尝试分配 standby MDS 到 rank 0(可选)

cpp 复制代码
mds_gid_t gid = fsmap.find_replacement_for({fs->fscid, 0}, "");
if (gid != MDS_GID_NONE) {
    mon->clog->info() << info.human_name() << " assigned to filesystem "
                      << fs_name << " as rank 0";
    fsmap.promote(gid, *fs, 0);  // standby → rank 0,将进入 CREATING 流程
}

4.3 Paxos 提交阶段

复制代码
prepare_command() 返回 true(表示需要提交)
    ↓
encode_pending(t)
  ├── pending FSMap 序列化 → put_version(t, epoch, bl)
  ├── MDS health 信息 → RocksDB("mds_health" prefix)
  └── OSDMap 变更(pool application 标记)→ OSDMonitor 独立提交
    ↓
Paxos::propose_pending()
  → Monitor 间多数派同步
    ↓
提议通过 → update_from_paxos()
  → 所有 Monitor decode 新 FSMap,更新内存
    ↓
check_subs()
  → 向订阅了 "mdsmap" / "fsmap" 的 MDS daemon 和客户端推送新 map
    ↓
wait_for_finished_proposal 回调
  → reply_command(op, 0, "new fs with metadata pool X and data pool Y")
    ↓
用户收到成功响应

4.4 MDS daemon 收到新 FSMap 后的动作

复制代码
standby MDS daemon
  ↓ 收到新 FSMap(发现 rank 0 已分配给自己)
  ↓ 从 STANDBY → CREATING
  ↓ 初始化 journal、idalloc 等元数据结构(写 metadata pool)
  ↓ CREATING → ACTIVE
  ↓ 向 Monitor 上报 beacon(STATE_ACTIVE)
  ↓ Monitor 更新 FSMap(mds_info[gid].state = ACTIVE)

五、完整交互组件图

复制代码
┌──────────┐   MMonCommand     ┌───────────────────────────────────────────┐
│ ceph CLI │ ────────────────► │ Monitor (MDSMonitor)                      │
│          │ ◄──────────────── │                                           │
│          │  reply (success)  │  preprocess_command()                     │
└──────────┘                   │  prepare_command() → FsNewHandler         │
                               │    ├── OSDMonitor.osdmap (同进程查询)      │
                               │    ├── OSDMonitor.do_application_enable() │
                               │    ├── OSDMonitor.propose_pending()       │
                               │    └── fsmap.create_filesystem()          │
                               │                                           │
                               │  encode_pending() → Paxos 提交            │
                               │    ├── FSMap → RocksDB (mds prefix)       │
                               │    └── OSDMap → RocksDB (osd prefix)      │
                               │                                           │
                               │  update_from_paxos() → 所有 Mon 同步       │
                               │  check_subs() → 推送新 FSMap               │
                               └───────────────────────────────────────────┘
                                         │ MFSMap 消息(推送)
                                         ▼
                               ┌──────────────────┐
                               │ MDS daemon        │ 收到新 FSMap
                               │ (standby → rank0) │ STANDBY → CREATING → ACTIVE
                               └──────────────────┘
                                         │ MMDSBeacon (STATE_ACTIVE)
                                         ▼
                               Monitor 更新 FSMap,广播新 epoch

六、关键设计总结

设计 作用
epoch 版本号 所有变更都递增 epoch,客户端/MDS 靠 epoch 判断是否需要更新
pending_fsmap 写时隔离,Paxos 提交失败不影响已提交的 fsmap
OSDMonitor 联动 创建 FS 必须同时修改 OSD map(打 application tag),pool 使用可追踪
Paxos 保证一致性 FSMap 和 OSDMap 变更分别通过独立 Paxos 提交,所有 Monitor 节点强一致
check_subs 主动推送 变更后主动推 map 给订阅者,MDS/客户端无需轮询
幂等性 相同参数重复执行 fs new 直接返回 0,不产生副作用

七、参考源码

文件 内容
mds/FSMap.h FSMapFilesystem 数据结构
mds/MDSMap.h MDSMapmds_info_t 数据结构
mon/PaxosFSMap.h fsmap / pending_fsmap 双版本管理
mon/MDSMonitor.cc prepare_command()encode_pending()update_from_paxos()
mon/FSCommands.cc FsNewHandler::handle()fs new 核心逻辑)
相关推荐
运维栈记3 小时前
Ceph 入门:一文读懂分布式存储的“瑞士军刀”
分布式·ceph
一个行走的民4 小时前
Ceph Monitor 订阅推送机制深度解析
ceph
一个行走的民17 小时前
Ceph OSD 故障恢复机制与 PG Log 深度解析
ceph
bukeyiwanshui1 天前
20260527 Ceph 集群安装过程
ceph
AOwhisky1 天前
Ceph系列第一期:Ceph分布式存储核心概念与架构初识
linux·运维·笔记·分布式·ceph·学习·架构
bukeyiwanshui1 天前
20260527 ceph添加节点
ceph
bukeyiwanshui1 天前
20260527 Ceph 集群组件管理
ceph
AOwhisky1 天前
Ceph系列第二期:Ceph集群部署实战(cephadm)
linux·运维·笔记·分布式·ceph·云计算·存储
信创工程师-小杨2 天前
openEuler24.03搭建Ceph高可用
ceph