73

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

 5 years ago
source link: https://www.sdnlab.com/22880.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

转载自DPDK与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为例):

001.jpg

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组成,它们之间的关系如下所示:

002-.jpg

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的结构如下:

003-.jpg

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

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

004-.jpg

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流程:

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

005-.jpg

为了提高文件的读取效率,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,其结构如下所示:

006-.jpg

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

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

007-.jpg

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK