Crimson OSD 三副本实现分析

Crimson OSD 三副本实现分析

概述

虽然 main.cc 是 Crimson OSD 的入口点,但三副本(replication)的实现并不在 main.cc 中。main.cc 主要负责 OSD 的启动和初始化,而三副本的核心逻辑在 ReplicatedBackend 类中实现。

架构概览

1
2
3
4
5
6
7
main.cc (启动 OSD)

OSD::start()

PG 创建和管理

ReplicatedBackend (三副本实现)

三副本实现位置

1. 核心类:ReplicatedBackend

文件位置

  • cephMain/src/crimson/osd/replicated_backend.h
  • cephMain/src/crimson/osd/replicated_backend.cc

继承关系

1
ReplicatedBackend : public PGBackend

2. 关键方法:submit_transaction

这是三副本写入的核心方法,负责将事务提交到所有副本。

三副本工作流程

1. 副本集合确定

副本集合由 PG 的 acting set 决定,通常包含 3 个 OSD:

  • Primary OSD(主副本,通常是当前 OSD)
  • Secondary OSD(第二副本)
  • Tertiary OSD(第三副本)
1
2
// 在 ReplicatedBackend::submit_transaction 中
const std::set<pg_shard_t> &pg_shards // 包含所有副本的 OSD 列表

2. 写操作流程

步骤 1: 创建待处理事务

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
// replicated_backend.cc:94-119
ReplicatedBackend::rep_op_fut_t
ReplicatedBackend::submit_transaction(
const std::set<pg_shard_t> &pg_shards, // 副本集合(通常 3 个)
const hobject_t& hoid,
crimson::osd::ObjectContextRef &&new_clone,
ceph::os::Transaction&& t,
osd_op_params_t&& opp,
epoch_t min_epoch, epoch_t map_epoch,
std::vector<pg_log_entry_t>&& logv)
{
// 1. 生成事务 ID
const ceph_tid_t tid = shard_services.get_tid();

// 2. 创建待处理事务记录
auto pending_txn = pending_trans.try_emplace(
tid,
pg_shards.size(), // 待确认的副本数量
osd_op_p.at_version,
pg.get_last_complete()
).first;

// 3. 编码事务
bufferlist encoded_txn_p_bl, encoded_txn_d_bl;
txn.encode(encoded_txn_p_bl, encoded_txn_d_bl, pg.min_peer_features());
}

步骤 2: 发送到所有副本

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
27
// replicated_backend.cc:136-167
// 遍历所有副本 OSD
for (auto &pg_shard : pg_shards) {
if (pg_shard == whoami) {
continue; // 跳过自己(主副本)
}

// 创建副本操作消息
MURef<MOSDRepOp> m;
if (pg.should_send_op(pg_shard, hoid)) {
// 发送完整操作
m = new_repop_msg(
pg_shard, hoid, encoded_txn_p_bl, encoded_txn_d_bl,
osd_op_p, min_epoch, map_epoch, log_entries, true, tid);
} else {
// 发送简化操作(如果副本已有数据)
m = new_repop_msg(..., false, tid);
}

// 记录待确认的副本
pending_txn->second.acked_peers.push_back({pg_shard, eversion_t{}});

// 发送消息到副本 OSD
sends->emplace_back(
shard_services.send_to_osd(
pg_shard.osd, std::move(m), map_epoch));
}

步骤 3: 本地提交

1
2
3
4
5
6
7
8
9
10
// replicated_backend.cc:169-176
// 主副本本地提交事务
pg.log_operation(
std::move(log_entries),
osd_op_p.pg_trim_to,
osd_op_p.at_version,
osd_op_p.pg_committed_to,
true,
txn,
false);

步骤 4: 等待所有副本确认

1
2
3
4
5
6
7
8
9
10
11
12
13
// replicated_backend.cc:178-197
auto all_completed = interruptor::make_interruptible(
shard_services.get_store().do_transaction(coll, std::move(txn))
).then_interruptible([this, peers=pending_txn->second.weak_from_this()] {
if (--peers->pending == 0) {
// 所有副本已确认(包括自己)
pg.complete_write(peers->at_version, peers->last_complete);
peers->all_committed.set_value();
return seastar::now();
}
// 等待其他副本的确认
return peers->all_committed.get_shared_future();
});

3. 副本确认处理

当副本 OSD 完成写入后,会发送 MOSDRepOpReply 消息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// replicated_backend.cc:231-249
void ReplicatedBackend::got_rep_op_reply(const MOSDRepOpReply& reply)
{
// 查找对应的事务
auto found = pending_trans.find(reply.get_tid());
auto& peers = found->second;

// 更新副本的确认状态
for (auto& peer : peers.acked_peers) {
if (peer.shard == reply.from) {
peer.last_complete_ondisk = reply.get_last_complete_ondisk();
pg.update_peer_last_complete_ondisk(
peer.shard, peer.last_complete_ondisk);

// 检查是否所有副本都已确认
if (--peers.pending == 0) {
pg.complete_write(peers.at_version, peers.last_complete);
peers.all_committed.set_value(); // 唤醒等待的 future
}
}
}
}

关键数据结构

1. pending_on_t - 待处理事务状态

1
2
3
4
5
6
7
8
// replicated_backend.h:51-71
class pending_on_t {
unsigned pending; // 待确认的副本数量
const eversion_t at_version; // 版本号
const eversion_t last_complete; // 最后完成版本
crimson::osd::acked_peers_t acked_peers; // 已确认的副本列表
seastar::shared_promise<> all_committed; // 所有副本确认的 promise
};

2. acked_peers_t - 已确认副本列表

1
2
3
4
5
6
// acked_peers.h:9-13
struct peer_shard_t {
pg_shard_t shard; // 副本 OSD
eversion_t last_complete_ondisk; // 磁盘上的最后完成版本
};
using acked_peers_t = std::vector<peer_shard_t>;

3. pending_transactions_t - 待处理事务映射

1
2
3
// replicated_backend.h:72
using pending_transactions_t = std::map<ceph_tid_t, pending_on_t>;
pending_transactions_t pending_trans; // 事务 ID -> 状态

三副本保证机制

1. 同步写入

  • 主副本必须等待所有副本(包括自己)确认后才完成写操作
  • 使用 shared_promiseshared_future 实现多等待者同步

2. 版本控制

  • 每个写操作都有唯一的版本号(at_version
  • 副本必须按顺序应用操作,保证一致性

3. 故障处理

1
2
3
4
5
6
7
8
9
// replicated_backend.cc:221-229
void ReplicatedBackend::on_actingset_changed(bool same_primary)
{
// 当副本集合改变时,取消所有待处理的事务
for (auto& [tid, pending_txn] : pending_trans) {
pending_txn.all_committed.set_exception(e_actingset_changed);
}
pending_trans.clear();
}

消息类型

1. MOSDRepOp - 副本操作请求

主副本发送给其他副本的操作请求,包含:

  • 事务数据(txn_payloaddata
  • 日志条目(log_entries
  • 版本信息
  • PG 统计信息

2. MOSDRepOpReply - 副本操作响应

副本 OSD 完成写入后发送的确认消息,包含:

  • 事务 ID(tid
  • 最后完成版本(last_complete_ondisk
  • 操作结果

与 main.cc 的关系

虽然 main.cc 不直接实现三副本逻辑,但它负责:

  1. 初始化 OSD
1
2
3
4
5
// main.cc:210-213
crimson::osd::OSD osd(
whoami, nonce, std::ref(should_stop.abort_source()),
std::ref(*store), cluster_msgr, client_msgr,
hb_front_msgr, hb_back_msgr);
  1. 启动 OSD
1
2
// main.cc:239
osd.start().get();
  1. 创建 Messenger
1
2
3
4
// main.cc:189-204
// 创建集群通信和客户端通信的 Messenger
crimson::net::MessengerRef cluster_msgr, client_msgr;
crimson::net::MessengerRef hb_front_msgr, hb_back_msgr;

这些组件为三副本通信提供了基础设施。

完整写操作流程示例

假设有 3 个副本:OSD 0(主)、OSD 1、OSD 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1. 客户端发送写请求到 OSD 0(主副本)

2. OSD 0 的 PG 处理请求,创建事务

3. ReplicatedBackend::submit_transaction()
├─ 创建 pending_txn (pending = 2,等待 OSD 1 和 OSD 2)
├─ 编码事务数据
├─ 发送 MOSDRepOp 到 OSD 1
├─ 发送 MOSDRepOp 到 OSD 2
├─ 本地提交事务
└─ 等待所有副本确认

4. OSD 1 接收 MOSDRepOp,执行写入,发送 MOSDRepOpReply

5. OSD 2 接收 MOSDRepOp,执行写入,发送 MOSDRepOpReply

6. OSD 0 收到所有回复
├─ got_rep_op_reply() 处理每个回复
├─ pending 减为 0
└─ all_committed.set_value() 唤醒等待

7. 写操作完成,返回客户端

性能优化

1. 异步发送

所有副本操作消息异步发送,不阻塞主流程:

1
2
sends->emplace_back(
shard_services.send_to_osd(pg_shard.osd, std::move(m), map_epoch));

2. 批量处理

使用 when_all_succeed 等待所有发送完成:

1
2
3
auto sends_complete = seastar::when_all_succeed(
sends->begin(), sends->end()
);

3. 条件发送

根据副本状态决定发送完整操作还是简化操作:

1
2
3
4
5
if (pg.should_send_op(pg_shard, hoid)) {
// 发送完整操作
} else {
// 发送简化操作(副本已有数据)
}

总结

三副本实现的核心特点:

  1. 位置:主要在 ReplicatedBackend 类中,不在 main.cc
  2. 机制:主副本发送操作到所有副本,等待所有确认
  3. 同步:使用 shared_promise/shared_future 实现同步
  4. 容错:副本集合变化时取消待处理事务
  5. 性能:异步发送,批量等待,条件优化

main.cc 的作用是启动整个系统,为三副本提供运行环境(OSD、Messenger、Store 等),但具体的副本逻辑由 ReplicatedBackend 实现。

文章互动

阅读 --

留言

0 条留言

正在加载留言…