文章目录
疑问1:为什么有了FreelistManager还需要Allocator问题澄清疑问2:逻辑段和位图区别Data StructureInitUsageBlueFSBlueStoreConfigSummary疑问1:为什么有了FreelistManager还需要Allocator
问题澄清
1、BlueFS不用FreelistManager管理块设备空间的使用情况,而是将其持久化记录在文件系统的日志文件中
2、BlueStore自己的slow空间(存放data),当写object文件的时候,先通过Allocator分配磁盘存储空间,仅仅在内存中将空间标记为已分配,并封装在写操作的事务中,待后续完成写操作的时候,才会更新FreelistManger的空闲空间,并将对象的磁盘空间信息记录在对象的metadata中。不同的写case(new/cow/overwrite),封装的事务也不同,目的是保证数据的一致性,所以磁盘空间的管理和写操作是密切相关的,不能简单的调用FreelistManager的接口完事。
综上:
Allocator只负责在内存中将空闲空间标记为已分配,最终磁盘空间使用情况的持久化操作,由Allocator的使用者负责,BlueFS将其记录在文件系统的日志中,BlueStore通过FreelistManager将其存储在k/v中,并在对象metadata中记录对象的磁盘空间信息。
目前系统中有StupidAllocator(基于extent)和BitMapAllocator两种实现,最开始用的Stupid,后来换成BitMap,但是最近因为性能问题又将默认的改回Stupid
疑问2:逻辑段和位图区别
1、当管理内存较大时,使用逻辑段可以节省内存空间
例如:位图每个位控制一个4K block,每个逻辑点的地址消耗为(64offset+64length)128位,,所以如果以最小逻辑块管理内存,位图消耗空间是逻辑段的128倍。
但是逻辑段指定地址可以动态设置,当物理内存特别大时,使用逻辑段可以用更小的内存,管理更大的物理空间。
2、位图查找速度快
1G空间需要消耗256kb内存管理,获取空闲地址时可以直接按位取反。
Data Structure
Allocator实现的时候,主要数据结构用到了区间树,高效的管理(offset, length):
class StupidAllocator : public Allocator {int64_t num_free;// 总的空闲大小int64_t num_reserved; // 预留空间// 初始化的时候,free数组的长度为10,即有十颗区间树// 根据每个区间的长度,分别插入不同的区间树std::vector<btree_interval_set<uint64_t,allocator>> free;uint64_t last_alloc;};
Init
创建Allocator后,调用者紧接着会向Allocator中加入或删除空闲空间:
// 增加空闲空间void StupidAllocator::init_add_free(uint64_t offset, uint64_t length){std::lock_guard<std::mutex> l(lock);_insert_free(offset, length); // 向free中插入数据num_free += length; // 更新可用空间}// 删除空闲空间void StupidAllocator::init_rm_free(uint64_t offset, uint64_t length){std::lock_guard<std::mutex> l(lock);btree_interval_set<uint64_t,allocator> rm;rm.insert(offset, length);for (unsigned i = 0; i < free.size() && !rm.empty(); ++i) {btree_interval_set<uint64_t,allocator> overlap;overlap.intersection_of(rm, free[i]); // 求交集if (!overlap.empty()) { // 删除free[i].subtract(overlap);rm.subtract(overlap);}}num_free -= length; // 更新可用空间}
核心实现函数就是向free的区间树插入区间:
// 根据区间的长度,选取将要存放的区间树,长度越大,bin值越大unsigned StupidAllocator::_choose_bin(uint64_t orig_len){uint64_t len = orig_len / cct->_conf->bdev_block_size;// cbits = (sizeof(v) * 8) - __builtin_clzll(v);// 结果是最高位1的下标,len越大,值越大int bin = std::min(int)cbits(len), (int)free.size() - 1);return bin;}void StupidAllocator::_insert_free(uint64_t off, uint64_t len){unsigned bin = _choose_bin(len); // 计算区间树的idwhile (true) {free[bin].insert(off, len, &off, &len);unsigned newbin = _choose_bin(len);if (newbin == bin)break;// 插入数据后,可能合并区间,导致区间长度增大,可能要调整bin,此时需要将旧的删除,然后插入新的binfree[bin].erase(off, len);bin = newbin;}}
Allocate & Release用来分配和回收空闲空间,最终都是调用区间树的insert和erase API,不再赘述。
Usage
Allocator本身比较简单,更多的应该关注怎么使用它,特别是怎么将分配信息最终持久化。
BlueFS
BlueFS分配空间的函数:
int BlueFS::_allocate(uint8_t id, uint64_t len, mempool::bluefs::vector<bluefs_extent_t> *ev){if (alloc[id]) {r = alloc[id]->reserve(left); // 预留空间}if (r < 0) {if (id != BDEV_SLOW) { // 空间不足,递归降级到下一级的设备return _allocate(id + 1, len, ev);}......}AllocExtentVector extents;extents.reserve(4);int64_t alloc_len = alloc[id]->allocate(left, min_alloc_size, hint, &extents); // 真正分配空间的操作......}
这个函数在多种情况下会调用,包括为普通文件分配空间的_preallocate以及和日志文件相关的compact/flush等操作。以普通文件为例:
int BlueFS::_preallocate(FileRef f, uint64_t off, uint64_t len){if (f->deleted) {return 0;}uint64_t allocated = f->fnode.get_allocated();if (off + len > allocated) {uint64_t want = off + len - allocated;int r = _allocate(f->fnode.prefer_bdev, want, &f->fnode.extents); // 分配空间if (r < 0)return r;f->fnode.recalc_allocated();log_t.op_file_update(f->fnode); // 记录日志,日志就是文件的inode信息,inode中包含extents,即物理磁盘空间}return 0;}
在BlueFS的超级快(SuperBlock)中,记录了日志文件的inode,异常情况下通过重新mount文件系统,读取超级块,定位到日志文件,然后读取日志进行回放,重建所有文件的内存映像(file_map),遍历file_map,即可初始化Allocator的空间。
int BlueFS::mount(){......r = _replay(false); // 重建file_map......for (auto& p : file_map) {for (auto& q : p.second->fnode.extents) {alloc[q.bdev]->init_rm_free(q.offset, q.length); // 将已有文件占用的磁盘块从allocator中删除}}}
BlueStore
BlueStore的情况,涉及到读写object文件的IO流程,留着分析BlueStore时介绍。
Config
配置参数比较简单,目前默认用stupid,不需要调整任何参数。
bluefs_allocator // 默认为stupidbluestore_allocator // 默认为stupid// bitmap allocator 相关的参数bluestore_bitmapallocator_blocks_per_zonebluestore_bitmapallocator_span_size
Summary
•Allocator用来分配磁盘空间,只在内存做标记,实现包含Stupid和BitMap两种,Stupid即为基于extent的方式
•Allocator的使用者在创建Allocator的时候需要初始化可用空间,并且在分配空间后,需要使用者在合适时机将磁盘空间使用信息持久化
•Allocator的使用者包含BlueFS和BlueStore,BlueFS通过文件系统的日志文件固化磁盘空间使用情况,BlueStore通过FreelistManager将磁盘空间信息固化到k/v中
•Allocator默认的stupid实现比较简单,理解Allocator在整个BlueStore存储引擎中的作用以及使用方式比其实现更有意义