PG.cc 业务功能与原理详细分析
一、PG 概述
1.1 PG 的定义与作用
Placement Group (PG) 是 Ceph 分布式存储系统的核心抽象,负责:
- 数据分片管理:将对象映射到特定的 PG,实现数据分片
- 一致性保证:通过 Peering 机制保证副本间一致性
- 故障恢复:自动检测和恢复数据不一致
- 负载均衡:通过 PG 分布实现数据在 OSD 间的均衡
1.2 PG 在 Ceph 架构中的位置
1 | 客户端请求 |
二、核心业务功能
2.1 PG 生命周期管理
2.1.1 PG 初始化
1 | PG::PG(OSDService *o, OSDMapRef curmap, const PGPool &_pool, spg_t p) |
初始化流程:
- 设置 PG ID 和集合(Collection)
- 初始化 SnapMapper(快照映射器)
- 创建 PeeringState(peering 状态机)
- 初始化统计信息
关键代码位置:
PG::PG()(199行):构造函数PG::init()(854行):初始化 PG 状态PG::read_state()(1086行):从磁盘读取 PG 状态
2.1.2 PG 状态读取
1 | void PG::read_state(ObjectStore *store) |
流程:
- 从 ObjectStore 读取 PG 元数据
- 读取 PG 信息(info)和 PastIntervals
- 读取 PGLog(操作日志)
- 初始化 PeeringState
- 触发 Peering 流程
数据结构:
pg_info_t:PG 基本信息(版本、统计等)PastIntervals:历史区间信息PGLog:操作日志
2.2 Peering 机制
2.2.1 Peering 概述
Peering 是 PG 的核心机制,用于:
- 确定权威数据:在多个副本间确定哪个版本是权威的
- 同步状态:确保所有副本对 PG 状态达成一致
- 处理分歧:解决副本间的数据分歧
2.2.2 Peering 事件处理
1 | void PG::do_peering_event(PGPeeringEventRef evt, PeeringCtx &rctx) |
Peering 事件类型:
NullEvt:空事件Activate:激活 PGAdvMap:OSDMap 推进Query:查询对等节点状态Notify:通知对等节点Info:信息交换
处理流程:
1 | 收到 Peering 事件 |
关键代码位置:
PG::do_peering_event()(2128行):处理 peering 事件PG::queue_peering_event()(2142行):将事件加入队列PG::handle_advance_map()(2194行):处理 map 推进
2.2.3 OSDMap 推进处理
1 | void PG::handle_advance_map( |
处理步骤:
- 检测 acting set 变化
- 检测 up set 变化
- 触发 Peering 状态机转换
- 处理 PG 分裂/合并
- 更新 PG 状态
2.3 客户端操作处理
2.3.1 操作路由
PG 接收来自 OSD 的操作请求,根据 PG 状态决定处理方式:
操作状态检查:
can_discard_op():检查操作是否可以丢弃can_discard_request():检查请求是否可以丢弃old_peering_msg():检查是否为过期的 peering 消息
关键代码位置:
PG::can_discard_op()(1970行):检查操作是否过期PG::can_discard_request()(2070行):检查请求是否过期
2.3.2 操作等待机制
当 PG 处于非活跃状态时,操作会被暂存:
1 | void PG::requeue_op(OpRequestRef op) |
等待队列类型:
waiting_for_map:等待 OSDMap 更新waiting_for_peered:等待 Peering 完成waiting_for_flush:等待刷新完成waiting_for_clean_to_primary_repair:等待清理完成
2.4 恢复(Recovery)机制
2.4.1 恢复触发
1 | void PG::queue_recovery() |
触发条件:
- PG 是主副本(is_primary())
- PG 已完成 Peering(is_peered())
- 存在缺失对象(missing objects)
恢复流程:
1 | queue_recovery() |
关键代码位置:
PG::queue_recovery()(425行):将恢复加入队列PG::start_recovery_op()(493行):开始恢复操作PG::finish_recovery_op()(508行):完成恢复操作PG::finish_recovery()(452行):完成所有恢复
2.4.2 恢复状态管理
1 | void PG::clear_recovery_state() |
恢复状态:
recovery_queued:是否已加入恢复队列recovery_ops_active:活跃的恢复操作数recovering_oids:正在恢复的对象集合
2.5 Scrub 机制
2.5.1 Scrub 概述
Scrub 用于检测和修复数据不一致:
Scrub 类型:
- Shallow Scrub:检查对象元数据和校验和
- Deep Scrub:读取并验证对象数据
2.5.2 Scrub 调度
1 | Scrub::schedule_result_t PG::start_scrubbing( |
Scrub 条件检查:
- PG 必须是活跃的(is_active())
- 不能有 NOSCRUB 标志
- 满足 scrub 间隔要求
关键代码位置:
PG::start_scrubbing()(1278行):启动 scrubPG::scrub_requested()(1328行):scrub 请求处理PG::on_scrub_schedule_input_change()(1314行):scrub 调度变化处理
2.6 快照管理
2.6.1 快照映射
1 | void PG::update_object_snap_mapping( |
快照映射器(SnapMapper)作用:
- 维护对象到快照的映射关系
- 支持快照克隆和删除
- 管理快照元数据
2.6.2 快照修剪
1 | void PG::queue_snap_retrim(snapid_t snap) |
快照修剪流程:
- 检测需要修剪的快照
- 加入修剪队列(snap_trimq)
- 执行修剪操作
- 更新快照映射
关键代码位置:
PG::queue_snap_retrim()(1529行):加入快照修剪队列PG::on_active_actmap()(1550行):激活时处理快照修剪
2.7 PG 分裂与合并
2.7.1 PG 分裂
1 | void PG::split_into(pg_t child_pgid, PG *child, unsigned split_bits) |
分裂流程:
- 创建子 PG
- 复制父 PG 状态到子 PG
- 更新 SnapMapper 的 split_bits
- 复制快照修剪队列
- 执行数据分裂(_split_into)
关键代码位置:
PG::split_into()(528行):PG 分裂PG::start_split_stats()(545行):开始分裂统计PG::finish_split_stats()(550行):完成分裂统计
2.7.2 PG 合并
1 | void PG::merge_from(map<spg_t,PGRef>& sources, PeeringCtx &rctx, |
合并流程:
- 合并源 PG 的状态
- 合并 PGLog
- 合并集合(merge_collection)
- 更新 SnapMapper
- 删除源 PG 元数据
关键代码位置:
PG::merge_from()(555行):PG 合并
2.8 Backoff 机制
2.8.1 Backoff 概述
Backoff 用于在 PG 状态不稳定时阻止客户端操作:
1 | void PG::add_backoff(const ceph::ref_t<Session>& s, |
Backoff 状态:
STATE_NEW:新建STATE_ACKED:已确认STATE_DELETING:删除中
使用场景:
- Peering 过程中
- 恢复过程中
- PG 分裂/合并时
关键代码位置:
PG::add_backoff()(582行):添加 backoffPG::release_backoffs()(608行):释放 backoffPG::clear_backoffs()(670行):清除所有 backoff
2.9 资源预留机制
2.9.1 恢复资源预留
1 | void PG::request_remote_recovery_reservation( |
资源类型:
- 本地资源:本地 OSD 的 IO 资源
- 远程资源:远程 OSD 的恢复资源
- Scrub 资源:scrub 操作的资源
关键代码位置:
PG::request_local_background_io_reservation()(1385行)PG::request_remote_recovery_reservation()(1410行)PG::cancel_remote_recovery_reservation()(1423行)
三、核心设计原理
3.1 状态机驱动
PG 使用状态机(PeeringState)管理 PG 生命周期:
1 | Initial → Reset → Started → Peering → Active |
状态转换触发:
- OSDMap 变更
- Peering 事件
- 恢复完成
- 错误处理
3.2 版本控制机制
3.2.1 版本类型
- last_update:最后更新版本
- last_complete:最后完整版本
- log_tail:日志尾部版本
3.2.2 版本比较
通过版本比较确定:
- 哪个副本数据最新
- 是否需要恢复
- 数据是否一致
3.3 PGLog 机制
3.3.1 PGLog 作用
- 操作记录:记录所有写操作
- 版本追踪:追踪对象版本变化
- 恢复依据:用于恢复缺失对象
3.3.2 PGLog 结构
1 | class PGLog { |
3.4 锁机制
3.4.1 PG 锁
1 | void PG::lock(bool no_lockdep) const |
锁的作用:
- 保护 PG 状态一致性
- 防止并发修改
- 确保操作原子性
锁的持有者:
- 操作处理线程
- Peering 线程
- 恢复线程
3.5 事务机制
3.5.1 事务使用
1 | void PG::prepare_write( |
事务包含:
- PG 信息更新
- PGLog 写入
- 对象操作
- 元数据更新
3.6 引用计数管理
3.6.1 引用计数
1 | void PG::get(const char* tag) |
引用计数用途:
- 防止 PG 在使用中被删除
- 跟踪 PG 使用情况
- 调试内存泄漏
四、关键数据结构
4.1 PG 核心数据结构
1 | class PG { |
4.2 等待队列
1 | map<entity_name_t, list<OpRequestRef>> waiting_for_map; |
4.3 恢复相关
1 | bool recovery_queued; // 是否已加入恢复队列 |
五、业务流程示例
5.1 客户端写操作流程
1 | 客户端发送写请求 |
5.2 Peering 流程
1 | OSDMap 变更 |
5.3 恢复流程
1 | 检测到缺失对象 |
六、性能优化设计
6.1 异步处理
- Peering 事件异步处理
- 恢复操作异步执行
- 操作等待队列异步唤醒
6.2 批量操作
- 批量恢复对象
- 批量更新统计信息
- 批量处理等待操作
6.3 资源预留
- 恢复资源预留避免冲突
- Scrub 资源预留保证优先级
- 本地 IO 资源预留
七、容错与可靠性
7.1 状态一致性
- 通过 Peering 保证副本一致性
- 通过 PGLog 追踪操作历史
- 通过版本控制检测分歧
7.2 故障恢复
- 自动检测缺失对象
- 自动从对等节点恢复
- 自动处理副本故障
7.3 数据校验
- Scrub 检测数据不一致
- 自动修复(auto-repair)
- 校验和验证
八、总结
PG.cc 实现了 Ceph 存储系统的核心逻辑:
- 状态管理:通过 PeeringState 状态机管理 PG 生命周期
- 一致性保证:通过 Peering 和 PGLog 保证数据一致性
- 故障恢复:自动检测和恢复数据不一致
- 操作处理:路由和处理客户端操作
- 资源管理:管理恢复、scrub 等后台任务
整个设计体现了分布式系统的核心原则:
- 状态机驱动:清晰的状态转换
- 版本控制:精确的版本追踪
- 异步处理:高效的并发处理
- 容错设计:可靠的故障处理
PG 作为 Ceph 的核心抽象,承担了数据分片、一致性保证、故障恢复等关键职责,是整个存储系统稳定运行的基础。
正在加载留言…