【Claude 4.6 生成】
本文基于 Ceph 三副本场景,深入分析 OSD 故障后的数据恢复流程,以及 PG Log 的存储位置、格式与副本写入策略。
背景场景
假设集群中某台机器上有 10 个 OSD,机器发生故障需要维修。运维流程如下:
- 机器故障 → 将该机器上所有 OSD 标记为
out - Ceph 集群自动触发 Recovery/Backfill,将三副本恢复到其他 OSD 上
- 机器维修完成 → 将 OSD 重新标记为
in
围绕步骤 3,存在两种情形,以下分别讨论。
Case 1:Recovery 未完成时 OSD 重新 in
结论
会尽量复用原 OSD 上的旧数据,只补增量(Recovery 模式),不需要全量拷贝。
详细流程
① OSD out 后发生了什么
OSD 标记 out 后,CRUSH 算法重新计算 PG 的 Acting Set,原 OSD 上的 PG 被重新映射到其他 OSD,触发 Backfill/Recovery 流程,开始向新 OSD 传输对象数据。
② OSD 提前 in 回来
由于 CRUSH 是确定性哈希 ,相同的 OSD 集合算出的结果完全相同。OSD 重新 in 后,这些 PG 的 Acting Set 会回到(或接近)原来的分配结果,即故障 OSD 仍会被选为某些 PG 的副本之一。
③ Peering 过程中的增量判断
Peering 时,Primary 会收集所有副本的 PG Log,对比各副本的 last_update 版本:
- 如果某 OSD 上的 PG 仅落后了一段时间的写入 ,且这段 gap 在其他副本的 PG Log 保留范围内(受
osd_min_pg_log_entries控制),则只需 replay log(增量 Recovery),无需全量传输。 - 如果落后太多,超出 PG Log 保留范围,则必须走 全量 Backfill。
④ 关键判断条件
| 条件 | 恢复方式 |
|---|---|
last_update 落后,但 gap 在 PG Log 范围内 |
Recovery(增量 replay) |
last_update 落后,gap 超出 PG Log 范围 |
Backfill(全量拷贝) |
| OSD 上 PG 数据完全丢失 | Backfill(全量拷贝) |
小结
OSD 重新 in 后,Ceph 不会无脑丢弃原 OSD 上的数据,而是通过 Peering + PG Log 对比,尽可能地利用旧数据、只补增量,这是 Ceph Recovery 机制高效性的核心所在。
Case 2:Recovery 完全完成后 OSD 重新 in
结论
旧数据确实已完全无用,但 Ceph 不会"直接覆盖",而是通过 Backfill 流程清除旧数据再写入新数据。
详细流程
① Recovery 完成后的集群状态
Recovery 完全完成后,集群进入 active+clean 状态,三副本已在其他 OSD 上完整存在。
② OSD 重新 in 后的 Peering
Peering 时,Primary 发现这些重新 in 的 OSD 上的 PG 处于以下状态之一:
- PG 为空(OSD 被格式化过)
- PG 数据完全过期(
last_update远落后于当前版本,且超出 log 范围)
此时必然走 Backfill 流程。
③ Backfill 流程的处理方式
Backfill 过程中,Primary 会向该 OSD 逐对象地进行操作:
- 对于该 OSD 上已不属于当前 PG 的旧对象 :发送
remove操作,清理掉旧数据 - 对于该 PG 应有但缺失的对象:从 Primary 传输,写入该 OSD
④ 不存在"直接当空盘用"的优化
Ceph 不会在 Backfill 前将整个 OSD 格式化(那样会影响该 OSD 上其他 PG 的数据)。BlueStore 通过正常的对象写入/删除流程处理每个对象,旧数据会在 Backfill 期间被逐步 remove + write 替换。
小结
Recovery 完成后 OSD 重新 in,旧数据虽然无用,但依然需要经历完整的 Backfill 流程来重建 PG 数据,代价高于 Case 1 中的增量 Recovery。
PG Log 深度解析
1. PG Log 存储在哪里?
PG Log 存储在 OSD 本地的 BlueStore RocksDB 中。
具体来说:
- BlueStore 底层使用 RocksDB 存储对象元数据
- PG Log 以
pgmeta对象的形式,通过 BlueStore 的对象存储接口写入 RocksDB - 每次数据写入时,PG Log entry 和数据写入在同一个
ObjectStore::Transaction中原子提交------这是 Ceph 保证"数据与日志一致性"的关键设计
2. PG Log 是什么格式?
PG Log 使用 Ceph 自身的 encode/decode 二进制序列化格式(基于 ENCODE_START/ENCODE_FINISH 宏),序列化后存入 RocksDB。
核心数据结构(位于 src/osd/pg_log.h):
cpp
// 单条 PG Log 记录
struct pg_log_entry_t {
hobject_t soid; // 操作的对象(object id)
eversion_t version; // 本次操作后的版本号
eversion_t prior_version; // 操作前的版本号
osd_reqid_t reqid; // 客户端请求 ID
utime_t mtime; // 操作时间
int32_t op; // 操作类型:MODIFY / DELETE / CLONE 等
ObjectModDesc mod_desc; // 回滚描述(用于 rollback)
// ...
};
// PG 的完整 Log
struct pg_log_t {
eversion_t head; // 最新 log entry 的版本(log 末尾)
eversion_t tail; // 最旧 log entry 的版本(log 起点)
eversion_t can_rollback_to;
list<pg_log_entry_t> log; // log 条目列表
// ...
};
每条 log entry 记录了:谁(对象)、做了什么(操作类型)、操作前后的版本号------足以支撑 Peering 时的版本对比与 Recovery 时的增量重放。
3. 三个副本都写 PG Log 吗?
是的,Primary 和所有 Replica 都写 PG Log,但职责和完整度略有差异:
| 副本角色 | 是否写 PG Log | 具体职责 |
|---|---|---|
| Primary | ✅ 完整写 | 维护完整 log;维护 pg_missing_t(哪些对象在哪里缺失);驱动 Recovery/Backfill 调度;向 Monitor 汇报 PG 状态 |
| Replica | ✅ 写 | 写相同的 log entry(版本、操作类型、对象名);mod_desc 回滚信息可能不完整 |
为什么 Replica 也必须写 PG Log?
原因在于 Peering 的高可用需求:
-
Primary 故障后选主:当 Primary 挂掉,某个 Replica 被选为新 Primary 时,它必须有自己的 PG Log 来:
- 判断哪些对象 missing(需要 Recovery)
- 与其他副本对比 log,找出分歧点(
divergent entries) - 驱动新一轮 Recovery
-
Peering 中的
merge_log:Primary 在 Peering 阶段会收集所有副本的 PG Log,执行merge_log操作,构建权威的完整 log 视图,用于确定各副本的 missing 列表。
Primary 独有的职责:
- 维护
pg_missing_t:记录每个 OSD 上哪些对象版本缺失 - 调度 Recovery 任务:决定先恢复哪些对象(按优先级)
- 向 Monitor 上报 PG 健康状态
全流程总结
机器故障
│
▼
OSD 标记 out
│
▼
CRUSH 重新计算 Acting Set
│
▼
Recovery / Backfill 开始(三副本恢复到其他 OSD)
│
├─── 机器维修完成,OSD 重新 in(Recovery 尚未完成)
│ │
│ ▼
│ Peering(对比各副本 PG Log)
│ │
│ PG Log 能覆盖 gap?
│ ├─ Yes ──→ Recovery(增量 replay log) 【Case 1 - 高效】
│ └─ No ──→ Backfill(全量拷贝对象) 【Case 1 - 退化】
│
└─── Recovery 完全完成 → active+clean
│
OSD 重新 in → Backfill(必然全量) 【Case 2】
PG Log 关键信息汇总
| 维度 | 内容 |
|---|---|
| 存储位置 | OSD 本地 BlueStore 的 RocksDB |
| 存储格式 | Ceph encode/decode 二进制序列化 |
| 原子性保证 | 与数据写入在同一 ObjectStore::Transaction 中提交 |
| 副本写入 | Primary + 所有 Replica 都写 |
| 主要用途 | Peering 版本对比、Recovery 增量重放、选主后重建权威 log |
参考源码位置
| 功能 | 源码路径 |
|---|---|
| PG Log 数据结构 | src/osd/pg_log.h |
| PG Log 操作实现 | src/osd/pg_log.cc |
| Peering 流程 | src/osd/PeeringState.cc |
| Recovery/Backfill | src/osd/PrimaryLogPG.cc |
| BlueStore 存储接口 | src/os/bluestore/BlueStore.cc |