本文基于 Ceph 源码(
mds/FSMap.h、mds/MDSMap.h、mon/MDSMonitor.cc、mon/FSCommands.cc、mon/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 |
FSMap、Filesystem 数据结构 |
mds/MDSMap.h |
MDSMap、mds_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 核心逻辑) |