Luminous 新特性:RBD 和 CephFS 的纠删码
Luminous 现在完全支持对纠删码 (EC) RADOS 池的覆盖写入,允许 RBD 和 CephFS(以及 RGW)直接使用纠删码池。 这有可能大大降低 Ceph 系统的每 TB 存储成本,因为复制通常的 3 倍存储开销可以降低到 1.2 倍到 2 倍,具体取决于所选的纠删码。
背景 ¶
Ceph 的 RADOS 对象存储最初是围绕复制构建的——通常是 2 倍或 3 倍。 这相对容易实现(尽管在 CP 分布式系统中仍然非常难以正确实现),并且为我们提供了良好的服务一段时间。 在 Firefly 版本(2014 年 5 月)中,我们添加了对纠删码 (EC) 池的支持。 纠删码允许 Ceph 将对象数据条带化到 k 个 OSD 上,同时添加 m 个额外的块,其中包含编码的冗余信息。 这就像传统 RAID 系统中的奇偶校验一样,但纠删码通常可以泛化到几乎任何 k 和 m 的值,而 RAID5 固定 m=1,RAID6 固定 m=2。 Ceph 的纠删码通过插件实现,包括传统的 Reed-Soloman 库(针对 Intel 和 ARM 架构进行了优化)、本地重构码 (LRC) 插件和 SHEC 错位纠删码。
然而,直到现在,EC 池只允许对象追加和删除——不支持覆盖写入。 这允许 EC 池用于 RGW 对象存储,并作为复制缓存层后面的冷层,但 RBD 和 CephFS 无法直接使用 EC 池。 鉴于纠删码可以显著降低系统的每 GB 存储成本,这让许多用户感到不满。
故障后的数据一致性 ¶
限制的原因是维护 一致性的难度,Ceph 一直对此非常重视。 如果更新已复制的对象时发生故障,无论有多少或哪些副本最终获得新版本或旧版本,恢复都是直接的。 在上面的示例中,对象的三个副本最初包含内容“A”,然后更新将其覆盖为“B”被故障中断,导致只有两个副本更新。 我们可以将第三个副本前进到 B 或将前两个副本回滚到 A;无论哪种选择都可以使系统保持一致。 在 RADOS 中,由于只有在所有副本更新后才会确认副本,因此两种选择都可以(我们当前会前进)。
对于纠删码对象,情况要复杂一些。 而不是三个相同且完整的副本,内容分布在(在上面的示例中)四个对象上,每个对象包含 1/4 的数据,以及两个纠删对象 p 和 q。 如果更新过程中发生故障,并且只有三个“A”对象使用新内容“B”更新,我们遇到了一个问题:4+2 纠删码需要任何 4 个分片才能重建原始内容,但我们只有三个新的和三个旧的,因此两者都无法重建。 这个问题是所谓的“RAID 洞”的泛化版本。 许多系统(甚至在生产中广泛使用的系统)都会掩盖这个问题,而且大多数情况下他们都能成功,通常是因为在系统崩溃之前提交写入的人仍然会在那里重新提交它并覆盖问题。 我们从未认为这种选择是可以接受的。
为了从这种情况下恢复,RADOS 必须能够将 B1、B2 和 B3 对象回滚到它们之前的状态,然后再进行更新,以便恢复系统一致性。 对于现有的纠删码池,我们通过只允许追加来实现这一点,这意味着我们可以通过截断新内容来回滚。 然而,直到现在,维护覆盖数据回滚信息成本很高:我们需要读取旧内容,将其写入临时位置,然后写入新内容,稍后(在确定所有分片都已应用更新后)丢弃临时回滚数据。 有了 BlueStore,我们终于可以实现我们需要的底层原语。 在这种情况下,BlueStore 只是创建对磁盘上即将被覆盖的旧块的逻辑引用,逻辑上用新数据覆盖它(将新内容写入新位置),然后丢弃旧引用。 最终结果是,在发生故障后,任何部分应用的更新都可以回滚,以便存储在 RADOS 中的数据始终处于一致状态。
创建具有覆盖写入的纠删码池 ¶
创建纠删码池的工作方式与以前类似。 首先,您需要决定要使用哪种纠删码。 这通常意味着选择 k 和 m 的值。 m 值控制您可以容忍多少次故障,而不会丢失数据。 通常, m=2 将提供与 3 倍复制相同的数据保护级别。 k 值确定对象的数据将条带化到多少个 OSD 上。 较大的 k 值意味着较低的存储开销(因为您需要为存储的每 k 字节的用户数据存储 k+m 字节),但较大的值也意味着任何写入(和许多读取)都需要与更多的设备通信,从而增加延迟。
在此示例中,我们将创建一个可与 RBD 配合使用的纠删码池。 我们选择 k=4 和 m=2,这意味着我们的存储开销是 1.5 倍(是 3 倍复制成本的一半,太棒了!),但我们没有让 太多的 OSD 参与每个 IO。
首先,我们创建我们的 纠删码配置文件,我们将其命名为“ec-42-profile”,它还指定了我们想要使用的故障域和 CRUSH 设备类。 (指定设备类 是 Luminous 中的一个可选但方便的新功能。)
$ ceph osd erasure-code-profile set ec-42-profile k=4 m=2 crush-failure-domain=host crush-device-class=ssd
然后我们使用此配置文件创建名为“ec42”的纠删码池
$ ceph osd pool create ec42 64 erasure ec-42-profile
此命令基于配置文件(描述我们的故障域和设备类)创建 CRUSH 规则,并具有 64 个 PG。 您可能需要根据集群的大小选择不同的 PG 值。
接下来,我们在新池上启用覆盖写入(以便它可以用于 RBD 或 CephFS)
$ ceph osd pool set ec42 allow_ec_overwrites true
将 EC 池与 RBD 配合使用 ¶
要将其标记为 RBD 池,首先对其进行标记
$ ceph osd pool application enable ec42 rbd
RBD 可以将图像数据存储在 EC 池中,但图像标头和元数据仍然需要存储在复制池中。 假设您有通常名为“rbd”的池用于此目的,
$ rbd create rbd/myimage --size 1T --data-pool ec42
然后可以使用该图像正常使用,就像任何其他图像一样,除了所有数据都将存储在“ec42”池而不是“rbd”池中。 请注意,客户端必须使用 Luminous(或更高版本)的 librbd; Ceph 的旧版本(例如 Jewel)不支持数据池功能。
将 EC 池与 CephFS 配合使用 ¶
要将池标记为 CephFS 数据池,请对其进行标记
$ ceph osd pool application enable ec42 cephfs
然后将其添加为文件系统的的数据池。 假设您正在使用“default”文件系统,
$ ceph fs add_data_pool default ec42
然后可以标记应存储在新池中的特定目录。 例如,如果 CephFS 挂载在 /ceph 上,并且 您希望 /ceph/logs 中的所有内容都使用“ec42”池存储,
$ setfattr -n ceph.dir.layout -v pool=ec42 /ceph/logs
这将导致在 /ceph/logs(或该点之下的任何子目录)中创建的任何新文件将数据放置在新池中。
将 EC 池与 RGW 配合使用 ¶
RGW 从 Firefly 开始支持使用纠删码池进行对象数据。 该过程与以前相同。 需要注意的是,不必在 RGW 数据池上启用覆盖写入(尽管这样做不会造成任何损害)。
速度有多快? ¶
一如既往,取决于具体情况。
如果您正在将大量数据写入大对象,EC 池通常比复制池更快:写入的数据更少(仅为提供的 1.5 倍,而不是复制的 3 倍)。 然而,OSD 进程消耗的 CPU 比以前多,因此如果您的服务器速度较慢,您可能无法实现加速。 大型或流式读取的性能与以前大致相同。
但是,小写入比复制慢,主要有两个原因。
- 首先,所有写入都必须更新完整的条带(所有 k + m 个 OSD),这通常比您拥有的副本数量更多(在上面的示例中为 6 个与 3 个)。 这会增加延迟。
- 其次,如果写入仅更新条带的一部分,我们需要读取条带的先前值(来自所有 k + m 个 OSD),进行更新,重新编码,然后将更新后的分片写出。 因此,我们倾向于默认情况下使条带非常小(通过牺牲一些 CPU 开销来降低部分条带更新的可能性),但问题并不总是消失。 有一些系统在这些问题上撒上不同程度的巧妙之处,以延迟条带更新或避免触及未更改的分片,但我们尚未在此处实现任何复杂的功能。
一如既往,您应该在您的工作负载上尝试一下,看看效果如何!
限制 ¶
在 librados 级别,纠删码池看起来就像复制池一样,因此 CephFS 和 RBD 几乎不需要更改即可从该功能中受益。 唯一的例外是,EC 池不支持 RADOS 对象的“omap”部分,该部分允许您将任意键/值数据存储在每个对象中。 这就是为什么 RBD 图像仍然看起来属于正常的复制池,但具有特殊的“数据池”属性——RBD 标头对象使用 omap 并且无法存储在 EC 池中。
总结 ¶
擦除编码是一种有价值的工具,可以减少容忍设备故障所需的总体存储开销。 至今,擦除编码只能与 RGW 用于对象存储,但从 Luminous 版本开始,它也可以用于 RBD 和 CephFS 工作负载。