当前位置: 首页 > news >正文

谈谈BlueStore的BitmapAllocator

背景

BlueStore在ceph里面承担了数据在底层磁盘上的读写任务,那它的功能里自然就有一块是管理磁盘空间使用的。说白了,就是记录&管理磁盘里哪些空间已经使用了,哪些空间还没有被使用。
目前官方的ceph里使用BitmapAllocator来管理磁盘空间使用。
简单的说就是用一个二进制bit来表面一个磁盘区间是否被使用了。
咱们先说几个关键术语:

bdev_block_size:磁盘块大小,IO的最小单元,默认4K。
min_alloc_size:最小分配单元,SSD默认16K,HDD默认64K。
max_alloc_size:最大分配单元,默认0不限制,即一次连续的分配结果只包含一个extent。
alloc_unit:分配单元(AU),一般设置为min_alloc_size。

一次allocate的单元就是alloc_unit,咱们就按64KB计算。这个二进制就只需要1个Bit。1个字节的bit位就可以表示512KB。那这么来说一个1TB的磁盘就需要2MB的内存空间。每次allocate平均就需要遍历整个2MB内存的一半(一共2MB,平均一次就取一半计算)。效率略低。
后面就有了改良版的BitmapAllocator。
初始版的BitmapAllocator是使用一个字节序列来标识整个磁盘空间。改良版是使用3个字节序列来标识磁盘空间。
如下:
在这里插入图片描述
分别有3个字节序列,分别是L0,L1,L2,其中L0最接近磁盘。
记住下面这些概念

每一层自己都有slot,每个slot的大小都是64Bit,每个slot里面有children。
L2和L0层1个slot管理自己slot里面的64个children,L1层1个slot管理32个children L2 L0
一个children是1个bit;L1层一个children是2个bit 每个children管理下层8个slot
一个L0的children就是1个AU(Alloc_Unit),默认64KB(HDD)
一个L1的children,管理L0里8个slot,L0里1个slot管理64个children,也就是说L1的一个children对应L0
864个children
一个L2的children,管理L1里8个slot,L1层1个slot管理32个children,也就是说L2的一个children对应L1
8
32个children

1个L0的childre 对应 64KB 1个L1的solt对应4MB
1个L1的children 对应 86464KB=32MB 1个L1的solt对应1G
1个L2的children 对应 83232MB = 8G 1个L2的solt 对应 512GB
如果4TB的磁盘,在L2层就需要8个slot

生命周期&使用sample

// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab
/** In memory space allocator test cases.* Author: Ramesh Chander, Ramesh.Chander@sandisk.com*/
#include <iostream>
#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>#include "common/Cond.h"
#include "common/errno.h"
#include "include/stringify.h"
#include "include/Context.h"
#include "os/bluestore/Allocator.h"using namespace std;typedef boost::mt11213b gen_type;class AllocTest : public ::testing::TestWithParam<const char*> {public:boost::scoped_ptr<Allocator> alloc;AllocTest(): alloc(0) { }void init_alloc(int64_t size, uint64_t min_alloc_size) {std::cout << "Creating alloc type " << string(GetParam()) << " \n";alloc.reset(Allocator::create(g_ceph_context, GetParam(), size,min_alloc_size));}void init_close() {alloc.reset(0);}
};TEST_P(AllocTest, mytest)
{uint64_t block = 64*1024;//4TBuint64_t size = 4L*1024*1024*1024*1024L;init_alloc(size, block);alloc->init_add_free(0, size);PExtentVector extents;alloc->allocate(512L*1024*1024*1024, block, 0, (int64_t)0, &extents);std::cout<<"-------"<<std::endl;alloc->allocate( block*3, block, 0, (int64_t)0, &extents);std::cout<<"xxxxxxxxxxxx"<<std::endl;alloc->allocate( block*4, block, 0, (int64_t)0, &extents);
}INSTANTIATE_TEST_SUITE_P(Allocator,AllocTest,::testing::Values("stupid", "bitmap", "avl", "hybrid", "btree"));

以上代码参考在src/test/objectstore/Allocator_test.cc

相关类图

AllocatorLevel是基础类,磁盘分配器需要实现这个类

class AllocatorLevel01Loose : public AllocatorLevel01
class AllocatorLevel01 : public AllocatorLevel
class AllocatorLevel02 : public AllocatorLevelclass BitmapAllocator : public Allocator,public AllocatorLevel02<AllocatorLevel01Loose> 

分配原理

在构造函数里

BitmapAllocator::BitmapAllocator(CephContext* _cct,int64_t capacity,int64_t alloc_unit,std::string_view name) :Allocator(name, capacity, alloc_unit),cct(_cct)
{_init(capacity, alloc_unit, false);
}static const slot_t all_slot_set = 0xffffffffffffffff;
static const slot_t all_slot_clear = 0;

在_init里面会把L2,L1,L0的每个solt(8个字节,64个bit,刚好是一个long long 无符号整数)都置成0。
之后
init_add_free 的时候
又把L2,L1,L0层的每个槽位都置成为0xffffffffffffffff

每次分配空间的时候,就先从L2找,
如果L2的槽位是0,就说明这个槽位已经完全都分配了,查找下一个槽位
如果L2的槽位是上0xffffffffffffffff,就说明整个槽位都还没有分配过任何空间,完全是空闲的,把free_pos设为0,然后去L1查找
如果L2的槽位既不是0也不是0xffffffffffffffff,说明这个槽位分配过数据,但是又没有完全分配,计算出free_pos后,传递给L1进行allocate
上面的逻辑是我从代码里分析来的,但是我感觉很疑惑,为什么

如果L2的槽位是上0xffffffffffffffff,就说明整个槽位还完全没有分配

0xffffffffffffffff不是all_slot_set 么?它不应该代表已经完全都分配了么??!!
希望熟悉这块的小伙伴不吝赐教。
第一次进入_allocate_l1之后,第一个槽位是0xffffffffffffffff,在上面进行分配空间,这部分逻辑可以参考:https://zhuanlan.zhihu.com/p/643938193
另外l1._allocate_l1 的返回值如果是true,就说明在L1对应的8个solt都已经分配完全了。如果真的返回true,那就需要修改L2上对应pos的bit为0。代表已经分配过了。

我任务这里面最麻烦的就是不知道什么时候0代表已经分配,什么时候1代表已经分配。

参考资料

https://zhuanlan.zhihu.com/p/643938193

相关文章:

  • D - New Friends(AtCoder Beginner Contest 350)
  • 海外仓快递系统哪个好?教你快速选到适合自己的管理系统
  • # linux 中使用 visudo 命令,怎么保存退出?
  • 【网络】高级IO(select||poll||epoll)
  • 中断处理过程介绍
  • 9. C++通过epoll+fork的方式实现高性能网络服务器
  • 前端面试题日常练-day37 【面试题】
  • 合并featurecount产生的多个表达矩阵文件
  • busco,checkM2:基因组或MAG完整度分析
  • Web开发——HTMLCSS
  • HTTP方法、状态码和请求过程
  • 安装 Android Studio 2024.1.1.6(Koala SDK35)和过程问题解决
  • Python开发 —— 错误ModuleNotFoundError: No module name
  • Java原生JDBC概览
  • 快速排序算法备考
  • Angular js 常用指令ng-if、ng-class、ng-option、ng-value、ng-click是如何使用的?
  • echarts花样作死的坑
  • IE报vuex requires a Promise polyfill in this browser问题解决
  • JavaScript对象详解
  • Java基本数据类型之Number
  • PHP面试之三:MySQL数据库
  • React系列之 Redux 架构模式
  • vue+element后台管理系统,从后端获取路由表,并正常渲染
  • Web标准制定过程
  • 笨办法学C 练习34:动态数组
  • 海量大数据大屏分析展示一步到位:DataWorks数据服务+MaxCompute Lightning对接DataV最佳实践...
  • 前嗅ForeSpider教程:创建模板
  • 悄悄地说一个bug
  • 一天一个设计模式之JS实现——适配器模式
  • 一些css基础学习笔记
  • 分布式关系型数据库服务 DRDS 支持显示的 Prepare 及逻辑库锁功能等多项能力 ...
  • ​二进制运算符:(与运算)、|(或运算)、~(取反运算)、^(异或运算)、位移运算符​
  • # Panda3d 碰撞检测系统介绍
  • # Swust 12th acm 邀请赛# [ E ] 01 String [题解]
  • #QT(TCP网络编程-服务端)
  • $redis-setphp_redis Set命令,php操作Redis Set函数介绍
  • (2)(2.10) LTM telemetry
  • (TOJ2804)Even? Odd?
  • (二)pulsar安装在独立的docker中,python测试
  • (二)什么是Vite——Vite 和 Webpack 区别(冷启动)
  • (附源码)ssm航空客运订票系统 毕业设计 141612
  • (更新)A股上市公司华证ESG评级得分稳健性校验ESG得分年均值中位数(2009-2023年.12)
  • (原創) 如何解决make kernel时『clock skew detected』的warning? (OS) (Linux)
  • (转载)跟我一起学习VIM - The Life Changing Editor
  • .apk文件,IIS不支持下载解决
  • .gitignore文件---让git自动忽略指定文件
  • .libPaths()设置包加载目录
  • .NET CORE 3.1 集成JWT鉴权和授权2
  • .net 怎么循环得到数组里的值_关于js数组
  • /3GB和/USERVA开关
  • @column注解_MyBatis注解开发 -MyBatis(15)
  • @Conditional注解详解
  • @Service注解让spring找到你的Service bean
  • @在php中起什么作用?
  • [100天算法】-二叉树剪枝(day 48)