分布式存储系统 Ceph 的后端存储引擎研究

文章目录

摘要

Ceph 是一个可靠的、自治的、可扩展的分布式存储系统,它支持文件系统存储、块存储、对象存储三种不同类型的存储,满足多样存储的需求。Ceph 沿袭了传统在本地文件系统上面构建存储后端的方法,并受益于十年来经过测试的代码的便利性和成熟性,但是,在带来便利的同时,也相应地限制了 Ceph 的发展,比如,原始 Ceph 后端存储引擎 FileStore 需要先将磁盘操作转换为服务端主机能够识别的文件操作,而这个转换的过程却相当繁杂。除此以外,零开销事务机制的挑战,元数据的管理,以及新型存储硬件的生产,也推动着 Ceph 后端存储的不断发展,于是 2015 年 Ceph 开发团队提出了 BlueStore 新型后端存储。BlueStore 直接将对象数据保存在原始块设备中,绕过本地文件系统层,避免了文件系统开销和其性能缺陷,并采用 RocksDB 管理其元数据,提高了存储效率。BlueStore 表现出来的优越性,使得其在生产中被 70\%的用户所采用。此次报告将主要针对 Ceph 现有的存储引擎进行多方位的对比并进行测试,进一步理解分布式后端存储的实现原理。

引言

随着近年来多媒体技术和互联网技术的爆发式发展,使得信息技术领域中的数据规模越来越大,传统的企业在信息化的浪潮下也不断地开始将数据信息化,云计算的概念应运而生。云计算的三架马车:计算、网络和存储也逐渐成为工业界和学术界重点研究的领域。作为三驾马车之一的存储,传统的存储系统无法满足呈爆炸性增长的海量数据存储需求,为解决信息存储容量、数据备份、数据安全等问题,分布式存储系统应运而生,在近年来的研发过程中也产生了很多优秀的产品,应用到了实际的生产环境中。Ceph 则是众多优秀开源产品中的一个,作为现如今在云计算领域应用最为广泛的分布式存储系统,其高扩展性、高性能和高可靠性等特点使得 Ceph 成为了云计算厂商的最佳选择。Ceph 是由学术界于 2006 年提出的分布式存储系统的解决方案,一开始致力于解决分布式文件系统中存在的问题,发展到现在已经不再仅仅支持文件系统的存储需求,还提供了块设备和对象存储的接口,使得 Ceph 能够应用到更多的生产环境和实际应用场景。

本文阐述了分布式存储系统 Ceph 的架构和实现原理,主要针对 Ceph 底层的三种存储引擎进行介绍。结合 Ceph 后端存储引擎的发展历程,来深入比较文件系统、键值存储系统和裸设备作为存储后端的优劣,并延伸到文件系统、键值存储系统和块设备裸盘 IO 各自独立的应用和局限,旨在帮助学习研究人员进一步了解 Ceph 的存储后端和现如今应用广泛的各类存储引擎。

Ceph 概述

2.1 Ceph 架构

Ceph 是一个可靠的、自治的、可扩展的分布式存储系统,它支持文件系统存储、块存储、对象存储三种不同类型的存储,满足多样存储的需求。Ceph 整体架构包含最上层的存储接口层,用来提供客户端访问存储后端服务的各种接口,Ceph 支持块存储、文件存储和对象存储,其中间层是 librados,它用来提供各种访问底层 RADOS 集群存储系统的各种库函数。RADOS 层是 Ceph 的重要组成部分,它提供给用户一个完整的存储系统,其主要组成部分是 Monitors、OSDs、MDS,其中 Monitor 主要功能是用来监视 Ceph 集群的健康状态,并保存各种 map(节点健康状况、逻辑和物理关系映射等)信息,OSDs 是运行在存储设备上的主要用于存储数据的守护进程,它以 PG 为单位进行数据的存储、迁移、和恢复,MDS 是文件系统的元数据管理服务进程,主要针对 CephFS 中的元数据信息进行管理。在 librados 的基础之上又根据具体的上层应用需求抽象出了块设备的库 librbd、对象存储的库 librgw 和文件系统库 libcephfs。Ceph 架构分别如下图 2.1 和图 2.2 所示。

如图

图 2.1 Ceph 整体架构

如图

图 2.2 Ceph 分层架构

2.2 Ceph IO 流程

此处以 CephFS 为例简要介绍 Ceph 的 IO 流程。首先由客户端发起 IO 请求,对提供的文件系统接口的 IO 请求可以大致分为文件数据 IO 请求和元数据 IO 请求。元数据 IO 请求对应地和元数据管理服务集群进行通信即可,此处重点介绍文件数据的 IO 处理流程。客户端发起对应的文件 IO,调用 libcephfs 的相关接口,libcephfs 作为 librados 的客户端,会运行简单的 HASH 路由算法计算此次 IO 请求对应的 PG,再根据 CRUSH 算法计算出 PG 对应的 OSD 组,返回 OSD 信息给客户端即 libcephfs 之后,对应地客户端将读写请求发送到 OSD 上进行处理。而 OSD 集群中的每一个 OSD 进程都运行在独立的存储驱动器上,OSD 接收到对应的 IO 请求之后,根据对应的后端存储类型,执行对应后端存储引擎的 IO 操作,如选择了文件系统作为后端存储引擎,则相应地需要将 IO 请求转化为文件的读写。

如图

图 2.3 Ceph IO 流程

Ceph 的核心是分布式对象存储系统 RADOS,RADOS 可以扩展为数千个 OSD,并提供自我管理,自我修复,强一致性保障的副本策略等功能。在客户端侧,Ceph 的库文件 librados 提供了事务性接口,能够对 RADOS 进行事务性的对象操作、对象集合操作。

RADOS 中的对象保存在一个逻辑分区——存储池(pool)中。Pool 通过多副本或纠删码两种方式实现了数据冗余的功能。在每个 pool 中,对象被划分到不同的放置组(Placement Group, PG)。在 Ceph 的数据冗余策略中,数据以 PG 为单位进行保存,PG 是副本划分,数据迁移的最小单位。PG 通过一种伪随机的哈希算法——CRUSH 算法分配到不同的 OSD 中,客户端在了解集群的拓扑结构后,可以通过 CRUSH 算法判定某个对象所在的 OSD。PG 和 CRUSH 算法构成了对象到 OSD 的转换层。而当集群负载发生变化时,RADOS 能够提供有效的数据迁移策略,确保高可用和负载均衡。

在 RADOS 集群的每个节点上,每块磁盘运行有独立的 Ceph OSD 守护进程。每个 OSD 都会处理来自客户端的 I/O 请求,并且 OSD 之间也会相互合作,完成数据迁移,副本更新,错误恢复等功能。OSD 内部提供抽象接口 ObjectStore。ObjectStore 是 Ceph OSD 中最重要的概念之一,它封装了所有对底层存储的 IO 操作。读请求会通过 ObjectStore 提供的 API 获得相应的内容,写请求也会利用 ObjectStore 提供的事务 API 将所有写操作组合成一个原子事务提交给 ObjectStore。ObjectStore 通过接口对上层提供不同的隔离级别,目前 PG 层只采用了 Serializable 级别,保证读写的顺序性。

ObjectStore 主要接口分为三部分,第一部分是 Object 的读写操作,类似于 POSIX 的部分接口,第二部分是 Object 的属性读写操作,这类操作的特征是 kv 对并且与某一个 Object 关联。第三部分是关联 Object 的 kv 操作(在 Ceph 中称为 omap)。

对应存储后端需要实现 ObjectStore 接口,数据最终会调用 ObjectStore 接口持久化到存储后端。同一 RADOS 集群可以使用不同种类的存储后端,但在实践中通常统一。

2.3 Ceph 后端存储引擎发展历程

Ceph 提供存储功能的核心组件是 RADOS 集群,其本质是一个提供了大量接口的分布式对象存储集群,最终都是以对象存储的形式对外提供服务。但在底层的内部实现中,Ceph 的后端存储引擎在近十年来经历了许多变化。现如今的 Ceph 系统中仍然提供的后端存储引擎有 FileStore、NewStore 和 BlueStore。但该三种存储引擎都是近年来才提出并设计实现的。

Ceph 存储引擎最初的实现为 EBOFS(Extent and B-Tree-based Object File System),其本质也为文件系统,只是在文件系统的基础上做了扩展,主要核心逻辑使用 B-Tree 来实现,但 EBOFS 缺少了很多生产环境中的必须的实用性功能,如事务和校验和等,2008 年出现并引入了 Btrfs 之后就摒弃了 EOBFS。

Btrfs 作为 Ceph 的后端存储系统经历了长时间的开发,提供了 EBOFS 不能提供的事务、校验、数据去重等功能,但投入使用很长一段时间后发现 Btrfs 的碎片化现象比较严重,所以又开始了对新的文件系统存储后端的探索。

在 FileStore 的存储后端中,一个对象的集合对应一个目录,对象数据保存在文件中。一开始,对象的属性保存在 POSIX 的扩展文件属性中,但很快,由于文件存储的扩展属性数量和长度限制,Ceph 将对象属性转移到 LevelDB 这类事务性数据库中。基于 BtrFS 的 FileStore 成为业界主流并持续了数年,暴露了一些问题。比如 BtrFS 仍然不稳定,数据和元数据碎片化严重。而与此同时,ObjectStore 的接口也发生了很大变化,不能再使用过去的 EBOFS 实现。为了获得更好的扩容性和元数据处理性能,FileStore 迁移到通用文件系统。

在 2011 年正式使用 XFS 作为 Ceph 存储后端之前,Ceph 团队还尝试过使用其他文件系统如 ext4、ZFS 等作为存储后端,但最后选择了 XFS,因为其具有更好的伸缩性,元数据的操作性能也较好。

XFS 比起 Btrfs 在运行过程中更为稳定,性能波动较小,但仍让有元数据碎片的问题,无法充分利用硬件设备的性能。同时缺少事务的支持,使得需要实现额外的 WAL 机制来提供事务功能。Ceph 于是提出了新的解决方案来处理元数据碎片化的问题。

Ceph 于 2015 年开发了 NewStore,一种键值存储引擎来管理元数据,从而避免元数据碎片带来的问题。通过建立在 KV 存储中建立和文件系统上的物理文件的索引来向外提供服务,该存储引擎未真正投入生产环境。但该存储引擎奠定了后来且流行至今的 BlueStore 的基础。

2017 年开始在 NewStore 的基础上提出了 BlueStore,使用了 NewStore 中对元数据的管理方式,但不再使用文件系统存储实际的数据,而直接使用了裸设备来直接存放,从而提升 IO 的性能。该引擎被无数次实践表明是目前性能最好的后端存储引擎,所以被广泛使用到生产环境中。

如图

图 2.4 Ceph 后端引擎发展历程

Ceph 典型后端存储引擎介绍

3.1 FileStore

3.1.1 概述

FileStore,也就是利用文件系统的 POSIX 接口实现 ObjectStore API。每个 Object 在 FileStore 层会被看成是一个文件,Object 的属性会利用文件的 xattr 属性存取,因为有些文件系统(如 Ext4)对 xattr 的长度有限制,因此超出长度的元数据会被存储在 DBObjectMap 或 omap 中。通常 DBObjectMap 的存储引擎时 LevelDB 或 RocksDB。

3.1.2 体系架构

图 3‑1 是生产环境中 FileStore 的架构图。如图所示,开始时,所有写事务的数据和元数据都会先写入 Journal 文件,写入 Journal 成功以后立即返回。Journal 类似于数据库的 write-ahead-log。写入完成后,会使用 O_DIRECT 和 O_DSYNC 同步写入到 Journal 文件,随后该事务会被转移到 FileStore 的写入队列中,数据和元数据被异步写到指定区域。

如图

图 3‑1 FileStore 架构图

3.1.3 关键技术

Ceph 曾持续很长一段时间,将 FileStore 作为产品线的存储后端。从当时的历史背景来看,是必然的。FileStore 提供了很多优良的特性。

·利用了文件和对象天然的映射关系。

·利用页缓存机制和 inode 节点缓存机制作为数据、元数据的缓存。

·从软件层面保证磁盘的隔离性。

利用了文件和对象天然的映射关系

文件和对象存在天然的映射关系。文件名对应对象名,文件内容对应对象数据,文件的附加属性对应对象的元数据,文件的目录对应对象集合。通过简单的协议转化,FileStore 可以利用现有的文件系统快速构建后端存储的基本功能。同时,POSIX 接口是 linux 文件系统的标准,FileStore 的转换层和文件系统耦合性很低,可以根据不同的要求,在不同文件系统上快速开发存储后端。

利用页缓存和 inode 缓存

Linux 为文件系统提供了页缓存机制和 inode 缓存机制,能大幅度提升文件的访问速度。而 FileStore 基于文件系统构建,使用 inode 存储对象、对象集合的元数据,使用文件存储对象数据,能够直接利用页缓存和 inode 缓存,管理元数据,提升读写性能。

为了保证事务性,存储系统常常会采用 WAL 的方法。WAL 机制需要先将数据同步到 Journal 文件,随后可以异步地写入磁盘,因而我们常常需要为 Journal 设定缓存,管理写入磁盘的数据队列。而使用文件系统避免了去实现另一套缓存方案。

从软件层面保证磁盘的隔离性

文件系统地一大优势在于,从软件的层次提供了隔离不同应用数据的方案。这让我们可以更高效的利用磁盘空间。我们可以同时在同一文件系统上运行操作系统,应用软件,以及 Ceph 的存储后端。这些进程的数据不会相互污染。

3.1.4 存在的问题

事务机制和性能难以均衡

应用需要存储系统提供事务性保证,但是在 FileStore 上,事务机制的实现却遇到了种种问题。现有的很多工作提出了文件系统的事务机制,但最终因为高性能开销,功能缺失,接口复杂,实现困难等原因,都不能适应生产环境。

例如,有三类实现事务的方法。

·利用文件系统内部的事务机制。

·在用户空间实现 WAL。

·使用 WAL 机制的数据库。这类实现就是上述提到的 NewStore,在本节中不做过多阐述。

一二两种方案都有各自的缺点。

第一类方法。利用文件系统内部的事务机制已经被证明是不合适的。一方面,我们需要进入内核态对文件系统内部进行修改,另一方面,文件系统的事务机制仅能保证文件系统中原子性操作的一致性,并不能保证整个存储后端的一致性。例如 Btrfs,第一版的存储后端利用 Btrfs 的原子性操作接口来完成事务,但是常常如果事务中途发生故障,可能会出现事务部分提交的现象,产生不一致。

第二类方法,在用户空间实现 WAL,这也是 FileStore 在生产环境中使用的方法。

这样的实现方式却面临三个问题。第一是读后写的问题。在进行小块数据写入时,因为块大小的限制,我们需要读取相邻数据进行修改。在连续的写入过程中,因为 Journal 的操作是严格串行化的,即只有当前一个事务提交成功后,才可以进行读取操作。事务的提交总共包含三个步骤:1)序列化事务并写入日志;2)调用 fsync 提交事务;3)等待事务操作在文件系统中生效。因为第二步的副作用,下一个事务可能会因为读后写机制阻塞。

第二个问题是操作的非幂等性。这里举一个例子,我们考虑这样一个事务:①克隆 a->b,②更新 a,③更新 c。事务提交后,将日志异步写入后端存储的过程中,如果②③之间发生故障,系统会回滚重新执行操作①,但这个时候对象 a 已经被更新过了,这将导致不一致性。

第三个问题,重复写。在文件系统上添加 WAL 机制,直观来说会有两次重复写,一次写入 Jounel,一次写入 XFS 文件系统。同时 XFS 本身也是一个日志文件系统,在其内部数据会写入两次。因而同一数据可能至少被写入三次。面对这种现象,开发者往往会进行妥协,仅日志化元数据而取消回滚的功能。即先将数据同步写入磁盘,然后将元数据写入日志,执行 fsync 完成事务提交。但这也会导致极大的同步开销。fsycn 会调用开销巨大的 FLUSH CACHE 指令,而如 XFS 的日志文件系统写入数据时,也会调用上述两次 fsycn 操作,于是单次写入将调用四次 fsycn。

低效的元数据管理

本地文件系统中低效的元数据管理始终是分布式系统的心腹大患。在 FileStore 中,这一问题主要表现为在庞大的目录项中枚举符合条件的目录的效率低下,并且返回的数据没有顺序难以处理。

RADOS 中的对象通过 hash 函数映射到不同的 PG 中,并且按照 hash 值的排序进行遍历。在 Ceph 中,枚举操作是常常发生的,比如在数据去冗余、数据恢复、客户端提取对象列表等操作是,我们需要一一访问相应对象。而很多时候由于上层负载的特性,RADOS 中的对象拥有很长的对象名。FileStore 因为文件名称长度限制,会将过长的对象名放在文件附加属性中,于是访问这些对象,常常需要调用额外的 stat 指令获取对象名。以上种种问题导致了元数据遍历十分低效,解决这一问题的一般方法是创建具有大扇出的目录层次结构,将对象分布在目录中,然后在读取后对所选目录的内容进行排序。

如图

图 3‑2 分割文件夹时后端存储的性能表现

为了进行快速排序,减少额外的 stat 调用损耗,文件夹会保持较少的子文件数(一般是数百个)。当子文件夹数量增加时,会采取分割文件夹的放置维持数量。但这导致极大的并行性开销,首先,会占用大量目录项缓存,并产生大量磁盘小 I/O;其次,XFS 采用分配组的方式,让新的目录项与原有目录项相邻,导致分割文件夹使会浪费许多时间用于寻找对应空间。以上两个原因,影响了分割文件夹时的 I/O 性能。

缺乏对新型存储设备的支持

存储硬件的发展和进步给基于本地文件系统的分布式文件系统提出了新的挑战。为了增加容量,硬件供应商将重心转移到了 SMR 磁盘上,而 SMR 的驱动需要一种新的接口来发挥其最佳性能。如果 SMR 磁盘使用向后兼容的接口,可能会表现出无法预测的性能。为了同时充分利用容量和性能,一种被称为 zone interface 的不向后兼容的接口被逐渐使用。Zone interface 将硬盘按 256MiB 进行分割,在 256MiB 的范围内智能进行顺序的写入。这种特性与 log-structed、copy-on-write 的设计相得益彰,但却与大多数文件系统的覆盖写模式相冲突。

除了 SMR 磁盘的发展,数据中心使用的 SSD 设备也有类似变化。OpenChanel SSD 抛弃了 FTL,将裸设备直接暴露给操作系统,这带来许多优势,比如减少写放大,减少尾延迟,提高吞吐率,减少了一个数量级的 overprovisioning,并通过减少 DRAM 来降低成本。销售商们为 OpenChanel SSD 设计了多种接口,导致了实现碎片化。为了统一标准,销售商共同推出了一种新的 NVMe 标准——ZonedNamespaces(ZNS)。ZNS 定义了相关接口来管理无 FTL 的 SSD 设备。

以上两种新型存储器件在分布式文件存储中的应用逐渐受到重视。他们都具备不向前兼容的驱动接口,本地文件系统改革势在必行。目前在本地文件系统,如 XFS、EXT4 上运行 zone interface 的器件还远未成功,主要原因在于覆盖写的文件系统与顺序写的 zone interface 相冲突。

3.2 NewStore

3.2.1 概述

为了解决上述 FileStore 存在的问题,Ceph 引入了新的存储引擎 NewStore(又被称为 Key-File Store)。通过将元数据从传统的文件系统上分离出来,使用专门的 KV 数据库如 LevelDB 或 RocksDB 来进行管理,而对应的对象数据依旧保存在文件系统中,从而避免文件系统作为后端存储引擎时的元数据碎片化问题,从而提升 Ceph 的整体性能。

3.2.2 体系架构

NewStore 的关键数据结构和 KV 数据分布如图所示。NewStore 首先将 Key 的相关信息、对象的元数据信息以及对象的值的地址信息都统一进行了封装,以 KV 的形式存储在 KV 数据库中,而对象的值的信息则沿用文件系统的方式进行存储,相比于文件系统,添加了一些映射信息来将 KV 数据库中存取的元数据信息和存储在文件系统中的对象信息进行物理映射。图中的保存在 KV 数据库中的 ONode 即为索引结构,建立 Obejct 和存储在文件系统中的物理文件之间的映射关系。ONode 又主要包含对象的扩展属性信息和存储了物理文件地址的分段信息。Object 和 Fragment 的对应关系取决于对象的大小,所以很有可能一个 Object 对应多个 Fragment,或者多个 Object 对应多个 Fragment(当对象较大时,由于 Fragment 文件大小限制,所以需要你多个文件共同构成,当对象较小时,为了不浪费 Fragment 文件的空间,可以多个小对象对应一个 Fragment 文件)。为了较好地处理这两种情况,所以需要根据 Fragment 所在的物理地址(即偏移量)和 Fragment 的数据长度作为 Fragment 对象的属性,同时还包含 FID 对象,FID 又主要包含了物理文件所在的目录信息即 fset 和文件编号 fno,从而能够在文件系统中正确定位到真正的物理文件。

如图

图 3-3 NewStore 的 KV 数据分布

3.2.3 关键技术

(1) 解耦对象与本地物理文件间的一一对应关系,通过索引结构(上图中 ONode)在对象和本地物理文件建立映射关系,并使用 KV 数据库存储索引数据信息,从而减小元数据碎片;

(2) 在保证事务特性的同时,对于对象的创建/追加写/覆盖写(与分段文件大小对齐)操作,无需 Journal 支持,减小了写放大;

(3) 对于非对齐的数据更新操作,先同步写入 write-ahead-log 文件中(简称为 WAL,使用 KV 存储),再异步写入相应的分段(fragement)文件;

(4) 可以在 KV 数据库上层建立 Onode 数据缓存以加速索引数据信息的读取操作;

(5) 单个对象可以有多个分段(fragement)文件,多个对象也可共存于一个分段(fragement)文件,从而能够有效提升分段(fragement)文件的空间利用率,减小数据碎片的产生;

(6) 为了减小 WAL 的开销,在数据修改操作同步到分段(fragement)文件中后,立即将 WAL 日志从 KV 数据库中删除,同时增大 KV 数据的写缓冲区,尽量将 WAL 保存在缓冲区中,从而减少 dump 操作,在 dump 操作执行前,合并多个缓冲区的数据,减少 dump 的次数。

3.2.4 存在的问题

NewStore 存在的问题和 FileStore 相似,因为 NewStore 沿用了 FileStore 存储对象数据的方式,只是将对象的元数据信息和对象数据进行了分离,引入 KV 数据库优化了元数据管理,但在文件系统层面仍然存在严峻的写放大问题。由于对象数据的最终形式都是以文件的形式存储在文件系统中,文件系统相关的保障机制不可避免地产生了写放大问题。即便是存储在 KV 数据库中的元数据信息和对象扩展数据信息,由于 KV 数据库仍然运行在传统的文件系统上,最终的存储流程仍然不能绕过文件系统的层次,所以文件系统引入的高开销问题仍然不能被有效解决。但 NewStore 的提出和研究也为后来的 BlueStore 奠定了基础。

3.3 BlueStore

3.3.1 概述

在经历了 FileStore 的发展和应用之后,逐渐意识到元数据管理和写放大问题成为了限制 Ceph 存储性能的重要原因。在对 NewStore 进行了设计和研究的基础之上,发现元数据的管理可以采用 KV 数据库进行存取,同时将 Key 和 Value 进行解耦,但仍然避免不了对象数据存储在文件系统上的开销。于是 Ceph 官方团队逐渐意识到,其真正限制后端存储引擎性能的根本原因是文件系统本身。之所以一开始采用文件系统,得益于文件系统在过去的存储系统中应用广泛,且经受了住许多考验,文件系统的稳定性使文件系统成为了现如今绝大多数存储系统存储后端的首选。但随着 Ceph 的不断发展,意识到必须要突破文件系统的限制才能在性能上有比较大的突破,所以 Ceph 研发了新的基于裸设备的存储引擎 BlueStore。

BlueStore 是在 NewStore 的基础之上,将对象数据的存放方式改为直接对裸设备进行指定地址和长度的读写操作,不再依赖文件系统提供的 POSIX 接口。对于元数据的管理则沿用 NewStore 中的方式,使用 KV 数据库来对元数据进行管理,但 KV 数据库无法直接运行在裸盘上,必须运行在文件系统的基础之上,为了避免传统文件系统引入的开销,BlueStore 对文件系统进行了定制,实现了一个自定义的小型的文件系统 BlueFS,其 KV 数据库则运行子啊 BlueFS 的基础之上,从而提供元数据管理的相关功能。

3.3.2 体系架构

BlueStore 整体架构分为三个部分:BlockDevice、BlueFS 和 RocksDB。

BlockDevice 为最底层的块设备,通常为 HDD 或者 SSD,BlueStore 直接操作块设备,抛弃了 XFS 等本地文件系统。BlockDevice 在用户态直接以 linux 系统实现的 AIO 直接操作块设备,由于操作系统支持的 aio 操作只支持 directIO,所以对 BlockDevice 的写操作直接写入磁盘,并且需要按照 page 对齐。

BlueFS 是一个小的文件系统,其文件系统的文件和目录的元数据都保存在全部缓存在内存中,持久化保存在文件系统的日志文件中, 当文件系统重新 mount 时,重新 replay 该日志文件中保存的操作,就可以加载所有的元数据到内存中。其数据和日志文件都直接保存在依赖底层的 BlockDevice 中。主要还实现了 RocksDB:Env 所需要的接口。BlueFS 在设计上支持把.log 和.sst 分开存储,.log 使用速度更快的存储介质(NVME 等),从而提高 WAL 日志的性能。

RocksDB 是 Facebook 在 leveldb 上开发并优化的 KV 存储系统。本身是基于文件系统的,不是直接操作裸设备。它将系统相关的处理抽象成 Env,用户可实现相应的接。BlueFS 的主要的目的,就是支持 RocksDB Env 接口,保证 RocksDB 的正常运行。

BlueStore 是最终基于 RocksDB 和 BlockDevice 实现的 Ceph 的对象存储,其所有的元数据都保存在 RocksDB 这个 KV 存储系统中,包括对象的集合,对象,存储池的 omap 信息,磁盘空间分配记录等都保存 RocksDB 里, 其对象的数据直接保存在 BlockDevice 上,不使用本地文件系统,直接接管裸设备,并且只使用一个原始分区。

如图

图 3-4 BlueStore 架构图

3.3.3 关键技术

(1) 快速的元数据操作:通过在 RocksDB 中存储元数据,BlueStore 实现了快速元数据操作。RocksDB 运行在 BlueFS 上,而 BlueFS 是在用户空间实现的基于区段和日志记录的高度定制化文件系统,实现了 RocksDB 运行必需的 Env 相关接口,实现了 RocskDB 要求的基础系统调用,BlueFS 会为每一个文件维护一个 inode,其中包括分配给该文件的区段列表,类似与 NewStore 中的机制。超级块存储在确定的物理地址上,且包含了日志的 inode 信息,其文件系统的文件和目录的元数据都保存在全部缓存在内存中,持久化保存在文件系统的日志文件中, 当文件系统重新 mount 时,重新 replay 该日志文件中保存的操作,就可以加载所有的元数据到内存中。当日志大小达到阈值时,日志文件将被压缩并写到一个新的日志文件中,同时将日志的新地址信息记录到超级块中。其 BlueFS 的架构如图 3-5 所示。

如图

图 3-5 BlueFS 架构图

(2) 减小了写放大:首先,它直接将数据写入原始磁盘,从而只产生一个用于数据写入的缓存刷新操作。其次,BlueStore 改变了 RocksDB 的机制,重用 WAL 日志文件作为一个循环缓冲区,从而为元数据的写入也只产生一个缓存刷新的操作。缓存刷新操作的减少直接决定了开销较大的同步系统调用操作减少,从而优化了在文件系统中存在的多次缓存刷新操作带来的效率问题。

(3) CoW 机制保证了快速的克隆操作:BlueStore 支持 CoW 机制,CoW(Copy-On-Write)是指当覆盖写发生时,不是更新磁盘对应位置已有的内容,而是新分配一块空间,写入本次更新的内容,然后更新对应的地址指针,最后释放原有数据对应的磁盘空间。对于数据大小大于最小分配块的写操作(HDD 最小分配块大小为 64 KiB,SSD 最小分配块大小为 16KiB),数据将被写到一个新分配的数据块内。一旦数据被持久化,相应的元数据就被插入到 RocksDB 中。这使得 BlueStore 能够提供快速的克隆操作,克隆操作只是增加所属区段的引用计数,并将写操作定向到新的区段。

(4) 没有日志双写问题:由于 BlueStore 的对象数据的写流程中没有了文件系统的参与,对象数据都是直接写入到底层块设备中,从而避免了文件系统中广泛存在的日志双写问题,只是在元数据的 IO 过程中仍然要使用 WAL 机制来进行保证,但又由于引入了 CoW 机制,使得需要 WAL 机制保证的 IO 数据大小大大减小,从而减小了日志双写问题带来的影响。

(5) 针对 SSD 和 HDD 进行了优化:对于数据大小小于最小分配快的写操作,数据和元数据都将预先插入到 RocksDB 中,然后在事务提交后异步写入磁盘,该操作对小 IO 进行了优化使得小 IO 能够批量地执行,同时针对 HDD,小于或等于 64 KiB 的大对象的覆盖操作是在适当的位置异步执行的,以避免在读取期间进行 seek,同理针对 SSD,小于 16KiB 的对象覆盖写操作比避免了读数据过程中进行 seek 操作。

(6) 增加数据校验及数据压缩等功能: BlueStore 为每次写操作计算校验和,为每次读操作验证校验和,也支持多种校验和算法。CRC32c 由于其在 x86 和 ARM 架构上都有比较好的优化被用作默认校验和算法,在检测随机 Bit 位错误时也很高效。由于 BlueStore 完全控制了整个 IO 栈,BlueStore 可以根据 IO 的情况决定计算校验和的大小。

(7) 能够对新型存储器件提供更好的支持:由于传统的文件系统作为存储后端,更多的是支持块设备等物理存储器件,而现如今出现的新型的存储设备,如 SMR SSD 和 NVM 等新型存储器件,不再如以往的块设备一样对提供块接口,在文件系统没有做到真正的适配之前,BlueStore 可以充分利用自己裸盘读写的特点,不受文件系统的限制,直接操纵裸设备。

3.3.4 存在的问题

BlueStore 在自己实现读裸设备管理的核心思想下,就不得不面对很多机制都需要自己来实现优化的场景,面临的问题主要表现在以下三个方面:

(1) 缓存大小的调整和写回策略的实现:由于 BlueStore 抛弃了文件系统作为实际的存储形式,所以不可避免地就需要自己是现在在文件系统中表现优异的各类缓存机制和策略。文件系统继承了操作系统页缓存的优势,会根据应用程序的使用情况动态调整缓存的大小;而对于类似于 BlueStore 穿过内核的存储后端需要自己实现类似的机制。BlueStore 中需要自己手动设置缓存大小相关的参数,如何在用户态构建一个如操作系统页缓存动态调整大小的机制时很多存储系统都面临的问题,如 PostgreSQl,RocksDB。同时面对已经出现的 NVMe SSD,缓存需要更加高效,才能减小 SSD 的写负载,同时也是当前页缓存面临的问题。

(2) KV 存储的效率问题::Ceph 的经验表明把所有元数据给移植到有序的 KV 存储(如 RocksDB)上能够显著提高元数据操作的性能,然而同时也发现嵌入 RocksDB 带来的一些问题:Ceph 的经验表明把所有元数据给移植到有序的 KV 存储(如 RocksDB)上能够显著提高元数据操作的性能,然而同时也发现嵌入 RocksDB 带来的一些问题:

  1. 在 OSD 节点上使用 NVMe SSD 时,RocksDB 的压缩机制和严峻的写放大问题已经成为了主要的性能限制;
  2. 由于 RockDB 被视为一个黑盒,因此数据被序列化,并在其中来回复制,消耗数据 CPU 时间;
  3. RocksDB 有自己的线程模型,这限制了自定义分片的能力。

(3) CPU 和内存的效率问题:Ceph 中复杂的数据结构,且生命周期较长,采用默认的布局一定程度上会造成内存的浪费。因为现代编译器在内存中对齐和填充基本数据类型,这样 CPU 就可以方便地获取数据,从而提高了性能。然而跨越了内核的存储后端控制几乎机器的所有的内存,内存的优化和隔离机制需要自己动手实现;如上提到的 KV 存储的问题,在使用 NVMe SSD 时,其工作负载会被 CPU 限制,涉及到大量的序列化和反序列化操作。故 Ceph 开发团队试图减小 CPU 的消耗,通过减小序列化和反序列化数据的大小,尝试使用 SeaStar 框架的共享模型来避免由于锁导致的上下文切换。

总结

Ceph 的后端存储引擎随着日新月异的需求的变化不断地向前发展,但发展的大体脉络也基本揭示了了分布式存储系统的变革规律。从一开始选择使用功能稳定、机制成熟、且代码久经考验的文件系统作为存储后端,更多的是出于功能稳定性上的考虑,但随着场景的复杂化,数据规模的不断增大,不同的 IO 类型的出现,以及实际生产环境中对性能的极致要求,以 Ceph 为代表的分布式存储系统的经验表明文件系统不再适用于如今的分布式存储场景,其性能的低下和开销的巨大使得开发者们不得不在文件系统的基础上进行优化。但随着近年来新型存储器件技术的发展,使得存储设备对外提供的接口多样化,不再局限于传统的块接口,而基于传统的块设备设计的文件系统无法对新型存储器件进行较好的适配。在对性能的极致要求和新型存储器件提出的挑战的共同作用下,Ceph 开始了新型存储后端引擎的研究,打破了文件系统作为存储后端的垄断格局,并结合其他后端存储引擎的优势如 KV 存储等,设计了一种新的基于裸设备的管理方案,并定制化小型文件系统来对传统的依赖 POSIX 接口应用进行适配,充分利用存储器件的性能,尽可能地降低软件的开销,将存储引擎的职责单一化专门化,从而更好地为分布式存储系统提供相应的服务和功能。

如今 BlueStore 已经成为了 Ceph 用户的后端存储引擎第一选择,BlueStore 带来的性能上其他存储后端不可比拟的优势和对新型存储器件的支持使得 BlueStore 已经被应用到绝大多数场景。现在也已经成为了 Ceph 默认使用的存储引擎后端。BlueStore 解决了 FileStore 的许多缺陷的同时提升了相应的写性能,同时在读性能方面没有损失,也逐渐在适应变化的需求和应用场景,增加了诸如数据校验、数据压缩等新特性。BlueStore 虽然整体表现优异,但在主打机械磁盘的传统阵列中的表现依旧差强人意,小粒度的读写性能相比于文件系统作为存储后端提升相对有限,因此针对 BlueStore 的性能优化依然任重而道远。

展望

通过 Ceph 后端引擎的发展历程,我们不难发现,传统的文件系统存储后端在不断完善相关功能如事务、克隆和快照等操作的同时,也在进行转型。通过融合其他存储引擎的优势共同组成一个存储系统来对外提供服务,诸如元数据管理更多的是交给 KV 存储来进行处理,充分利用其他存储引擎所长,并将各自进行组件化,模块化,以模块的形式和其他存储模块进行融合,共同构成高效的存储系统。后来产生的 BlueStore 正是存储引擎模块化组件化的成果,通过充分发挥各类存储引擎的优势,将 KV 存储引擎用于元数据的管理,将文件系统进行定制,根据实际的场景减小文件系统的功能复杂程度,定制了 BlueFS,同时也有开天辟地的新型存储引擎的设计与实现,如基于裸设备的存储,通过将各类存储引擎组件化,可以根据实际的需求将组件进行整合,从而构成一个与需求紧密结合的存储系统。未来存储系统的组件化将应用更加广泛,尤其是针对现如今不断发展的存储器件技术,传统的基于块设备的存储引擎势必需要对新型存储器件进行适配,组件化存储引擎之后可以充分减小适配的工作量,同时对于未来可能出现的新技术也能较好地进行吸收和整合。BlueStore 的出现开启了一段存储系统变革的新纪元,未来面向实际应用场景的定制化存储系统可能才能真正充分发挥存储系统的性能。

参考文献

[1] Abutalib Aghayev, Sage Weil, Michael Kuchnik, Mark Nelson, Gregory R. Ganger, George Amvrosiadis. File Systems Unfit as Distributed Storage Backends:Lessons from 10 Years of Ceph Evolution[J]. ACM 2019.10.

[2] Dong-Yun Lee, Kisik Jeong, Sang-Hoon Han, Jin-Soo Kim, Joo-Young Hwang, and Sangyeun Cho. Understanding Write Behaviors of Storage

Backends in Ceph Object Store[J]. In Proc. MSST 2017

[3] Ceph optimizations for NVMe.

https://www.flashmemorysummit.com/English/Collaterals/Proceedings/2018/20180808_SOFT-202-1_Liu.pdf

[4] http://ceph.io

来源

分布式存储系统 Ceph 的后端存储引擎研究 - 知乎