BlobStore

BlobStore


简介

SPDK bdev层类似于内核中的通用块设备层,是对底层不同类型设备(如NVMe bdev、Malloc bdev、AIO bdev等)的统一抽象管理。

BlobStore是位于SPDK bdev之上,通过不同层级的抽象,实现对磁盘块(LBA)的管理,实现了对Blob的管理,包括Blob的分配、删除、读取、写入、元数据的管理等;

BlobFS是在Blobstore的基础上进行封装的一个轻量级文件系统,用于提供部分对于文件操作的接口,并将对文件的操作转换为对Blob的操作,

用于与用户态文件系统Blobstore Filesystem (BlobFS)集成,从而代替传统的文件系统,支持更上层的服务,如数据库MySQL、K-V存储引擎Rocksdb以及分布式存储系统Ceph、Cassandra等。


BlobStore

数据块管理

在blobstore中,将SSD中的块划分为多个抽象层:


  • 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中,会将cluster 0作为一个特殊的cluster。

该cluster用于存放Blobtore的所有信息以及元数据,对每个blob数据块的查找、分配都是依赖cluster 0中所记录的元数据所进行的。

Cluster 0的结构如下:

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


元数据

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

  • Metadata Page Allocation:用于记录所有元数据page的分配情况。在分配或释放元数据页后,将会对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实现了对磁盘空间分配的动态管理,并保证断电不丢失数据,具有persistent特性。

Blobstore中的配置信息与数据信息均在super block与metadata region中管理,在重启后,若要保持persistent,可以通过Blobstore中所提供的load操作。

注意

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


BlobFS

BlobFS 文件接口

blobfs文件系统接口实现了基本的文件操作,

操作同步API异步API
打开文件spdk_fs_open_filespdk_fs_open_file_async
创建文件spdk_fs_create_filespdk_fs_create_file_async
删除文件spdk_fs_delete_filespdk_fs_delete_file_async
重命名文件spdk_fs_rename_filespdk_fs_rename_file_async
文件状态spdk_fs_file_statspdk_fs_file_stat_async
spdk_file_writespdk_file_write_async/sspdkfile_writev_async
spdk_file_readspdk_file_read_async/sspdkfile_readv_async
truncatespdk_file_truncatespdk_file_truncate_async
syncspdk_file_sync
关闭spdk_file_closespdk_file_close_async

缓存

为了提高文件的读取效率,BlobFS在内存中提供了cache buffer,由多层树结构组成,其结构如下所示:

  • 最底层Level 0叶子节点为buffer node,是用于存放数据的buffer。

  • Level 0以上的其它层中,均为tree node,用于构建树的索引结构。

  • 在文件读写的时候,根据文件结构中的根节点以及读取位置的offset信息,在树结构中通过索引查找buffer node的位置,即从Level N,逐步定位到对应的Level 0的叶子节点。


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记录的信息相匹配。


读流程


在文件读写时:

  • 首先会进行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完成信号,至此文件读取结束。


BlobFS FUSE

BlobFS提供了一个FUSE插件,用于将SPDK BlobFS作为内核文件系统安装,以便进行检查或调试。FUSE插件需要fuse3,并在系统上检测到fuse3时自动构建。

1
test/blobfs/fuse/fuse /usr/local/etc/spdk/rocksdb.conf Nvme0n1 /mnt/fuse
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
static struct fuse_operations spdk_fuse_oper = {
    .getattr    = fuse_getattr,
    .readdir    = fuse_readdir,
    .mknod        = fuse_mknod,
    .unlink        = fuse_unlink,
    .truncate    = fuse_truncate,
    .utimens    = fuse_utimens,
    .open        = fuse_open,
    .release    = fuse_release,
    .read        = fuse_read,
    .write        = fuse_write,
    .flush        = fuse_flush,
    .fsync        = fuse_fsync,
    .rename        = fuse_rename,
};

BlobFS当前限制

  • 现有BlobFS只在RocksDB上进行了测试,其他不同于RocksDB的文件系统使用场合可能会有问题,以后会进行更严格的测试;

  • 现在只支持同步操作API。异步API开发中,未经过严格测试,将于以后版本中完成;

  • 文件renameAPI不是原子操作。将于未来版本修复;

  • 当前不支持目录,只支持扁平的文件命名空间。文件名作为xattrs存储于blob中,文件名lookupO(n)级。btree版本目录支持实现将于未来版本支持;

  • 当前write操作仅支持append到文件末尾。任意位置写操作将在未来版本实现;

总结

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栈。


参考

  1. SPDK: BlobFS (Blobstore Filesystem)

  2. https://www.sdnlab.com/22880.html

  3. SPDK在字节跳动存储业务中的应⽤

updatedupdated2024-05-102024-05-10