打造用户态存储利器,基于SPDK的存储引擎Blobstore & BlobFS

作者简介

周雁波,Intel存储软件工程师,主要从事SPDK软件开发工作。

    

    Blobstore是位于SPDK bdev之上的Blob管理层,用于与用户态文件系统Blobstore Filesystem (BlobFS)集成,从而代替传统的文件系统,支持更上层的服务,如数据库MySQL、K-V存储引擎Rocksdb以及分布式存储系统Ceph、Cassandra等。以Rocksdb为例,通过BlobFS作为Rocksdb的存储后端的优势在于,I/O经由BlobFS与Blobstore下发到bdev,随后由SPDK用户态driver写入磁盘。整个I/O流从发起到落盘均在用户态操作,完全bypass内核。此外,可以充分利用SPDK所提供的异步、无锁化、Zero Copy、轮询等机制,大幅度减少额外的系统开销。它们之间的关系如下所示(以NVMe bdev为例):

 

640?wx_fmt=jpeg

    

    BlobFS在管理文件时,主要依赖于Blobstore对blob的分配与管理。Blob类似于文件的概念,而又不完全等同于文件,其并不支持所有文件的POSIX接口。BlobFS与Blobstore的关系可以理解为Blobstore实现了对Blob的管理,包括Blob的分配、删除、读取、写入、元数据的管理等,而BlobFS是在Blobstore的基础上进行封装的一个轻量级文件系统,用于提供部分对于文件操作的接口,并将对文件的操作转换为对Blob的操作,BlobFS中的文件与Blobstore中的Blob一一对应。在Blobstore下层,与SPDK bdev层对接。SPDK bdev层类似于内核中的通用块设备层,是对底层不同类型设备的统一抽象管理,例如NVMe bdev、Malloc bdev、AIO bdev等。

Blobstore中结构的划分

    在blobstore中,将SSD中的块划分为多个抽象层,主要由Logical Block、Page、Cluster、Blob组成,它们之间的关系如下所示:

640?wx_fmt=jpeg

  • Logical Block:与块设备中所提供的逻辑块相对应,通常为512B或4KiB。

  • Page:由多个连续的Logical Block构成,通常一个page的大小为4KiB,因此一个Page由八个或一个Logical Block构成,取决于Logical Block的大小。在Blobstore中,Page是连续的,即从SSD的LBA 0开始,多个或一个块构成Page 0,接下来是Page 1,依次类推。

  • Cluster:由多个连续的Page构成,通常一个Cluster的大小默认为1MiB,因此一个Cluster由256个Page构成。Cluster与Page一样,是连续的,即从SSD的LBA 0开始的位置依次为Cluster 0到Cluster N。

  • Blob:Blobstore中主要的操作对象为Blob,与BlobFS中的文件相对应,提供read、write、create、delete等操作。一个Blob由多个Cluster构成,但构成Blob中的Cluster并不一定是连续的。

那么Blobstore是如何管理块的分配呢?

    在Blobstore中,会将cluster 0作为一个特殊的cluster。该cluster用于存放Blobtore的所有信息以及元数据,对每个blob数据块的查找、分配都是依赖cluster 0中所记录的元数据所进行的。Cluster 0的结构如下:

640?wx_fmt=jpeg

    Cluster 0中的第一个page作为super block,Blobstore初始化后的一些基本信息都存放在super block中,例如cluster的大小、已使用page的起始位置、已使用page的个数、已使用cluster的起始位置、已使用cluster的个数、Blobstore的大小等信息。

 

    Cluster 0中的其它page将组成元数据域(metadata region)。元数据域主要由以下几部分组成:

640?wx_fmt=jpeg

  • Metadata Page Allocation:用于记录所有元数据页的分配情况。在分配或释放元数据页后,将会对metadata page allocation中的数据做相应的修改。

  • Cluster Allocation:用于记录所有cluster的分配情况。在分配新的cluster或释放cluster后会对cluster allocation中的数据做相应的修改。

  • Blob Id Allocation:用于记录blob id的分配情况。对于blobstore中的所有blob,都是通过唯一的标识符blob id将其对应起来。在元数据域中,将会在blob allocation中记录所有的blob id分配情况。

  • Metadata Pages Region:元数据页区域中存放着每个blob的元数据页。每个blob中所分配的cluster都会记录在该blob的元数据页中,在读写blob时,首先会通过blob id定位到该blob的元数据页,其次根据元数据页中所记录的信息,检索到对应的cluster。对于每个blob的元数据页,并不是连续的。

    对于一个blob来说,metadata page记录了该blob的所有信息,数据存放于分配给该blob的cluster中。在创建blob时,首先会为其分配blob id以及metadata page,其次更新metadata region。当对blob进行写入时,首先会为其分配cluster,其次更新该blob的metadata page,最后将数据写入,并持久化到磁盘中。

 

    为了实现对磁盘空间的动态分配管理,Blobstore中为每个blob分配的cluster并不是连续的。对于每个blob,通过相应的结构维护当前使用的cluster以及metadata page的信息:clusters与pages。Cluster中记录了当前该blob所有cluster的LBA起始地址,pages中记录了当前该blob所有metadata page的LBA起始地址。

    Blobstore实现了对磁盘空间分配的动态管理,并保证断电不丢失数据,因此Blob具有persistent特性。Blobstore中的配置信息与数据信息均在super block与metadata region中管理,在重启后,若要保持persistent,可以通过Blobstore中所提供的load操作。

注意:

Blob的persistent主要是针对NVMe这类bdev。对于Malloc bdev,由于其本身的性质,是无法保证Blob的persistent,需要重启后进行重新配置。

    下面通过文件的读写来讲解BlobFS与Blobstore中的I/O流程:

    文件读取:文件读取操作的流程图如下所示:

640?wx_fmt=jpeg

    为了提高文件的读取效率,BlobFS在内存中提供了cache buffer。在文件读写时,首先会进行read ahead操作,将一部分数据从磁盘预先读取到内存的buffer中。其后,根据cache buffer的大小,对文件的I/O进行切分,使每个I/O的最大长度不超过一个cache buffer的大小。对于拆分后的文件I/O,会根据其offset在cache buffer tree中查找相应的buffer。若存在,则直接从cache buffer中读取数据,进行memcpy。而对于没有缓存到cache buffer中的数据,将会对该文件的读取,转换到该文件对应的Blob进行读取。对Blob读取时候,根据已打开的blob结构中记录的信息,可以获取该blob所有cluster的LBA起始位置,并根据读取位置的offset信息,计算相应的LBA地址。最后向SPDK bdev层发送异步的读请求,并等待I/O完成。BlobFS所提供的读操作为同步读,I/O完成后会在callback函数中,通过信号量通知BlobFS完成信号,至此文件读取结束。

    对于cache buffer tree,其结构如下所示:

640?wx_fmt=jpeg

    Cache buffer tree是由多层树结构组成。最底层Level 0叶子节点为buffer node,是用于存放数据的buffer。Level 0以上的其它层中,均为tree node,用于构建树的索引结构。在文件读写的时候,根据文件结构中的根节点以及读取位置的offset信息,在树结构中通过索引查找buffer node的位置,即从Level N,逐步定位到对应的Level 0的叶子节点。

    文件写入:文件写入操作的流程图如下所示:

640?wx_fmt=jpeg

    BlobFS目前用于支持上层的Rocksdb,在Rocksdb的抽象环境层中提供文件的接口,目前仅支持append类型的写操作。在进行文件写入时,首先会根据文件当前的写入位置检查是否符合cache buffer写入需求,若满足,则直接将数据写入到cache buffer中,同时触发异步的flush操作。在flush的过程中,BlobFS触发Blob的写操作,将cache buffer中的数据,写入到文件对应blob的相应位置。若不满足cache buffer的写入需求,BlobFS则直接触发文件对应的blob的写操作。Blobstore首先为该blob分配cluster,根据计算得到的写入LBA信息,向SPDK bdev层发送异步的写请求,将数据写入,并更新相应的元数据。对于元数据的更新,出于性能考虑,当前对元数据的更新都在内存中操作,当用户使用强制同步或卸载Blobstore时,更新后的元数据信息才会同步到磁盘中。此外,blob结构中维护了两份可变信息(指cluster与metadata page)的元数据,分别为clean与active。Clean中记录的是当前磁盘的元数据信息,而active中记录的是当前在内存中更新后的元数据信息。同步操作会将clean中记录的信息与active记录的信息相匹配。

总结

    Blobstore实现对Blob管理,Blob类似与文件的概念,但又不完全等同于文件,Blob没有完全遵循文件的POSIX接口,因此避免与文件混淆,在SPDK中称之为Blob而不是File。Blobstore Filesystem (BlobFS)是基于Blobstore实现的轻量级文件系统,对Blobstore进行封装,提供一些文件的常用接口,如read、write、open、sync等,其目的在于作为文件系统支持更上层的应用,例如Rocksdb。但其本质仍然是Blobstore,因此命名为BlobFS。目前SPDK基于维护了Rocksdb的一个分支,该分支下的Rocksdb在环境抽象层主要通过BlobFS进行对接,I/O可以经由BlobFS绕过内核I/O栈。关于Rocksdb的搭建与测试步骤,可以参考[3]。

参考链接:

[1] https://spdk.io

[2] https://github.com/spdk/spdk

[3] https://spdk.io/doc/blobfs.html

640?wx_fmt=jpeg

 相关阅读 


640?wx_fmt=jpeg

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SPDK存储性能开发套件)官方文档中文版。 第一章 简介 1 1.1.什么是SPDK? 1 1.2.入门 1 1.3. Vagrant开发环境 3 1.4.更新日志(略) 6 第二章 概念 6 2.1. 用户空间驱动程序** 6 2.2. 来自用户空间的DMA** 7 2.3. 消息传递和并发** 9 2.4. NAND Flash SSD内部 13 2.5. 将I / O提交到NVMe设备** 15 2.5.1 NVMe规范 15 2.5.2 SPDK NVMe驱动程序I / O路径 15 2.6. 使用Vhost-user进行虚拟化I / O. 16 2.6.1 介绍 16 2.6.2 QEMU 17 2.6.3 设备初始化 18 2.6.4 I / O路径 19 2.6.5 SPDK优化 20 2.7. SPDK目录结构概述 20 2.8. SPDK移植指南 22 第三章 用户指南 22 3.1. 系统配置用户指南 22 3.1.1 IOMMU配置 22 3.2. SPDK应用程序概述 23 3.2.1 配置SPDK应用程序 23 3.3. iSCSI Target 26 3.3.1. iSCSI Target入门指南 26 3.3.2. 通过配置文件配置iSCSI Target 27 3.3.3. 通过RPC方法配置iSCSI Target 28 3.3.4. 配置iSCSI启动器 29 3.3.5. rpc配置示例*** 30 3.3.6. iSCSI 热插拔 32 3.4. NVMe over Fabrics Target 32 3.5. Vhost Target(略) 37 3.6 块设备用户指南 38 3.6.1 bdev介绍 38 3.6.2 通用RPC命令 38 3.6.3 Ceph RBD 39 3.6.4 压缩虚拟Bdev模块 40 3.6.5 加密虚拟Bdev模块 41 3.6.6 延迟vbdev模块 41 3.6.7 GPT(GUID分区表) 42 3.6.8 iSCSI bdev 43 3.6.9 Linux AIO bdev 43 3.6.10 OCF虚拟bdev 43 3.6.11 Malloc bdev 44 3.6.12 NULL bdev 44 3.6.13 NVMe bdev 44 3.6.14 逻辑卷Lvol 45 3.6.15 RAID 46 3.6.16 Passthru 46 3.6.17 Pmem 46 3.6.18 Virtio Block 47 3.6.19 Virtio SCSI 47 3.7 BlobFSBlobstore文件系统) 48 3.7.1 RocksDB集成 48 3.7.2 FUSE插件 49 3.8 JSON-RPC方法(略) 49 第四章 程序员指南 49 4.1. Blobstore程序员指南 49 4.1.1 介绍 50 4.1.2 运作理论 50 4.1.3 设计注意事项 52 4.1.4 例子 54 4.1.5配置 54 4.1.6 组件细节 54 4.2. 块设备层编程指南 56 4.3 编写自定义块设备模块 58 4.3.1 介绍 58 4.3.2 创建一个新模块 59 4.3.3创建虚拟Bdev 60 4.4 NVMe over Fabrics目标编程指南 61 4.4.1 介绍 61 4.4.2 原语结构体 61 4.4.3 基础函数 62 4.4.4访问控制 62 4.4.5发现子系统 62 4.4.6 传输 63 4.4.7选择线程模型 63 4.4.8 跨CPU核心扩展 63 4.4.9 零拷贝支持 63 4.4.10 RDMA 63 4.5 Flash传输层 64 4.5.1 术语 64 4.5.2 使用方法 67 4.6 GDB宏用户指南 69 4.6.1 介绍 69 4.6.2 加载gdb宏 71 4.6.3 使用gdb数据目录 72 4.6.4 使用.gdbinit加载宏 72 4.6.5 为什么我们需要显式调用spdk_load_macros 72 4.6.6 以上可用的宏总结 73 4.6.7 添加新宏 73 4.7 SPDK “Reduce”块压缩算法 73 4.7.1 介绍 73 4.7.2 例子 74 4.8 通知库 78 第五章 基本信息 79 5.1 事件框架 79 5.1.1 事件框架设计注意事项 80 5.1.2 SPDK事件框架组件 80 5.1.3 应用框架 80 5.2 逻辑卷 81 5.2.1 术语 81 5.2.2 配置逻辑卷 84 5.3 矢量数据包处理(略) 86 第六章 杂项 86 6.1 介绍 86 6.2 NVMe的P2P API 86 6.3 确定设备支持 87 6.4 P2P问题 87 第七章 驱动程序 88 7.1 NVMe驱动程序*** 88 7.1.1 介绍 88 7.1.2 例子 88 7.1.3 公共接口 89 7.1.4 NVMe驱动程序设计 89 7.1.5 NVMe over Fabrics主机支持 91 7.1.6 NVMe多进程 91 7.1.7 NVMe Hotplug 92 7.2 I/OAT驱动程序 93 7.2.1 公共接口 93 7.2.2 关键功能 93 7.3 Virtio驱动程序 93 7.3.1 介绍 93 7.3.2 2MB大页面 93 第八章 工具 94 8.1 SPDK CLI 94 8.1.1 安装所需的依赖项 94 8.1.2 运行SPDK应用程序实例 94 8.1.3 运行SPDK CLI 94 8.1.4 可选 - 创建Python虚拟环境 94 8.2 nvme-CLI 95 8.2.1 nvme-cli with SPDK入门指南 95 8.2.2 使用场景 95 第九章 性能测试报告(略) 96 第十章NVMe-oF Target跟踪点*** 96 10.1 介绍 96 10.2 启用跟踪点 97 10.3 捕获事件的快照 97 10.4 捕获足够的跟踪事件 98 10.5 添加新的跟踪点 99
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值