摘要:分布式异步对象存储 (DAOS) 是一个开源的对象存储系统,专为大规模分布式非易失性内存 (NVM, Non-Volatile Memory) 设计,利用了 SCM(Storage-Class Memory) 和 NVMe(Non-Volatile Memory express) 等的下一代 NVM 技术。
本文分享自华为云社区《DAOS 分布式异步对象存储》,原文做者:debugzhang 。算法
分布式异步对象存储 (DAOS) 是一个开源的对象存储系统,专为大规模分布式非易失性内存 (NVM, Non-Volatile Memory) 设计,利用了 SCM(Storage-Class Memory) 和 NVMe(Non-Volatile Memory express) 等的下一代 NVM 技术。数据库
DAOS 是一种横向扩展的对象存储,能够为高性能计算应用提供高带宽、低延迟和高 IOPS 的存储容器,并支持结合仿真、数据分析和机器学习的下一代以数据为中心的工做流程。express
与主要针对旋转介质设计的传统存储堆栈不一样,DAOS 针对全新 NVM 技术进行了从新构建,可在用户空间中端对端地运行,并能彻底绕开操做系统,是一套轻量级的系统。后端
DAOS 提供了一种为访问高细粒度数据提供原生支持的 I/O 模型,而不是传统的基于高延迟和块存储设计的 I/O 模型,从而释放下一代存储技术的性能。数组
与传统的缓冲区不一样,DAOS 是一个独立的高性能容错存储层,它不依赖其它层来管理元数据并提供数据恢复能力。DAOS 服务器将其元数据保存在持久内存中,而将批量数据直接保存在 NVMe 固态盘中。安全
DAOS 特性
DAOS 依靠 OFI(OpenFabric Interface) 绕过操做系统,将 DAOS 操做交付给 DAOS 存储服务器,充分利用架构中的任何远程直接内存访问 (RDMA, Remote Direct Memory Access) 功能,进行低延迟、高消息速率的用户空间通讯,并将数据存储在持久内存和 NVMe 固态盘中。服务器
DAOS 的键值存储接口提供了统一的存储模型,经过迁移 I/O 中间件库实现对 DAOS API 的原生支持后,就能利用 DAOS 丰富的 API 和先进功能,例如 HDF五、MPI-IO 和 Apache Arrow。网络
DAOS 还提供 POSIX 的仿真。POSIX 再也不是新数据模型的基础,而是像其余 I/O 中间件同样,POSIX 接口将构建为 DAOS 后端 API 顶部的库。数据结构
DAOS 的 I/O 操做会被记录并存储到 SCM 维护到持久索引中,每次 I/O 都用一个特定时间戳标记,并与数据集的特定版本关联。内部不执行读-修改-写 (read-modify-write) 操做,写入操做是无损的,对对齐不敏感。在读取请求时,DAOS 服务器遍历持久索引,建立聚合 RDMA 描述符,从而直接在应用程序提供的缓冲区中重建所请求的版本的数据。架构
SCM 直接映射到 DAOS 服务地址空间的内存,DAOS 服务经过直接加载/存储来管理持久索引。根据不一样 I/O 的特性,DAOS 服务能够决定将 I/O 存储在 SCM 或 NVMe 中:
- 对延迟敏感的I/O(如应用程序元数据和字节粒度数据)一般存储在 SCM 中;
- 检查点和批量数据存储在 NVMe 中。
这种方法容许 DAOS 将数据流式传输到 NVMe 中,并在 SCM 中维护内部元数据索引,为批量数据提供原始 NVMe 带宽。持久内存开发工具包 PMDK 管理对 SCM 的事务性访问,存储性能开发工具包 SPDK 对 NVMe 设备进行用户空间 I/O 操做。
DAOS 能够提供:
- 超高细粒度、低延迟和真正零拷贝的 I/O
- 非阻塞型数据和元数据操做,以支持 I/O 和计算重叠
- 先进的数据放置,以解决故障域
- 由软件管理冗余,可经过在线重建,支持复制和擦除代码
- 端到端 (E2E) 数据完整性
- 可扩展的分布式事务,提供可靠的数据一致性和自动恢复功能
- 数据集快照功能
- 安全框架,用于管理存储池的访问控制
- 软件定义存储管理,用于调配、配置、修改和监控存储池
- 经过 DAOS 数据模型和 API,为 I/O 中间件库(例如 HDF五、 MPI-IO 和 POSIX)提供原生支持。应用无需移植代码,便可直接使用 DAOS API
- Apache Spark 集成
- 使用发布/订阅 API,实现原生生产者/消费者工做流程
- 数据索引和查询功能
- 存储内计算,以减小存储和计算节点之间的数据移动
- 容灾工具
- 与 Lustre 并行文件系统无缝集成,并能扩展到其余并行文件系统,从而为跨多个存储层的数据访问提供统一的命名空间
- 数据搬运器,用于在 DAOS 池之间迁移数据集,将数据集从并行文件系统迁移到 DAOS,反之亦然
DAOS 组件
一个数据中心可能有数十万个计算节点,经过一个可伸缩的高性能结构相互链接,其中全部节点或称为存储节点的节点子集均可以直接访问 NVM 存储。
DAOS 安装涉及几个能够集中或分布式的组件。
DAOS 系统和存储节点
DAOS系统由一个系统名标识,它由一组链接到同一结构的 DAOS 存储节点组成。DAOS 存储节点为每一个节点运行一个 DAOS 服务实例,该实例为每一个物理套接字启动一个 DAOS I/O 引擎进程。这些 DAOS 服务的信息被记录到系统映射中,该映射为每一个 I/O 引擎进程分配一个惟一的整数秩。两个不一样的 DAOS 系统由两组不相交的 DAOS 服务器组成,它们之间没法相互配合。
DAOS 服务
DAOS 服务是一个多租户守护进程,运行在每一个存储节点的 Linux 实例上(物理节点、虚拟机或容器)。服务的 I/O 引擎子进程经过网络导出本地链接的 SCM 和 NVM 存储。服务监听一个管理端口(由 IP 地址和 TCP 端口号寻址),以及一个或多个结构端点(由网络 URI 寻址)。
DAOS 服务经过 /etc/DAOS 中的 YAML 文件进行配置,包括其 I/O 引擎子进程的配置。服务的启动能够与不一样的守护进程管理或编排框架集成(systemd 脚本、Kubernetes 服务、或相似 pdsh 和 srun 的并行启动程序)。
I/O引擎
在 DAOS I/O 引擎中,存储静态地跨越多个 Target 分区,加强并发能力。为了不竞争,每一个 Target 都有其私有存储、本身的服务线程池以及专用的网络上下文,这些上下文能够直接经过结构寻址,而不依赖于托管在同一存储节点上的其余 Target 。
SCM 模块一般以 AppDirect interleaved 模式配置。所以,它们做为每一个套接字(在 fsdax 模式)的单个 PMEM 命名空间呈现给操做系统。当配置每一个 I/O 引擎的 N 个 Target 时,每一个 Target 都使用该套接字 fsdax 的 SCM 容量的 \frac{1}{N}N1,与其它 Target 独立。每一个 Target 还使用链接到此套接字的 NVMe 驱动器容量的一小部分。
Target
Target 没有针对存储介质故障实现任何内部数据保护机制。所以,一个 Target 就是一个单点故障,同时也是故障单元。动态状态与每一个 Target 相关联:其状态能够是“up and running”,也能够是“down and not available”。
Target 是性能的单位。与 Target 关联的硬件组件(如后端存储介质、CPU 核心和网络)的能力和容量有限。
DAOS I/O 引擎实例导出的 Target 数是可配置的,取决于底层硬件(I/O 引擎实例的 SCM 模块数和 NVMe SSD 数)。I/O 引擎的 Target 数的最佳配置是该 I/O 引擎服务的 NVMe 驱动器数的整数倍。
存储 API、应用程序接口和工具
应用程序、用户和管理员能够经过两个不一样的客户端 API 与 DAOS 系统交互。
管理 API 提供了管理 DAOS 系统的接口。它旨在与不一样供应商的存储管理或开源编排框架集成。dmg 命令行工具是在 DAOS 的管理 API 上构建的。
DAOS 库 libdaos 实现了 DAOS 存储模型,主要提供给但愿在 DAOS 系统中存储数据集的应用程序和 I/O 中间件的开发人员。用户经常使用的 daos 命令等的也构建在 API 之上,容许用户经过命令行管理数据集。
应用程序能够经过本机 DAOS API、I/O 中间件库(如 POSIX 仿真、MPI-IO、HDF5)或已与本机 DAOS 存储模型集成的 Spark 或 TensorFlow 等框架直接访问存储在 DAOS 中的数据集。
代理
DAOS 代理是驻留在客户端节点上的守护进程,经过与 DAOS 库交互验证应用程序进程。它是一个可信任的实体,支持使用证书对 DAOS 客户端进行签名。DAOS 代理支持不一样的身份验证框架,并使用 Unix 域套接字与客户端库通讯。
存储模型
DAOS Pool 是分布在 Target 集合上的存储资源预留。分配给每一个 Target 上的 Pool 的实际空间称为 Pool Shard。
分配给 Pool 的总空间在建立时肯定,后期能够经过调整全部 Pool Shard 的大小(在每一个 Target 专用的存储容量限制内)或跨越更多 Target(添加更多 Pool Shard)来随时间扩展。
Pool 提供了存储虚拟化,是资源调配和隔离的单元。DAOS Pool 不能跨多个系统。
一个 Pool 能够承载多个称为 DAOS Container 的事务对象存储。每一个 Container 都是一个私有的对象地址空间,能够对其进行事务性修改,而且独立于存储在同一 Pool 中的其余 Container。Container 是快照和数据管理的单元。属于 Container 的 DAOS 对象能够分布在当前 Pool 的任何一个 Target 上以提升性能和恢复能力,而且能够经过不一样的 API 访问,从而高效地表示结构化、半结构化和非结构化数据。
下表显示了每一个 DAOS 概念的目标可伸缩性级别:
DAOS Pool
Pool 由惟一的 Pool UUID 标识,并在称为 Pool 映射的持久版本控制列表中维护 Target 成员身份。成员资格是肯定的和一致的,成员资格的变动是按顺序编号的。Pool 映射不只记录活跃 Target 的列表,还以树的形式包含存储拓扑,用于标识共享公共硬件组件的 Target。例如,树的第一级能够表示共享同一主板的 Target,第二级能够表示共享同一机架的全部主板,最后第三级能够表示同一机房中的全部机架。
该框架有效地表示了层次化的容错域,而后使用这些容错域来避免将冗余数据放置在发生相关故障的 Target 上。在任什么时候候,均可以将新 Target 添加到 Pool 映射中,而且能够排除失败的 Target。此外,Pool 映射是彻底版本化的,这有效地为映射的每次修改分配了惟一的序列,特别是对于失败节点的删除。
Pool Shard 是永久内存的预留,能够选择与特定 Target 上 NVMe 预先分配的空间相结合。它有一个固定的容量,满了就不能运行。能够随时查询当前空间使用状况,并报告 Pool Shard 中存储的任何数据类型所使用的总字节数。
一旦 Target 失败并从 Pool 映射中排除,Pool 中的数据冗余将自动在线恢复。这种自愈过程称为重建。重建进度按期记录在永久内存中存储的 Pool 中的特殊日志中,以解决级联故障。添加新 Target 时,数据会自动迁移到新添加的 Target,以便在全部成员之间平均分配占用的空间。这个过程称为空间再平衡,使用专用的持久性日志来支持中断和重启。
Pool 是分布在不一样存储节点上的一组 Target,在这些节点上分布数据和元数据以实现水平可伸缩性,并使用复制或纠删码 (erasure code) 确保持久性和可用性。
建立 Pool 时,必须定义一组系统属性以配置 Pool 支持的不一样功能。此外,用户还能够定义将持久存储的属性。
Pool 只能由通过身份验证和受权的应用程序访问。DAOS 支持多种安全框架,例如 NFSv4 访问控制列表或基于第三方的身份验证 (Kerberos)。链接到 Pool 时强制执行安全性检查。成功链接到 Pool 后,将向应用程序进程返回链接上下文。
如前文所述,Pool 存储许多不一样种类的持久性元数据,如 Pool 映射、身份验证和受权信息、用户属性、特性和重建日志。这些元数据很是关键,须要最高级别的恢复能力。所以,Pool 的元数据被复制到几个来自不一样高级容错域的节点上。对于具备数十万个存储节点的很是大的配置来讲,这些节点中只有很小的一部分(大约几十个)运行 Pool 元数据服务。在存储节点数量有限的状况下,DAOS 能够依赖一致性算法来达成一致,在出现故障时保证一致性,避免脑裂。
要访问 Pool,用户进程应该链接到 Pool 并经过安全检查。一旦受权,Pool 就能够与任何或全部对等应用程序进程(相似 openg() POSIX 扩展)共享(经过 local2global() 和 global2local() 操做)链接。这种集体链接机制有助于在数据中心上运行大规模分布式做业时避免元数据请求风暴。当发出链接请求的原始进程与 Pool 断开链接时,Pool 链接将被注销。
DAOS Container
Container 表明 Pool 中的对象地址空间,由 Container UUID 标识。
下图显示了用户(I/O 中间件、特定领域的数据格式、大数据或 AI 框架等)如何使用 Container 来存储相关数据集:
与 Pool 同样,Container 能够存储用户属性。Container 在建立时必须传递一组属性,以配置不一样的功能,例如校验和。
要访问 Container,应用程序必须首先链接到 Pool,而后打开 Container。若是应用程序被受权访问 Container,则返回 Container 句柄,它的功能包括受权应用程序中的任何进程访问 Container 及其内容。打开进程能够与全部对等进程共享此句柄。它们的功能在 Container 关闭时被撤销。
Container 中的对象可能具备不一样的模式,用于处理数据分布和 Target 上的冗余,定义对象模式所需的一些参数包括动态或静态条带化、复制或纠删码。Object 类定义了一组对象的公共模式属性,每一个 Object 类都被分配一个惟一的标识符,并在 Pool 级别与给定的模式相关联。一个新的 Object 类能够在任什么时候候用一个可配置的模式来定义,这个模式在建立以后是不可变的(或者至少在属于这个类的全部对象都被销毁以前)。
为了方便起见,在建立 Pool 时,默认状况下会预约义几个最经常使用的 Object 类:
以下所示,Container 中的每一个对象都由一个惟一的 128 位对象地址标识。对象地址的高 32 位保留给 DAOS 来编码内部元数据,好比 Object 类。剩下的 96 位由用户管理,在 Container 中应该是惟一的。只要保证惟一性,栈的上层就可使用这些位来编码它们的元数据。DAOS API 为每一个 Container 提供了 64 位可伸缩对象 ID 分配器。应用程序要存储的对象 ID 是完整的 128 位地址,该地址仅供一次性使用,而且只能与单个对象模式相关联。
<---------------------------------- 128 bits ----------------------------------> -------------------------------------------------------------------------------- |DAOS Internal Bits| Unique User Bits | -------------------------------------------------------------------------------- <---- 32 bits ----><------------------------- 96 bits ------------------------->
Container 是事务和版本控制的基本单元。全部的对象操做都被 DAOS 库隐式地标记为一个称为 epoch 的时间戳。DAOS 事务 API 容许组合多个对象更新到单个原子事务中,并基于 epoch 顺序进行多版本并发控制。全部版本更新均可以按期聚合,以回收重叠写入所占用的空间,并下降元数据复杂性。快照是一个永久引用,能够放置在特定的 epoch 上以防止聚合。
Container 元数据(快照列表、打开的句柄、对象类、用户属性、属性和其余)存储在持久性内存中,并由专用 Container 元数据服务维护,该服务使用与父元数据 Pool 服务相同的复制引擎或本身的引擎,这在建立 Container 时是可配置的。
与 Pool 同样,对 Container 的访问由 Container 句柄控制。要获取有效的句柄,应用程序进程必须打开 Container 并经过安全检查。而后,能够经过 Container 的 local2global() 和 global2local() 操做与其余对等应用程序进程共享此句柄。
DAOS Object
为了不传统存储系统常见的扩展问题和开销,DAOS 有意将对象简化,不提供类型和架构以外的默认对象元数据。这意味着系统不维护时间、大小、全部者、权限,甚至不跟踪开启者。
为了实现高可用性和水平伸缩性,DAOS 提供了许多对象模式(复制/纠删码、静态/动态条带化等)。模式框架是灵活的,而且易于扩展,以容许未来使用新的自定义模式类型。模式布局是在对象标识符和 Pool 映射打开的对象上经过算法生成的。经过在网络传输和存储期间使用校验和保护对象数据,确保了端到端的完整性。
能够经过不一样的 API 访问 DAOS 对象:
- Multi-level key-array API 是具备局部性特征的本机对象接口。密钥分为分发密钥 (dkey) 和属性密钥 (akey)。dkey 和 akey 均可以是可变长度的类型(字符串、整数或其它复杂的数据结构)。同一 dkey 下的全部条目都保证在同一 Target 上并置。与 akey 关联的值能够是不能部分修改的单个可变长度值,也能够是固定长度值的数组。akeys 和 dkey 都支持枚举。
- Key-value API 提供了一个简单的键和可变长度值接口。它支持传统的 put、get、remove 和 list 操做。
- Array API 实现了一个由固定大小的元素组成的一维数组,该数组的寻址方式是 64 位偏移寻址。DAOS 数组支持任意范围的读、写和 punch 操做。
事务模型
DAOS API 支持分布式事务,容许将针对属于同一 Container 的对象的任何更新操做组合到单个 ACID 事务中。分布式一致性是经过基于多版本时间戳排序的无锁乐观并发控制机制提供的。DAOS 事务是可串行化的,能够在特定的基础上获取部分须要的数据集。
DAOS 版本控制机制容许建立持久的 Container 快照,该快照提供 Container 的实时分布一致性视图,该视图可用于构建生产者-消费者管道。
Epoch 和时间戳
每一个 DAOS I/O 操做都有一个称为 epoch 的时间戳。epoch 是一个 64 位整数,它集成了逻辑和物理时钟(详见 HLC paper)。DAOS API 提供了辅助函数,用于将 epoch 转换为传统的 POSIX 时间(即 struct timespec,详见 clock_gettime(3))。
Container 快照
以下图所示,Container 的内容能够随时快照。
DAOS 快照很是轻量级,而且使用与建立快照的时间相关联的 epoch 进行标记。一旦建立成功,快照将一直保持可读性,直到它被显式销毁。在特定快照未被销毁前,Container 的内容能够回滚到该快照。
Container 快照功能支持本机生产者/消费者管道:
一旦成功写入数据集的一致版本,生产者 (Producer) 将生成一个快照。使用者 (Consumer) 的应用程序能够订阅 Container 快照事件,以便在生产者提交更新时能够处理新的更新。
快照的不变性保证了使用者能够看到一致的数据,即便生产者继续进行新的更新。生产者和消费者实际上都在 Container 的不一样版本上操做,不须要任何串行化操做。一旦生产者生成了数据集的新版本,使用者就能够查询两个快照之间的差别,而且只处理增量修改。
分布式事务
与 POSIX 不一样,DAOS API 不强制执行最坏状况下的并发控制机制来处理冲突的 I/O 操做。相反,各个 I/O 操做被标记为不一样的 epoch,并按照 epoch 的顺序应用,而无论执行顺序如何。这个基准模型为不产生冲突的 I/O 工做负载的数据模型和应用程序提供了最大的可伸缩性和最高的性能。典型的例子是 MPI-IO 集合操做、POSIX 文件读/写操做和 HDF5 数据集读/写操做。
对于须要将冲突串行化的部分数据模型,DAOS 提供了基于多版本并发控制的分布式可串行化事务。当不一样的用户进程要覆盖与 dkey/akey 关联的值时,一般须要该事务。例如 DAOS 上的 SQL 数据库,或者由非一致的客户端并发访问的一致的 POSIX 命名空间。
在同一操做的上下文中提交的全部 I/O 操做(包括读取)将使用相同的 epoch。DAOS 事务机制自动检测传统的读/写、写/读和写/写冲突,并停止其中一个冲突事务(事务在 -DER_RESTART 参数下提交失败)。而后,用户/应用程序必须从新启动失败的事务。
在目前的实现中,事务 API 具备如下限制,这些限制将在将来的 DAOS 版本中解决:
- 不支持 Array API
- 经过同一上下文环境执行的对象获取/列表和键值获取/列表操做所进行的事务对象更新和键值放入操做不可见。