注意

本文档适用于 Ceph 开发版本。

基于日志的PG

背景

为什么是PrimaryLogPG?

目前,所有 Ceph 池类型的consistency是通过主日志复制来确保的。这适用于纠删码(EC)池和复制池。

主基于日志的复制

读取必须返回任何完成的写入所写入的数据(客户端可能已经收到提交消息)。有很多方法来处理这种情况,但 Ceph 的架构使得每个人在任何映射周期都能轻松知道主节点是谁。因此,简单的答案是让特定 PG 的所有写入都通过单个排序主节点,然后转发到副本。虽然我们实际上只需要在一个 RADOS 对象上序列化写入(即使这样,部分排序只需要在重叠区域之间的写入提供排序),但我们可以整个 PG 上序列化写入,因为它让我们能够使用两个数字来表示 PG 的当前状态:主节点上映射的周期,在该周期中最新的写入开始(这比它看起来要奇怪一些,因为映射分布本身是异步的——参见对等和区间变化的概念)和递增的每个 PG 版本号——这在代码中用类型eversion_t表示,并存储为pg_info_t::last_update。此外,我们维护一个“最近”操作的日志,至少可以回溯到包含任何不稳定的写入(已开始但未提交的写入)和本地未更新的对象的日志(参见恢复和回填)。实际上,日志会延伸得更远osd_min_pg_log_entries当干净时osd_max_pg_log_entries当不干净时),因为它便于快速执行恢复。

使用此日志,只要我们与必须接受我们最近接受的写入的 OSD 的非空子集交谈,我们就可以确定一个保守的日志,该日志必须包含任何报告给客户端作为已提交的写入。这里有一些自由度,我们可以选择该集合中记住的最老的头(任何更新的都不能在没有该日志的情况下完成)和记住的最新头(显然,日志中的所有写入都已开始,因此我们记住它们是好的)作为新的头。这是复制池和 EC 池在PG/PrimaryLogPG之间的主要分歧点:复制池试图选择最新的有效选项,以避免客户端需要重放那些操作,而是恢复其他副本。EC 池则试图选择最旧的可用的选项。

这样做的原因涉及到其余实现差异的核心:一个副本通常不足以重建 EC 对象。实际上,有一些编码方式,某些日志组合会留下无法恢复的对象(就像一个k=4,m=2编码,其中 3 个副本记住一个写入,但另外 3 个不记得——我们没有 3 个版本的副本)。因此,出于这个原因,代表不稳定的写入(尚未提交给客户端的写入)的日志条目必须仅使用 EC 池的本地信息进行回滚。因此,通常情况下,日志条目可能是可回滚的(在这种情况下,通过延迟应用或通过一组指令来回滚就地更新),也可能不是。复制池的日志条目永远无法回滚。

更多详情,请参阅PGLog.h/cc, osd_types.h:pg_log_t, osd_types.h:pg_log_entry_t,以及一般意义上的对等。

ReplicatedBackend/ECBackend统一策略

PGBackend

复制和纠删码之间的基本区别在于,复制可以进行破坏性更新,而纠删码不能。如果我们需要有两个完整的PrimaryLogPG实现,那将非常烦人,因为实际上只有几个基本区别:

  1. 读取如何工作——仅异步,EC 需要远程读取

  2. 写入如何工作——限制为追加,或必须另存并执行

  3. 在对等期间,我们选择最老或最新的可能头条目

  4. 日志条目中的一些额外信息以启用回滚

以及许多相似之处

  1. 对象的所有统计信息和元数据

  2. 混合客户端 IO 与恢复和清理的高级锁定规则

  3. 混合读取和写入的高级锁定规则,而不会暴露未提交状态(这可能被回滚或遗忘)

  4. 确定参与我们最近接受的写入的 OSD 集合的过程、元数据和协议

  5. 等。

相反,我们选择一些抽象(和一些笨拙的解决方案)来掩盖这些差异:

  1. PGBackend

  2. PGTransaction

  3. PG::choose_acting选择calc_replicated_actingcalc_ec_acting

  4. 写入管道的各个部分根据池类型禁止某些操作——比如 omap 操作、类操作读取,以及对于 EC 来说不是对齐追加的写入(到目前为止,官方的)

  5. 在这里和那里有一些其他的笨拙解决方案

PGBackendPGTransaction使上述差异 1 和 2 的差异抽象化,并在需要时将 4 添加到日志条目中。

复制实现位于ReplicatedBackend.h/cc,不需要太多额外的解释。更多关于ECBackend的细节可以在doc/dev/osd_internals/erasure_coding/ecbackend.rst.

PGBackend接口说明

中找到。

可读与退化

对于复制池,对象可读当且仅当它在主节点上存在(在正确的版本)。对于 EC 池,我们需要至少m片段存在才能执行读取,并且它需要在主节点上。因此,PGBackend需要包括一些接口来确定何时需要恢复以服务读取而不是写入。这也改变了当对等有足够的日志来证明

时对等的规则。

  • PGBackend需要能够返回IsPG(Recoverable|Readable)Predicate
    对象以允许用户做出这些决定。

客户端读取

从复制池读取总是可以由主 OSD 同步满足。在对等编码池中,主节点需要从一些副本请求数据才能满足读取。PGBackend因此,需要提供objects_read_syncobjects_read_async接口,其中前者不会由ECBackend.

PGBackend接口:

  • objects_read_sync

  • objects_read_async

刷写

我们目前有两种清理模式,具有不同的默认频率:

  1. [浅层] 清理:比较对象和元数据集,但不比较内容

  2. 深层清理:比较对象、元数据以及对象内容的 CRC32(包括 omap)

主节点请求每个副本在特定对象范围内的清理映射。副本填写此清理映射,包括如果清理是深层的,则每个对象的内容的 CRC32。主节点从每个副本收集这些清理映射,并执行比较以识别不一致的对象。

大部分内容可以基本上不变地适用于纠删码 PG,但需要注意PGBackend实现必须负责实际执行扫描。

PGBackend接口:

  • be_*

恢复

恢复对象的逻辑取决于后端。在当前的复制策略下,我们首先将对象副本拉到主节点,然后并发地推送到副本。在对等编码策略下,我们可能想要读取重建对象所需的最少副本片段,并并发地推送替换片段。

另一个区别是,纠删码 PG 中的对象可能在没有丢失的情况下无法恢复。应该将unfound状态重命名为unrecoverable。此外,实现必须能够引导对不可恢复对象片段的搜索,并能够确定特定对象是否可恢复。PGBackend implementation will have to be able to direct the search for PG replicas with unrecoverable object chunks and to be able to determine whether a particular object is recoverable.

Core changes:

  • s/unfound/unrecoverable

PGBackend 接口:

由 Ceph 基金会带给您

Ceph 文档是一个社区资源,由非盈利的 Ceph 基金会资助和托管Ceph Foundation. 如果您想支持这一点和我们的其他工作,请考虑加入现在加入.