注意

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

纠删码开发人员注释

简介

本文档的每一章都解释了 Ceph 中纠删码实现的一个方面。它主要基于示例进行解释,以演示事物的工作原理。

从 OSD 读取和写入编码块

纠删码存储池将每个对象存储为 K+M 个块。它被分为 K 个数据块和 M 个编码块。存储池配置为具有 K+M 个大小,以便每个块存储在活动集中的一个 OSD 中。块的排名作为对象的属性存储。

假设创建了一个纠删码存储池以使用五个 OSD ( K+M = 5 ) 并承受其中两个 OSD 的丢失 ( M = 2 )。

当包含 ABCDEFGHI 的对象 NYAN 被写入其中时,纠删码编码功能将内容分成三个数据块,简单地将内容分为三部分:第一部分包含 ABC,第二部分包含 DEF,最后一部分包含 GHI。如果内容长度不是 K 的倍数,则内容将进行填充。该功能还会创建两个编码块:第四个包含 YXY,第五个包含 GQC。每个块存储在活动集中的一个 OSD 中。这些块存储在具有相同名称 ( NYAN ) 但位于不同 OSD 上的对象中。创建块的顺序必须保留,并作为对象的属性 ( shard_t ) 存储,除了其名称之外。块 1 包含 ABC 并存储在 OSD5 上,而块 4 包含 YXY 并存储在 OSD3 上。

                          +-------------------+
                     name |        NYAN       |
                          +-------------------+
                  content |      ABCDEFGHI    |
                          +--------+----------+
                                   |
                                   |
                                   v
                            +------+------+
            +---------------+ encode(3,2) +-----------+
            |               +--+--+---+---+           |
            |                  |  |   |               |
            |          +-------+  |   +-----+         |
            |          |          |         |         |
         +--v---+   +--v---+   +--v---+  +--v---+  +--v---+
   name  | NYAN |   | NYAN |   | NYAN |  | NYAN |  | NYAN |
         +------+   +------+   +------+  +------+  +------+
  shard  |  1   |   |  2   |   |  3   |  |  4   |  |  5   |
         +------+   +------+   +------+  +------+  +------+
content  | ABC  |   | DEF  |   | GHI  |  | YXY  |  | QGC  |
         +--+---+   +--+---+   +--+---+  +--+---+  +--+---+
            |          |          |         |         |
            |          |          |         |         |
            |          |       +--+---+     |         |
            |          |       | OSD1 |     |         |
            |          |       +------+     |         |
            |          |       +------+     |         |
            |          +------>| OSD2 |     |         |
            |                  +------+     |         |
            |                  +------+     |         |
            |                  | OSD3 |<----+         |
            |                  +------+               |
            |                  +------+               |
            |                  | OSD4 |<--------------+
            |                  +------+
            |                  +------+
            +----------------->| OSD5 |
                               +------+

当从纠删码存储池读取对象 NYAN 时,解码功能读取三个块:包含 ABC 的块 1、包含 GHI 的块 3 和包含 YXY 的块 4,并重建对象的原始内容 ABCDEFGHI。解码功能被告知块 25 丢失了(它们被称为 erasures)。块 5 无法读取,因为 OSD4 处于 out 状态。

一旦读取了三个块,就可以调用解码功能: OSD2 是最慢的,它的块不需要考虑在内。此优化未在 Firefly 中实现。

                          +-------------------+
                     name |        NYAN       |
                          +-------------------+
                  content |      ABCDEFGHI    |
                          +--------+----------+
                                   ^
                                   |
                                   |
                            +------+------+
                            | decode(3,2) |
                            | erasures 2,5|
            +-------------->|             |
            |               +-------------+
            |                     ^   ^
            |                     |   +-----+
            |                     |         |
         +--+---+   +------+   +--+---+  +--+---+
   name  | NYAN |   | NYAN |   | NYAN |  | NYAN |
         +------+   +------+   +------+  +------+
  shard  |  1   |   |  2   |   |  3   |  |  4   |
         +------+   +------+   +------+  +------+
content  | ABC  |   | DEF  |   | GHI  |  | YXY  |
         +--+---+   +--+---+   +--+---+  +--+---+
            ^          .          ^         ^
            |    TOO   .          |         |
            |    SLOW  .       +--+---+     |
            |          ^       | OSD1 |     |
            |          |       +------+     |
            |          |       +------+     |
            |          +-------| OSD2 |     |
            |                  +------+     |
            |                  +------+     |
            |                  | OSD3 |-----+
            |                  +------+
            |                  +------+
            |                  | OSD4 | OUT
            |                  +------+
            |                  +------+
            +------------------| OSD5 |
                               +------+

纠删码库

使用参数 K+M 的 Reed-Solomon,通过将对象 O 分成块 O1, O2, … OM 并计算编码块 P1, P2, … PK 来对对象 O 进行编码。可以使用可用的 K+M 块中的任意 K 个块来获取原始对象。如果数据块 O2 或编码块 P2 丢失,可以使用 K+M 块中的任意 K 个块进行修复。如果丢失的块多于 M 个,则无法恢复对象。

读取对象 O 的原始内容可以是 O1, O2, … OM 的简单拼接,因为插件使用的是 系统码。否则,必须将块提供给纠删码库的 decode 方法以检索对象的内容。

性能取决于编码函数的参数,也受调用编码函数时使用的包大小(例如 Cauchy 或 Liberation)的影响:较小的包意味着更多的调用和更多的开销。

尽管提供了 Reed-Solomon 作为默认设置,但 Ceph 通过 抽象 API 使用它,该 API 旨在允许每个存储池选择使用存储在 纠删码配置文件 中的 key=value 对来实现它的插件。

$ ceph osd erasure-code-profile set myprofile \
    crush-failure-domain=osd
$ ceph osd erasure-code-profile get myprofile
directory=/usr/lib/ceph/erasure-code
k=2
m=1
plugin=isa
technique=reed_sol_van
crush-failure-domain=osd
$ ceph osd pool create ecpool erasure myprofile

插件目录 动态加载,并期望实现 int __erasure_code_init(char *plugin_name, char *directory) 函数,该函数负责在注册表中注册派生自 ErasureCodePlugin 的对象。 ErasureCodePluginExample 插件读取

ErasureCodePluginRegistry &instance =
                           ErasureCodePluginRegistry::instance();
instance.add(plugin_name, new ErasureCodePluginExample());

派生自 ErasureCodePlugin 的对象必须提供一个工厂方法,从中可以生成 ErasureCodeInterface 对象的具体实现。 ErasureCodePluginExample 插件 读取

virtual int factory(const map<std::string,std::string> &parameters,
                    ErasureCodeInterfaceRef *erasure_code) {
  *erasure_code = ErasureCodeInterfaceRef(new ErasureCodeExample(parameters));
  return 0;
}

parameters 参数是在创建存储池之前在纠删码配置文件中设置的 key=value 对列表。

ceph osd erasure-code-profile set myprofile \
   directory=<dir>         \ # mandatory
   plugin=isa              \ # mandatory
   m=10                    \ # optional and plugin dependent
   k=3                     \ # optional and plugin dependent
   technique=reed_sol_van  \ # optional and plugin dependent

注意事项

如果对象很大,在内存中对其进行编码和解码可能不切实际。然而,当使用 RBD 时,一个 1TB 的设备被分成许多单独的 4MB 对象,并且 RGW 也是如此。

编码和解码是在 OSD 中实现的。尽管它可以为读写在客户端实现,但 OSD 在 scrubbing 时必须能够自行编码和解码。

由 Ceph 基金会为您呈现

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