CephFS 把一个 POSIX 文件系统建在 RADOS 之上。块存储(RBD)只要求对象写入正确,文件系统要求更多:原子目录操作、inode 一致性、fsync 语义、硬链接、目录快照。这些保证在分布式场景下不是免费的。
核心问题:MDS 宕掉后元数据从哪恢复、capability 如何把缓存一致性钉在跨客户端协作上、多 active MDS 的 subtree 迁移何时变成 thrashing、以及 fsync / stat 最终触碰多少网络往返。
本文是「Ceph / RADOS 存储内核」系列第 13 篇(共 18 篇)。→ 系列目录
篇目 核心内容 第 12 篇 · RBD image→对象、krbd vs librbd、快照/克隆边界 第 13 篇 · CephFS / MDS 可恢复 MDS、caps、subtree thrashing、POSIX 代价 第 14 篇 · RGW 索引/数据池、S3→RADOS 映射
版本锚定:Ceph Squid v19.2.5(
docs.ceph.com/en/squid/cephfs/;源码src/mds/@ v19.2.5)。
一、「无状态 MDS」该怎么读
Squid 文档原话(CephFS 首页):MDS 把元数据变更聚合成对 RADOS 上 journal 的高效写;「no metadata state is stored locally by the MDS」——MDS 进程本地磁盘不持久化元数据权威态。这常被口语化成「无状态 MDS」,容易误读成「MDS 内存里什么都没有、随时可杀」。
更准确的工程表述是:
| 层次 | 有没有状态 | 落在哪 |
|---|---|---|
| 权威持久态 | 有 | 元数据池:MDS journal + dirfrag OMAP + inode backtrace 等 |
| 热缓存 / 进行中请求 | 有 | MDS
进程内存:MDCache、MDRequest、已发放
caps |
| 本地磁盘权威副本 | 无 | 故意不存;宕机靠 journal replay 重建内存视图 |
因此:MDS 是可恢复状态机,不是无状态微服务。杀进程会丢未 flush 的内存缓存,但已 journal 的变更可被 standby replay;客户端未收到 reply 的请求必须重发。把「无本地盘」说成「完全无状态」,会低估 failover 时的 journal 积压与 cap 重发成本。
谱系锚点:Weil et al.「Ceph: A Scalable, High-Performance Distributed File System」(OSDI 2006)提出动态 subtree 分区(DPSS)与「客户端直接访 RADOS 数据、MDS 只权威元数据」;与 GFS 单 master(Ghemawat et al., SOSP 2003)对照。CRUSH 放置另见 Weil et al.(SC 2006),本篇不展开。
二、CephFS 的 RADOS 存储布局
数据池与元数据池
CephFS 至少两个池:元数据池与数据池。元数据池存 inode、目录条目、MDS journal;数据池存文件内容对象。客户端数据 I/O 直连 OSD,不经 MDS 转发——这是 OSDI’06 架构相对「网关型」文件系统的关键分叉。
文件内容默认按 4MiB 对象切分,OID 形如
{inode_hex}.{stripe_seq}:
1000000000000.00000000
1000000000000.00000001
元数据池另含:MDS journal
对象序列(mds.{rank}.*)、dirfrag OMAP、snap
相关元数据。
三、MDS Journal:写路径与恢复
Journal 结构
Journal 写入元数据池 RADOS 对象序列(默认段约
4MiB)。journaler 维护
write_pos(追加点)与
expire_pos(已 flush 到 dirfrag/inode
对象后可截断点)。路径:
- 内存完成
MDRequest(改MDCache、目录树)。 - 序列化 event,append 到 journal(RADOS write)。
- journal ACK 后,才向客户端 reply / 发 cap grant。
- 后台把脏元数据 flush 到 dirfrag 等对象,再推进
expire_pos。
失败模式:步骤 2 完成后宕机 → standby 从
expire_pos replay。客户端未收到 reply
的请求重发;挂载短暂 block。replay 时间与 journal 未 trim
量成正比;元数据池慢时段数上涨,failover SLO 崩。可
ceph tell mds.{rank} flush journal 强制
flush(阻塞调用方)。
站在排障视角:MDS「看起来挂了」时,先分清是进程死了(要
replay)、是 journal 写不动(元数据池 commit latency)、还是
cap revoke
卡死某个客户端。三者症状都是「文件系统卡住」,机制入口完全不同——前者看
MDS daemon,中者看元数据池 OSD,后者看
cap_revoke_eviction(第 15 篇)。
四、Active-Standby 与多 Active MDS
默认:rank 0 active + standby(可 standby-replay 尾随
journal,缩短接管时的 replay
窗口)。ceph fs set {fs} max_mds N 开多
active:每 rank 管一棵 subtree;跨 rank rename
要两阶段协调。只在元数据 IOPS 真成瓶颈时开——subtree
迁移本身有 barrier 税,盲目加 rank 往往先买到
thrashing,而不是线性扩展。
单 active 的隐含假设是:元数据热点可以纵向加内存与更快的元数据池磁盘。多 active 的隐含假设是:热点可被空间切开且迁移开销可摊销。HPC scratch、CI 产物目录、大量小文件创建,常常违反第二假设。
五、Subtree 分区与 thrashing
Subtree 与 auth MDS
目录树切成 subtree,每棵有 auth MDS。inode backtrace
记录归属,客户端与 MDS 据此路由。负载高时
Migrator(src/mds/Migrator.cc)选目录发起
EXPORT_DIR:传输缓存与 caps,迁移中加
barrier,完成后目标变 auth,源侧收回旧授权。
迁移时长与活跃 inode 数量、持有的 cap 数量相关。barrier
窗口内,该子树上的元数据 op
会排队;对业务表现为「某一个目录树突然变慢」,而
ceph -s 可能仍然 HEALTH_OK——因为
RADOS 数据面并未降级。
Thrashing(震荡)机制
thrashing 不是「负载高」的同义词,而是:迁移触发阈值与负载采样周期耦合,导致同一目录短时间多次 export/import。典型诱因:
- 热点在深树上游下下游移(创建风暴扫过多个目录)。
mds_bal_interval过短,瞬时尖刺被当成持续倾斜。- 迁移本身抬高源与目标 rank 的观测负载,反馈放大。
后果:barrier 期间该子树 op 排队;客户端 cap 反复
revoke/grant;ceph fs status 上 Ops
抖动但业务吞吐下降。缓解方向是调高迁移门槛、拉长采样、或把热点目录钉到固定
rank——不是盲目加 max_mds。Weil OSDI 2006 的动态
subtree 假设负载可平滑迁移;高 inode
创建率场景是该假设的反例,社区均衡权重仍在迭代,尚无闭合最优策略。
六、Capability:客户端缓存与一致性
MDS 向客户端发放 capability(cap),授权缓存或缓冲写。关键类型:
| Cap | 含义 |
|---|---|
Fc / Fr |
缓存读 / 可读 |
Fb / Fw |
缓冲写 / 可写 |
Fsx |
独占写 |
Ls 等 |
链接与 inode 属性相关 |
持 Fb 的写客户端未 flush
前,其他客户端同文件读会被卡住(revoke
路径)。这是分布式下逼近 POSIX
一致读写的核心税:正确性靠「谁持有哪些
cap」的显式协议,而不是靠每次都问 MDS。
fsync()(kclient /
ceph-fuse)大致串行:脏数据落到 OSD 并拿到 ACK,再向 MDS 做
cap flush,使 journal 落 RADOS。两段往返叠在一起,比本机
fsync 贵一个数量级以上。频繁小 fsync 的数据库不该直接落
CephFS——这是机制结论,不是偏好。
Cap 失败模式:客户端慢响应会推高
cap_revoke_eviction;网络分区后「假死」客户端被踢,恢复时可能丢未
flush 的缓冲写(取决于挂载选项与应用是否真正
fsync)。排障时若只盯 OSD 延迟而忽略 MDS cap
状态,会把「元数据锁等待」误诊成「磁盘慢」。
七、POSIX 代价模型
stat:有有效 cap 则本地返回;否则走
GETATTR。大目录 ls -l 靠 readdir
batch 摊薄往返;精确 nlink 等字段仍可能额外
roundtrip。
目录分片:单 dirfrag OMAP 过大时 split;百万文件目录是已知痛点,split 过程中目录 op 短暂 stall。运维上这常被描述成「CephFS 卡住一下」,根因却在元数据对象形态,而不在数据池容量。
硬链接 / 跨 rank rename:backtrace 追踪多路径;distributed rename 需要锁定两个目录权威。跨 rank 的原子 rename 是「保留 POSIX」账单上最显眼的一行。
目录快照(mkdir .snap/):目录级;依赖客户端
flush 协作,与 RBD「OSD 侧纯 COW、不经客户端」不同。硬链接跨
snap 边界的语义重建是已知摩擦(见下节)。
把这些条目加总:CephFS 的税主要付在元数据面(journal、caps、subtree、dirfrag),数据面反而相对「直」。用 RBD 的延迟经验直接外推 CephFS,会漏掉最贵的那一段。
八、MDS 快照与 RBD 快照对照
| 维度 | RBD snap | CephFS .snap |
|---|---|---|
| 粒度 | 整 image | 目录子树 |
| 一致性协作 | 需应用 / fsfreeze / qemu-ga quiesce | MDS 通知持 cap 客户端 flush |
| COW 落点 | PrimaryLogPG snapset | 数据池对象 + MDS snap 元数据 |
| 硬链接 | 不适用 | 跨 snap 边界重建不完整是已知限制 |
CephFS 快照更贴近「用户可见的目录时间点」,也更依赖客户端诚实;RBD 快照更「存储层自闭」,但不知道文件系统内部是否一致。两者都借用 RADOS SnapContext 思想,却不能互相替代。
九、挂载方式
| 方式 | 实现 | 要点 |
|---|---|---|
| kclient | fs/ceph/ |
POSIX 完整度与性能更好;features 跟内核版本绑定 |
| ceph-fuse | 用户态 FUSE | 可移植、易调试;路径多一层 FUSE |
Squid 生产 Linux 挂载优先核对内核是否覆盖所需 cap 与 snap 行为;容器无权限挂内核文件系统时才退到 fuse。fuse 便于调试 journal/cap 问题,但不应默认当成与 kclient 等价的性能路径。
十、谱系、争论与开放问题
谱系:OSDI 2006(Weil et al.)定义「MDS 权威元数据 + 客户端直访数据 + 动态 subtree」;工程实现把权威态钉在 RADOS journal,进程内存是可丢缓存。官方文档的「no local metadata state」必须和本篇第一节一起读,避免「无状态」误导。CRUSH(SC 2006)回答对象放哪,不回答 inode 锁怎么发——两篇论文分工不同。
争论:完整 POSIX(含原子 rename、精确
nlink)对比弱化语义换扩展性(HDFS/GFS 阵营)。Ceph
选前者,跨 rank 付两阶段税。官方
docs.ceph.com/en/squid/cephfs/posix/
列出已知偏差(mtime 粒度、O_SYNC
范围等)——以文档为准,不靠传闻。
开放问题(可检验):
- thrashing:固定一组
mds_bal_*参数,在「每秒创建大量 inode、目录扇出固定」负载下,统计 10 分钟内同一 dirino 的 export 次数;若反复超过自定阈值,判均衡不稳定——是否存在参数使该计数趋近零? - fsync 尾延迟:元数据池 commit latency
与客户端
fsyncP99 是否同峰?若同峰,瓶颈在 journal 串行写而非数据池。 - fragment split 可观测性:split stall
是否能在不抓 MDS perf dump 的情况下被
ceph fs status暴露?Squid 上仍偏运维盲区。
再补一条工程读法:把「文件系统卡住」拆成三条互不替代的假设——元数据服务进程死亡需要日志重放、日志写不动对应元数据池提交延迟、能力证回收卡死对应客户端迟迟不归还缓冲写权限。三者在监控上看都像「目录树不动」,但入口分别是守护进程、对象存储守护进程延迟、以及元数据服务内部计数器。混为一谈会把能力证等待误诊成磁盘慢。
与第十二篇对照:块设备快照的写时复制落在主日志归置组,文件系统快照还要客户端协作刷新。选接入层时,先问应用要不要频繁同步写,再问要不要目录级时间点,最后才问挂载方式。顺序反了,容易在已经付了能力证税之后才发现同步写不可接受。
单活跃元数据服务足以覆盖许多中等元数据速率场景;多活跃只有在能够证明热点可被空间切开、且迁移屏障可接受时才值得打开。否则多出来的秩只会增加跨权威目录改名的两阶段协调面,把本来局部的抖动变成全局协调税。
参考资料
论文 / 官方文档(A 级)
- Weil, S. et al.「Ceph: A Scalable, High-Performance Distributed File System」OSDI 2006 — subtree / DPSS、MDS 与客户端直访数据
- Weil, S. et al.「CRUSH: Controlled, Scalable, Decentralized Placement of Replicated Data」SC 2006 — 放置(本系列第 4 篇);与 MDS 正交
- Weil, S. 博士论文《Ceph: Reliable, Scalable, and High-Performance Distributed Storage》UCSC 2007 — journal、cap
- Ceph Squid — Ceph File System — 「no metadata state is stored locally」原文语境
- Ceph Squid — POSIX compatibility
- 源码:
src/mds/MDCache.cc、src/mds/Migrator.cc、src/mds/Locker.cc(caps)@ v19.2.5
系列内
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
【Ceph RADOS】RADOS 内核全景:五轴坐标系与 18 篇路线
补齐站内 distributed/38 之上的 RADOS 内核层缺口;定义 Map 传播、通信、一致性、本地存储、接入代价五条坐标系,给出 18 篇阅读路线与系列边界。
【Ceph RADOS】MON/MGR 与 Map:quorum、epoch 传播与客户端可见性
拆解 Ceph MON Paxos quorum、OSDMap/PGMap 结构与 epoch 传播机制;说明 MGR 主备模型与模块框架;分析客户端 epoch 缓存与可见性窗口的边界条件。
【Ceph RADOS】OSD 与 Messenger:daemon 结构、msgr2 协议与 Op 入队路径
拆解 ceph-osd daemon 内部结构、msgr2 Frame 格式与握手阶段,追踪 Op 从 TCP socket 经 AsyncMessenger、OSD::dispatch_context 到 ShardedOpWQ 的完整入队路径。
【Ceph RADOS】CRUSH 加深:rule/bucket/weight 机制与失败域语义
相对 distributed/38 的 CRUSH 算法概览,深入 bucket 类型的算法复杂度差异、rule step 语义、weight 体系与 primary_affinity,分析 chooseleaf 与失败域的精确定义及 CRUSH 与一致性哈希的本质区别。