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_file | spdk_fs_open_file_async | |
创建文件 | spdk_fs_create_file | spdk_fs_create_file_async | |
删除文件 | spdk_fs_delete_file | spdk_fs_delete_file_async | |
重命名文件 | spdk_fs_rename_file | spdk_fs_rename_file_async | |
文件状态 | spdk_fs_file_stat | spdk_fs_file_stat_async | |
写 | spdk_file_write | spdk_file_write_async/sspdkfile_writev_async | |
读 | spdk_file_read | spdk_file_read_async/sspdkfile_readv_async | |
truncate | spdk_file_truncate | spdk_file_truncate_async | |
sync | spdk_file_sync | ||
关闭 | spdk_file_close | spdk_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时自动构建。
|
|
|
|
BlobFS当前限制
现有BlobFS只在RocksDB上进行了测试,其他不同于RocksDB的文件系统使用场合可能会有问题,以后会进行更严格的测试;
现在只支持同步操作API。异步API开发中,未经过严格测试,将于以后版本中完成;
文件
rename
API不是原子操作。将于未来版本修复;当前不支持目录,只支持扁平的文件命名空间。文件名作为xattrs存储于blob中,文件名
lookup
为O(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栈。