ceph bluestore 磁盘空间管理源码解析

ceph minic版本的bluestore默认使用BitmapFreelistManager来管理磁盘空闲空间,并将磁盘空间使用情况固化到rocksdb。同时bluestore使用StupidAllocator来分配磁盘空间。在bluestore申请空间时要BitmapFreelistManager和StupidAllocator配合完成。

关于FreelistManager,在osd上电时,会调用_open_fm来初始化磁盘使用情况,_open_fm函数调用栈如下

    fm = FreelistManager::create(cct, freelist_type, db, PREFIX_ALLOC);
        return new BitmapFreelistManager(cct, kvdb, "B", "b");
    if (create)
        fm->create(bdev->get_size(), (int64_t)min_alloc_size, t); //运行BitmapFreelistManager::create
            bytes_per_block = granularity;  //因为一次分配min_alloc_size大小的空间
            size = p2align(new_size, bytes_per_block);  //往下对齐 即返回不大于new_size的可以整除bytes_per_block的数
            blocks_per_key = cct->_conf->bluestore_freelist_blocks_per_key;  //128个
            _init_misc();
            
            blocks = size / bytes_per_block;
            if (blocks / blocks_per_key * blocks_per_key != blocks) 
                blocks = (blocks / blocks_per_key + 1) * blocks_per_key; //如果size不能被blocks_per_key整除,就向上扩大blocks
                _xor(size, blocks * bytes_per_block - size, txn); //设置人为造成的多出的空间已使用
                txn->set(meta_prefix, "bytes_per_block", bl);
                txn->set(meta_prefix, "blocks_per_key", bl);
                txn->set(meta_prefix, "blocks", bl);
                txn->set(meta_prefix, "size", bl);
        uint64_t reserved = round_up_to(std::max<uint64_t>(SUPER_RESERVED, min_alloc_size), min_alloc_size);   //SUPER_RESERVED 8192
        fm->allocate(0, reserved, t); //0-reserved区间标记使用
            _xor(offset, length, txn); 
            
        if (cct->_conf->bluestore_bluefs)//bluefs使用的磁盘空间用bluefs_extents标记,不标记在freelist
            interval_set<uint64_t>::iterator p = bluefs_extents.begin();  //open_db中bluefs_extents.insert(start, initial);
            //block extents owned by bluefs
            reserved = round_up_to(p.get_start() + p.get_len(), min_alloc_size);
            t->set(PREFIX_SUPER, "bluefs_extents", bl);
        db->submit_transaction_sync(t);
        fm->init()  //从rocksdb中加载bytes_per_block、blocks、size、blocks_per_key等,如果不是创建osd,则会运行这个

首先创建FreelistManager,因为默认类型是"bitmap",所以创建BitmapFreelistManager。如果是创建osd,则create是True,这里会初始化一些设置,比如bytes_per_block、size等,并将这些参数固化到rocksdb。同时标记磁盘前SUPER_RESERVED(给label和bluefs super block使用)字节空间已经使用。_xor函数就是反转某段磁盘空间的使用状态,同时将使用情况固化到rocksdb。另外bluefs使用的磁盘空间用bluefs_extents标记,不标记在freelist。
如果不是创建osd,则直接利用fm->init()来从rocksdb中读取size,bytes_per_block等配置。

BitmapFreelistManager设置完毕后,开始调用_open_alloc来初始化StupidAllocator,_open_alloc的函数调用栈如下:

alloc = Allocator::create(cct, cct->_conf->bluestore_allocator, bdev->get_size(), min_alloc_size); //"stupid"
    return new StupidAllocator(cct);
fm->enumerate_reset();    
while (fm->enumerate_next(&offset, &length))
    alloc->init_add_free(offset, length);
        _insert_free(offset, length);
			unsigned bin = _choose_bin(len);
			while (true)
			    free[bin].insert(off, len, &off, &len); //free大小为10,在StupidAllocator构造函数中初始化
			    unsigned newbin = _choose_bin(len);
			    if (newbin == bin)    
			         break;
			    free[bin].erase(off, len);
			    bin = newbin;
        num_free += length;
for (auto e = bluefs_extents.begin(); e != bluefs_extents.end(); ++e) 
    alloc->init_rm_free(e.get_start(), e.get_len());

bluestore默认使用StupidAllocator来分配磁盘空间。循环调用BitmapFreelistManager::enumerate_next来获取磁盘下一段空闲块,然后利用StupidAllocator::init_add_free来将获取到的块插入到free中,free的定义如下

std::vector<interval_set_t> free;  ///< leading-edge copy
typedef interval_set<uint64_t,interval_set_map_t> interval_set_t; 
typedef btree::btree_map<uint64_t,uint64_t,std::less<uint64_t>,allocator_t> interval_set_map_t;  

可以看到free是一个vector(大小为10),其元素类型包含一个btree结构,因此StupidAllocator是用btree来保存空闲块的,free中包含了10个btree。_choose_bin是计算块大小的数量级,目的为了将相同数量级大小的块插入到同一棵btree中。_insert_freej就是将[off,off+len]的磁盘空间插入到btree中,如果btree中有和[off,off+len]空间连续的其他空间,就将这两个空间合并,并返回最新合并后的空间的开始地址和长度,如果合并后的长度的数量级仍属于这个btree就退出,反之就从该btree中删除这个较大的合并后的空间,并插入到对应数量级的btree中。

分配空间
根据bluestore写操作中的处理来看bluestore如何分配空间的
在写操作中的_do_alloc_write函数中,会调用StupidAllocator::allocate来分配btree中的空闲空间,其调用栈如下:

alloc->allocate(need, min_alloc_size, need, 0, &prealloc);
    while (allocated_size < want_size)
        allocate_int(std::min(max_alloc_size, (want_size - allocated_size)), alloc_unit, hint, &offset, &length);
            uint64_t want = std::max(alloc_unit, want_size);    
            int bin = _choose_bin(want);   //我感觉是计算want处于的一个数量级,最大为10,因为free vector代表10个数量级
                len = orig_len / cct->_conf->bdev_block_size;
                bin = std::min((int)cbits(len), (int)free.size() - 1);
                int orig_bin = bin;
            auto p = free[0].begin();//free中一个元素代表一个数量级,free总共有10个树
            if (!hint)
                hint = last_alloc; //hint设置为上次已分配的磁盘的偏移        
            if (hint)
                for (bin = orig_bin; bin < (int)free.size(); ++bin) {
                    p = free[bin].lower_bound(hint);//寻找不小于上次分配的地址
                    while (p != free[bin].end())
                        if (_aligned_len(p, alloc_unit) >= want_size) //找到足够的空间
                            goto found;
                        ++p;
                for (bin = orig_bin; bin < (int)free.size(); ++bin)
                    p = free[bin].begin();
                    auto end = hint ? free[bin].lower_bound(hint) : free[bin].end();
                    while (p != end)
                        if (_aligned_len(p, alloc_unit) >= want_size)
                            goto found;
                        p++;
                if (hint)
                    for (bin = orig_bin; bin >= 0; --bin)//在前面的树里查找
                        p = free[bin].lower_bound(hint); 
                        while (p != free[bin].end())
                            if (_aligned_len(p, alloc_unit) >= alloc_unit)
                                goto found;
                            ++p;
                
                for (bin = orig_bin; bin >= 0; --bin)
                    p = free[bin].begin();
                    auto end = hint ? free[bin].lower_bound(hint) : free[bin].end();
                    while (p != end) 
                        if (_aligned_len(p, alloc_unit) >= alloc_unit)
                            goto found;
                        ++p;
                

                uint64_t skew = p.get_start() % alloc_unit;
                if (skew)
                    skew = alloc_unit - skew;
                *offset = p.get_start() + skew; //从后面最近的按照alloc_unit对齐的地址开始
                *length = std::min(std::max(alloc_unit, want_size), p2align((p.get_len() - skew), alloc_unit));
                
                free[bin].erase(*offset, *length); //从树中去掉这块区域
                if (*offset && free[bin].contains(*offset - skew - 1, &off, &len))  //包含为了对齐,头部浪费的空间
                    int newbin = _choose_bin(len);
                    if (newbin != bin)  //如果数量级没有变化,则不需要变换位置
                        free[bin].erase(off, len);
                        _insert_free(off, len);
                if (free[bin].contains(*offset + *length, &off, &len))//包含为了对齐,尾部浪费的空间
                    int newbin = _choose_bin(len);
                    if (newbin != bin)//如果数量级没有变化,则不需要变换位置
                        free[bin].erase(off, len);
                        _insert_free(off, len);
                
                last_alloc = *offset + *length; //更新上次被分配的偏移地址

StupidAllocator分配空间时优先分配上次分配空间末尾地址后的空间。

在_do_alloc_write函数中还会把分配好的pextent插入到TransContext中的allocated中,如下

for (auto& p : extents) 
    txc->allocated.insert(p.offset, p.length);//offset为在磁盘上的偏移

此时已分配空间只是从StupidAllocator的btree中删除记录,并没有固化到rocksdb中,bluestore是在_txc_finalize_kv中将txc->allocated中的分配记录写到rocksdb中的,如下‘

_txc_finalize_kv(txc, txc->t);
    interval_set<uint64_t> *pallocated = &txc->allocated;
    interval_set<uint64_t> *preleased = &txc->released;
    for (interval_set<uint64_t>::iterator p = pallocated->begin(); p != pallocated->end(); ++p)
        fm->allocate(p.get_start(), p.get_len(), t);
    for (interval_set<uint64_t>::iterator p = preleased->begin(); p != preleased->end();  ++p)
        fm->release(p.get_start(), p.get_len(), t);

其中会调用BitmapFreelistManager::allocate函数将分配的空间记录写到rocksdb中

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值