ceph bluefs 日志压缩解析

bluefs的操作大量增加时,日志也会增加,从而增加占用空间,由于文件系统的元数据在内存中都有记录,且内存中的元数据都是非重复的,因此可以通过遍历元数据,将元数据重新写到日志文件当中,即可实现日志的压缩,具体是_compact_log_async函数来实现的,其实现如下:

old_log_jump_to = log_file->fnode.get_allocated();  //获取已经分配的空间,这些空间最后要释放
_allocate(log_file->fnode.prefer_bdev, cct->_conf->bluefs_max_log_runway, &log_file->fnode); //重新在快速设备中开辟一段新的空间
log_t.op_file_update(log_file->fnode);
log_t.op_jump(log_seq, old_log_jump_to);
_flush_and_sync_log(l, 0, old_log_jump_to);
    
_compact_log_dump_metadata(&t); //压缩日志
new_log_jump_to = round_up_to(t.op_bl.length() + super.block_size * 2, cct->_conf->bluefs_alloc_size)
t.op_jump(log_seq, new_log_jump_to);
_allocate(BlueFS::BDEV_DB, new_log_jump_to, &new_log->fnode);
    
new_log_writer = _create_writer(new_log);
new_log_writer->append(bl);   //在新的空间里写
    
_flush(new_log_writer, true);
_flush_bdev_safely(new_log_writer);  //此时,原来的日志已经写在新空间里了
    
while (discarded < old_log_jump_to)
    bluefs_extent_t& e = log_file->fnode.extents.front();
    bluefs_extent_t temp = e;
    if (discarded + e.length <= old_log_jump_to) //这个pextent可以完全释放
        discarded += e.length;
        log_file->fnode.pop_front_extent(); //将这个pextent出栈
    else //该pextent只能释放前一部分
        uint64_t drop = old_log_jump_to - discarded;
        temp.length = drop; //将长度变小,就相当于截取pextent的前一部分了
        e.offset += drop;
        e.length -= drop;  //e代表原来的pextent,这里更新原来的pextent的元数据
    old_extents.push_back(temp);
//经过上述的while循环后,old_extents中保存了旧日志文件中old_log_jump_to位置之前的日志,旧日志文件中保存了old_log_jump_to位置之后的pextent

    
auto from = log_file->fnode.extents.begin();
auto to = log_file->fnode.extents.end();
while (from != to)
    new_log->fnode.append_extent(*from);  //将原先log文件的pextent元数据加入new_log
    ++from;
    
log_file->fnode.clear_extents();
new_log->fnode.swap_extents(log_file->fnode);  //交换新老日志文件的pextent,
    
log_writer->pos = log_writer->file->fnode.size = log_writer->pos - old_log_jump_to + new_log_jump_to; //更新日志文件的写偏移,即日志的最后一项

super.log_fnode = log_file->fnode;
++super.version;
_write_super();  //将super写的super block中
    encode(super, bl);
    encode(crc, bl);
    bdev[BDEV_DB]->write(get_super_offset(), bl, false);

for (auto& r : old_extents)  //释放老日志文件中,old_log_jump_to位置之前的空间
    pending_release[r.bdev].insert(r.offset, r.length);

(1)计算log_file目前已经分配磁盘空间大小,利用old_log_jump_to标记,这部分空间在压缩后可以释放。同时在块设备block.wal中申请新的空间用于以后的日志存储。
(2)调用_compact_log_dump_metadata函数来压缩日志,并设置下一个日志项的地址new_log_jump_to,注意new_log_jump_to是根据bluefs_alloc_size(1M)对齐的,因此很多时候新的日志文件中有一些空间是为了对齐没有使用的,向设备block.db申请new_log_jump_to大小的空间存放这些压缩后的日志。
(3)调用_flush和_flush_bdev_safely将压缩后的日志刷新到磁盘设备中。
(4)经过一系列的swap等操作后,log_file就只保存了压缩后的日志和op_jump(log_seq, new_log_jump_to)日志,然后把log_file的fnode写入到super中。
(5)最后将log_file中原来的处于old_extents的老日志所占用的空间释放。

关于磁盘压缩过程中磁盘空间的变化如下图所示
ceph bluefs 日志压缩解析_第1张图片
原来日志是存放在block.wal设备中,将压缩后的日志放在block.db设备中,并且最后一条日志为op_jump,所跳到位置就是new_log_jump_to,而在op_jump和new_log_jump_to之间是为了对齐而多出的空间,因此日志回放过程中遇到op_jump就会跳到new_log_jump_to,从new_log_jump_to开始读取block.wal设备中新增加的日志。因此压缩的日志是存放在block.db设备中的,未压缩的日志是存放在block.wal设备中的。

最后关于日志的压缩函数_compact_log_dump_metadata,其实现如下:

t->op_init();
for (unsigned bdev = 0; bdev < MAX_BDEV; ++bdev)
    interval_set& p = block_all[bdev];
    for (interval_set::iterator q = p.begin(); q != p.end(); ++q)
        t->op_alloc_add(bdev, q.get_start(), q.get_len());
for (auto& p : file_map)
    if (p.first == 1)
        continue;
    t->op_file_update(p.second->fnode);

for (auto& p : dir_map)
    t->op_dir_create(p.first);
    for (auto& q : p.second->file_map)
        t->op_dir_link(p.first, q.first, q.second->fnode.ino);

可以看到日志压缩就是主要是记录bluefs的总空间、目录、文件、目录和文件的联系,在日志回放的时候就可以仅根据这些信息来还原出bluefs文件组织结构,这样可以把不需要的重复日志删除。

你可能感兴趣的:(ceph,ceph,bluefs)