PG.cc 业务功能与原理详细分析

PG.cc 业务功能与原理详细分析

一、PG 概述

1.1 PG 的定义与作用

Placement Group (PG) 是 Ceph 分布式存储系统的核心抽象,负责:

  • 数据分片管理:将对象映射到特定的 PG,实现数据分片
  • 一致性保证:通过 Peering 机制保证副本间一致性
  • 故障恢复:自动检测和恢复数据不一致
  • 负载均衡:通过 PG 分布实现数据在 OSD 间的均衡

1.2 PG 在 Ceph 架构中的位置

1
2
3
4
5
6
7
8
9
客户端请求

OSD (OSD.cc)
├─> 路由到 PG
└─> PG (PG.cc)
├─> PeeringState (状态机)
├─> PGBackend (数据操作)
├─> PGLog (日志管理)
└─> Scrubber (数据校验)

二、核心业务功能

2.1 PG 生命周期管理

2.1.1 PG 初始化

1
PG::PG(OSDService *o, OSDMapRef curmap, const PGPool &_pool, spg_t p)

初始化流程:

  1. 设置 PG ID 和集合(Collection)
  2. 初始化 SnapMapper(快照映射器)
  3. 创建 PeeringState(peering 状态机)
  4. 初始化统计信息

关键代码位置:

  • PG::PG() (199行):构造函数
  • PG::init() (854行):初始化 PG 状态
  • PG::read_state() (1086行):从磁盘读取 PG 状态

2.1.2 PG 状态读取

1
void PG::read_state(ObjectStore *store)

流程:

  1. 从 ObjectStore 读取 PG 元数据
  2. 读取 PG 信息(info)和 PastIntervals
  3. 读取 PGLog(操作日志)
  4. 初始化 PeeringState
  5. 触发 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:激活 PG
  • AdvMap:OSDMap 推进
  • Query:查询对等节点状态
  • Notify:通知对等节点
  • Info:信息交换

处理流程:

1
2
3
4
5
6
7
8
9
10
11
收到 Peering 事件

验证事件有效性(epoch 检查)

调用 recovery_state.handle_event()

状态机转换

执行相应操作(PeeringCtx)

写入事务(write_if_dirty)

关键代码位置:

  • PG::do_peering_event() (2128行):处理 peering 事件
  • PG::queue_peering_event() (2142行):将事件加入队列
  • PG::handle_advance_map() (2194行):处理 map 推进

2.2.3 OSDMap 推进处理

1
2
3
4
5
void PG::handle_advance_map(
OSDMapRef osdmap, OSDMapRef lastmap,
vector<int>& newup, int up_primary,
vector<int>& newacting, int acting_primary,
PeeringCtx &rctx)

处理步骤:

  1. 检测 acting set 变化
  2. 检测 up set 变化
  3. 触发 Peering 状态机转换
  4. 处理 PG 分裂/合并
  5. 更新 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
2
3
void PG::requeue_op(OpRequestRef op)
void PG::requeue_ops(list<OpRequestRef> &ls)
void PG::requeue_map_waiters()

等待队列类型:

  • 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
2
3
4
5
6
7
8
9
10
11
12
13
queue_recovery()

计算恢复成本(平均对象大小)

加入恢复队列(osd->queue_for_recovery)

恢复调度器处理

选择恢复对象

执行恢复操作

finish_recovery_op()

关键代码位置:

  • PG::queue_recovery() (425行):将恢复加入队列
  • PG::start_recovery_op() (493行):开始恢复操作
  • PG::finish_recovery_op() (508行):完成恢复操作
  • PG::finish_recovery() (452行):完成所有恢复

2.4.2 恢复状态管理

1
2
void PG::clear_recovery_state()
void PG::cancel_recovery()

恢复状态:

  • 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
2
3
Scrub::schedule_result_t PG::start_scrubbing(
const Scrub::SchedEntry& candidate,
Scrub::OSDRestrictions osd_restrictions)

Scrub 条件检查:

  • PG 必须是活跃的(is_active())
  • 不能有 NOSCRUB 标志
  • 满足 scrub 间隔要求

关键代码位置:

  • PG::start_scrubbing() (1278行):启动 scrub
  • PG::scrub_requested() (1328行):scrub 请求处理
  • PG::on_scrub_schedule_input_change() (1314行):scrub 调度变化处理

2.6 快照管理

2.6.1 快照映射

1
2
3
4
void PG::update_object_snap_mapping(
ObjectStore *t, const hobject_t &soid, const set<snapid_t> &snaps)
void PG::clear_object_snap_mapping(
ObjectStore *t, const hobject_t &soid)

快照映射器(SnapMapper)作用:

  • 维护对象到快照的映射关系
  • 支持快照克隆和删除
  • 管理快照元数据

2.6.2 快照修剪

1
2
void PG::queue_snap_retrim(snapid_t snap)
void PG::on_active_actmap()

快照修剪流程:

  1. 检测需要修剪的快照
  2. 加入修剪队列(snap_trimq)
  3. 执行修剪操作
  4. 更新快照映射

关键代码位置:

  • 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)

分裂流程:

  1. 创建子 PG
  2. 复制父 PG 状态到子 PG
  3. 更新 SnapMapper 的 split_bits
  4. 复制快照修剪队列
  5. 执行数据分裂(_split_into)

关键代码位置:

  • PG::split_into() (528行):PG 分裂
  • PG::start_split_stats() (545行):开始分裂统计
  • PG::finish_split_stats() (550行):完成分裂统计

2.7.2 PG 合并

1
2
3
void PG::merge_from(map<spg_t,PGRef>& sources, PeeringCtx &rctx,
unsigned split_bits,
const pg_merge_meta_t& last_pg_merge_meta)

合并流程:

  1. 合并源 PG 的状态
  2. 合并 PGLog
  3. 合并集合(merge_collection)
  4. 更新 SnapMapper
  5. 删除源 PG 元数据

关键代码位置:

  • PG::merge_from() (555行):PG 合并

2.8 Backoff 机制

2.8.1 Backoff 概述

Backoff 用于在 PG 状态不稳定时阻止客户端操作:

1
2
3
4
void PG::add_backoff(const ceph::ref_t<Session>& s, 
const hobject_t& begin,
const hobject_t& end)
void PG::release_backoffs(const hobject_t& begin, const hobject_t& end)

Backoff 状态:

  • STATE_NEW:新建
  • STATE_ACKED:已确认
  • STATE_DELETING:删除中

使用场景:

  • Peering 过程中
  • 恢复过程中
  • PG 分裂/合并时

关键代码位置:

  • PG::add_backoff() (582行):添加 backoff
  • PG::release_backoffs() (608行):释放 backoff
  • PG::clear_backoffs() (670行):清除所有 backoff

2.9 资源预留机制

2.9.1 恢复资源预留

1
2
3
4
void PG::request_remote_recovery_reservation(
unsigned priority,
PGPeeringEventURef on_grant,
PGPeeringEventURef on_preempt)

资源类型:

  • 本地资源:本地 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
2
3
4
5
Initial → Reset → Started → Peering → Active

Incomplete

Down

状态转换触发:

  • 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
2
3
4
5
class PGLog {
IndexedLog log; // 操作日志
map<hobject_t, pg_missing_t> missing; // 缺失对象
// ...
};

3.4 锁机制

3.4.1 PG 锁

1
2
3
void PG::lock(bool no_lockdep) const
void PG::unlock() const
bool PG::is_locked() const

锁的作用:

  • 保护 PG 状态一致性
  • 防止并发修改
  • 确保操作原子性

锁的持有者:

  • 操作处理线程
  • Peering 线程
  • 恢复线程

3.5 事务机制

3.5.1 事务使用

1
2
3
4
5
6
7
8
9
void PG::prepare_write(
pg_info_t &info,
pg_info_t &last_written_info,
PastIntervals &past_intervals,
PGLog &pglog,
bool dirty_info,
bool dirty_big_info,
bool need_write_epoch,
ObjectStore::Transaction &t)

事务包含:

  • PG 信息更新
  • PGLog 写入
  • 对象操作
  • 元数据更新

3.6 引用计数管理

3.6.1 引用计数

1
2
void PG::get(const char* tag)
void PG::put(const char* tag)

引用计数用途:

  • 防止 PG 在使用中被删除
  • 跟踪 PG 使用情况
  • 调试内存泄漏

四、关键数据结构

4.1 PG 核心数据结构

1
2
3
4
5
6
7
8
9
class PG {
spg_t pg_id; // PG ID
coll_t coll; // 集合
pg_info_t info; // PG 信息
PeeringState recovery_state; // Peering 状态机
PGLog projected_log; // 投影日志
SnapMapper snap_mapper; // 快照映射器
// ...
};

4.2 等待队列

1
2
3
4
map<entity_name_t, list<OpRequestRef>> waiting_for_map;
list<OpRequestRef> waiting_for_peered;
list<OpRequestRef> waiting_for_flush;
list<OpRequestRef> waiting_for_clean_to_primary_repair;

4.3 恢复相关

1
2
3
bool recovery_queued;              // 是否已加入恢复队列
int recovery_ops_active; // 活跃恢复操作数
set<hobject_t> recovering_oids; // 正在恢复的对象

五、业务流程示例

5.1 客户端写操作流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
客户端发送写请求

OSD 路由到 PG

PG::can_discard_op() 检查

PG 状态检查
├─> 非活跃 → 加入 waiting_for_peered
└─> 活跃 → 继续处理

权限检查 (op_has_sufficient_caps)

路由到主/副本
├─> 主副本 → 执行写操作
└─> 副本 → 接收复制

更新 PGLog

提交事务

发送回复

5.2 Peering 流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
OSDMap 变更

handle_advance_map()

检测 acting set 变化

触发 Peering 事件

do_peering_event()

recovery_state.handle_event()

状态机转换
├─> Reset → Started
├─> Started → Peering
└─> Peering → Active

查询对等节点状态

确定权威数据

同步状态

激活 PG (on_activate)

处理等待的操作

5.3 恢复流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
检测到缺失对象

queue_recovery()

加入恢复队列

恢复调度器选择 PG

start_recovery_op()

选择恢复对象

从对等节点拉取数据

写入本地

finish_recovery_op()

继续下一个对象

所有对象恢复完成

finish_recovery()

触发 scrub(可选)

六、性能优化设计

6.1 异步处理

  • Peering 事件异步处理
  • 恢复操作异步执行
  • 操作等待队列异步唤醒

6.2 批量操作

  • 批量恢复对象
  • 批量更新统计信息
  • 批量处理等待操作

6.3 资源预留

  • 恢复资源预留避免冲突
  • Scrub 资源预留保证优先级
  • 本地 IO 资源预留

七、容错与可靠性

7.1 状态一致性

  • 通过 Peering 保证副本一致性
  • 通过 PGLog 追踪操作历史
  • 通过版本控制检测分歧

7.2 故障恢复

  • 自动检测缺失对象
  • 自动从对等节点恢复
  • 自动处理副本故障

7.3 数据校验

  • Scrub 检测数据不一致
  • 自动修复(auto-repair)
  • 校验和验证

八、总结

PG.cc 实现了 Ceph 存储系统的核心逻辑:

  1. 状态管理:通过 PeeringState 状态机管理 PG 生命周期
  2. 一致性保证:通过 Peering 和 PGLog 保证数据一致性
  3. 故障恢复:自动检测和恢复数据不一致
  4. 操作处理:路由和处理客户端操作
  5. 资源管理:管理恢复、scrub 等后台任务

整个设计体现了分布式系统的核心原则:

  • 状态机驱动:清晰的状态转换
  • 版本控制:精确的版本追踪
  • 异步处理:高效的并发处理
  • 容错设计:可靠的故障处理

PG 作为 Ceph 的核心抽象,承担了数据分片、一致性保证、故障恢复等关键职责,是整个存储系统稳定运行的基础。

文章互动

阅读 --

留言

0 条留言

正在加载留言…