Ceph OSD 故障恢复机制与 PG Log 深度解析

【Claude 4.6 生成】

本文基于 Ceph 三副本场景,深入分析 OSD 故障后的数据恢复流程,以及 PG Log 的存储位置、格式与副本写入策略。


背景场景

假设集群中某台机器上有 10 个 OSD,机器发生故障需要维修。运维流程如下:

  1. 机器故障 → 将该机器上所有 OSD 标记为 out
  2. Ceph 集群自动触发 Recovery/Backfill,将三副本恢复到其他 OSD 上
  3. 机器维修完成 → 将 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 的高可用需求

  1. Primary 故障后选主:当 Primary 挂掉,某个 Replica 被选为新 Primary 时,它必须有自己的 PG Log 来:

    • 判断哪些对象 missing(需要 Recovery)
    • 与其他副本对比 log,找出分歧点(divergent entries
    • 驱动新一轮 Recovery
  2. 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
相关推荐
bukeyiwanshui10 小时前
20260527 Ceph 集群安装过程
ceph
AOwhisky11 小时前
Ceph系列第一期:Ceph分布式存储核心概念与架构初识
linux·运维·笔记·分布式·ceph·学习·架构
bukeyiwanshui12 小时前
20260527 ceph添加节点
ceph
bukeyiwanshui13 小时前
20260527 Ceph 集群组件管理
ceph
AOwhisky13 小时前
Ceph系列第二期:Ceph集群部署实战(cephadm)
linux·运维·笔记·分布式·ceph·云计算·存储
信创工程师-小杨1 天前
openEuler24.03搭建Ceph高可用
ceph
潮起鲸落入海1 天前
ceph简介及部署安装
ceph
刘某的Cloud4 天前
ceph-s & ceph_osd_tree_ceph缩容恢复_集群状态.md
linux·运维·服务器·ceph
自由且自律5 天前
cenph三大存储方式
运维·经验分享·ceph