注意

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

ECBackend 实现策略

杂项初始设计注释

EC 池的初始设计(对于未启用 EC 覆盖调试标志的 EC 池仍然如此)限制了 EC 池的操作必须能够轻松回滚

  • CEPH_OSD_OP_APPEND:我们可以通过在 PG 日志事件中包含前一个对象大小来在本地回滚追加操作。

  • CEPH_OSD_OP_DELETE:回滚删除操作的可能性要求我们保留被删除的对象,直到所有副本都持久化了删除事件。因此,纠删码后端需要将对象的版本包含在提供给 filestore 的键中来存储对象。当所有副本都提交了删除该对象的日志事件时,可以修剪旧版本的对象。

  • CEPH_OSD_OP_(SET|RM)ATTR:如果我们包含要设置或删除的属性的先前值,我们可以本地回滚这些操作。

日志条目包含一个结构,解释如何本地撤销操作所代表的操作(参见 osd_types.h:TransactionInfo::LocalRollBack)。

PGTemp 和 Crush

主 OSD 能够请求一个临时的 acting set 映射,以便允许一个最新的 OSD 在新的主 OSD 正在回填时(以及出于其他原因)服务请求。一个纠删码 PG 需要能够出于这些原因指定一个主 OSD,而无需将其置于 acting set 的第一位。它还需要能够在请求的 acting set 中留下空位。

核心变更

  • OSDMap::pg_to_*_osds 需要单独返回一个主 OSD。对于大多数情况,这可以继续是 acting[0]。

  • MOSDPGTemp(和相关的 OSD 结构)需要能够指定一个主 OSD 以及一个 acting set。

  • 现有代码库的很大一部分假设 acting[0] 是主 OSD,并且 acting 中的所有元素都有效。由于 acting set 可能包含空位,这需要进行清理。

区别对待的 acting set 位置

在复制策略中,PG 的所有副本都是可互换的。在纠删码中,acting set 中的不同位置具有纠删码方案的不同部分,并且不可互换。更糟糕的是,crush 可能会导致块 2 被写入到恰好已经包含块 4 的(旧)副本的 OSD 上。这意味着 OSD 和 PG 消息需要以 pair<shard_t, pg_t> 这样的类型来工作,以区分单个 OSD 上的不同 PG 块。

因为 filestore 中对象名称到对象的映射必须是 1 对 1 的,我们必须确保块 2 和块 4 中的对象具有不同的名称。为此,对象存储必须在对象键中包含块 ID。

核心变更

  • 对象存储 ghobject_t 需要包含一个 chunk id,使其更像 tuple<hobject_t, gen_t, shard_t>。

  • coll_t 需要包含一个 shard_t。

  • OSD pg_map 和类似的 PG 映射需要以 spg_t(本质上是 pair<pg_t, shard_t>)来工作。类似地,pg->pg 消息需要包含一个 shard_t

  • 对于 client->PG 消息,OSD 需要一种方法来知道哪个 PG 块应该接收消息,因为 OSD 可能包含同一 PG 的主块和非主块

对象类

通过调用特殊的 SYNC 读取,从对象类进行的读取将返回 EC 池上的 ENOTSUP。

Scrub(擦洗)

然而,对于 EC 池来说,主要的问题是发送副本上存储块的 crc32 并不是特别有用,因为不同副本上的块可能存储不同的数据。然而,由于我们除了通过 DELETE 之外不支持覆盖,我们有一个选项,即通过每次追加来维护每个块上的 crc32。因此,每个副本只需计算其自己的存储块的 crc32,并将其与本地存储的校验和进行比较。然后,副本向主 OSD 报告校验和是否匹配。

对于覆盖操作,目前所有擦洗都已禁用,直到我们弄清楚该怎么做(参见 doc/dev/osd_internals/erasure_coding/proposals.rst)。

Crush

如果 crush 无法为 acting set 中下线的成员生成替换成员,则 acting set 应该在该位置有一个空位,而不是将 acting set 的其他元素移位。

ECBackend

主要操作概述

一个 RADOS put 操作可以跨越单个对象的多个条带。必须有代码将应用程序级别的写入分解为一组按条带进行的写入操作——一些完整的条带和最多两个部分条带。不失一般性,对于本文档的其余部分,我们将只关注写入单个条带(完整或部分)。我们将使用符号“W”来表示正在写入的条带中的块数,即 W <= K。

处理写入 EC 条带的数据流有三种。选择哪种数据流取决于写入操作的大小和所选奇偶校验生成算法的算术特性。

  1. 写入/覆盖整个条带

  2. 执行读-修改-写操作。

整个条带写入

这是一个简单的情况,并且在现有代码中已经执行(即对于追加操作)。主 OSD 在 RADOS 请求中接收到条带的所有数据,计算适当的奇偶校验块,并将数据和奇偶校验块发送到其目标分片进行写入。这本质上是当前的 EC 代码。

读-修改-写

主 OSD 确定 K-W 个块中哪些是未修改的,并从分片中读取它们。一旦接收到所有数据,它将与接收到的新数据结合起来,并计算新的奇偶校验块。修改后的块被发送到各自的分片并写入。RADOS 操作得到确认。

OSD 对象写入和一致性

无论选择哪种算法,数据的写入都是一个两阶段过程:提交和向前滚动(rollforward)。主 OSD 发送包含所描述操作的日志条目(参见 osd_types.h:TransactionInfo::(LocalRollForward|LocalRollBack))。在所有情况下,“提交”都在原地执行,可能在写入旁路对象中留下回滚所需的一些信息。向前滚动阶段在所有 acting set 副本都提交了提交后发生,然后它会删除回滚信息。

在覆盖现有条带的情况下,回滚信息采用稀疏对象的形式,其中包含被覆盖扩展的旧值,使用 clone_range 填充。这本质上是一个占位符实现,在实际生活中,bluestore 将有一个高效的原语来处理这个问题。

向前滚动部分可以延迟,因为一旦所有副本都已提交,我们就会报告操作已提交。目前,每当我们发送写入时,我们也会指示所有先前提交的操作都应该向前滚动(参见 ECBackend::try_reads_to_commit)。如果在我们到达 waiting_rollforward 队列时没有排队的操作,我们就会启动一个虚拟写入来推进(参见后面的 Pipeline 部分和 ECBackend::try_finish_rmw)。

ExtentCache(扩展缓存)

能够对同一对象进行流水线写入非常重要。因此,有一个由可缓存操作写入的扩展缓存。每个扩展都保持固定,直到引用它的操作被提交。流水线阻止 rmw 操作运行,直到不可缓存的事务(克隆等)从流水线中刷新。

有关缓存状态如何对应于关于并发操作可以引用同一对象的条件的高级不变量的详细解释,请参阅 ExtentCache.h。

流水线

阅读 src/osd/ExtentCache.h 应该能很好地了解操作是如何重叠的。处理写入操作涉及几个状态,并且有一个重要不变量不是由 PrimaryLogPG 在更高层次上强制执行的,需要由 ECBackend 来管理。重要的不变量是,我们不能在同一对象上同时运行不可缓存和 rmw 操作。为简单起见,我们强制要求任何包含 rmw 操作的操作必须等到所有正在进行的不可缓存操作完成。

未来将在此处进行改进。

有关更多详细信息,请参阅 ECBackend::waiting_* 和 ECBackend::try_<from>_to_<to>。

由 Ceph 基金会为您呈现

Ceph 文档是由非营利性 Ceph 基金会 资助和托管的社区资源。如果您希望支持这项工作和我们的其他努力,请考虑 立即加入