Ceph数据流分析
OSD类
OSD和OSDService是核心类,他们直接在顶层负责一个OSD节点的工作,从客户端的得到的消息,就是先到达OSD类中,通过OSD类的处理,在调用PrimaryLogPG类进行处理。该类中,在读写流程中的主要工作是消息(Message)封装为 RequestOp,检查epoch (版本)是否需要更新,并获取PG句柄,并做PG相关的检查,最后将请求加入队列。
PrimaryLogPG类
该类继承自PG类,PGBackend::Listener(该类是一个抽象类)类PG类处理相关状态的维护,以及实现PG层面的功能,核心功能是用boost库的statechart状态机来做PG状态转换。它实现了PG内的数据读写等功能。
PGBackend类
该类主要功能是将请求数据通过事务的形式同步到一个PG的其它从OSD上(注意:主OSD的操作PrimaryLogPG来完成)。他有两个子类,分别是 ReplicatedBackend(副本)和ECBackend(EC),对应着PG的的两种类型的实现。
OSD读写函数调用流程
OSD::ms_fast_dispatch
----> OSD::dispatch_session_waiting
----> OSD::enqueue_op
OSD::dequeue_op
----> PrimaryLogPG::do_request
----> PrimaryLogPG::do_op
----> PrimaryLogPG::find_object_context
---->PrimaryLogPG::get_object_context
----> PrimaryLogPG::execute_ctx
----> PrimaryLogPG::prepare_transaction
----> PrimaryLogPG::OpContext::start_async_reads
----> PrimaryLogPG::complete_read_ctx
----> PrimaryLogPG::issue_repop
----> ReplicatedBackend::submit_transaction
----> ReplicatedBackend::issue_op
----> OSDService::send_message_osd_cluster
----> ReplicatedBackend::queue_transactions
----> BlueStore::queue_transactions
个人理解:该图中ReplicatePG已被替换为PrimaryLogPG,FileStore已被Bluestore替换
场景:client需要向cluster集群中写入数据
1:client使用crush算法确认需要交流的主OSD
2:client和主OSD建立联系,发送数据
3:主OSD这边server监听,收到请求后通过ms_fast_dispatch进行处理
4:主OSD会用PrimaryLogPG进行处理,假设此时使用的是副本形式
5:主OSD会用ReplicatedBackend与从OSDs进行连接,将写请求发送到从OSDs(issue_op)
6:主OSD使用bluestore进行数据存储
Allocator类
实现对裸设备的管理,直接将数据保存到设备上。用来委派具体哪个实际存储块用来存储当前的object数据;目前采用bitmap的方式来实现allocator,同时采用层级索引来存储多种状态,这种方式对内存的消耗相对较小,平均1TB磁盘需要大概35M左右的ram空间。
新版本BitMap分配器以Tree-Like的方式组织数据结构,整体分为L0、L1、L2三层。每一层都包含了完整的磁盘空间映射,只不过是slot以及children的粒度不同,这样可以加快查找。
新版本分配器分配空间的大体策略如下:
循环从L2中找到可以分配空间的slot以及children位置。
在L2的slot以及children位置的基础上循环找到L1中可以分配空间的slot以及children位置。
在L1的slot以及children位置的基础上循环找到L0中可以分配空间的slot以及children位置。
在1-3步骤中保存分配空间的结果以及设置每层对应位置分配的标志位。
新版本分配器整体架构设计有以下几点优势:
Allocator避免在内存中使用指针和树形结构,使用vector连续的内存空间。
Allocator充分利用64位机器CPU缓存的特性,最大程序的提高性能。
Allocator操作的单元是64 bit,而不是在单个bit上操作。
Allocator使用3级树状结构,可以更快的查找空闲空间。
Allocator在初始化时L0、L1、L2三级BitMap就占用了固定的内存大小。
Allocator可以支持并发的分配空闲,锁定L2的children(bit)即可,暂未实现。
总体来说新版本BitMap分配器的优势在于使用连续的内存空间从而尽可能更多的命中CPU Cache以提高分配器性能。
分配流程图
最终分配的结果存储在interval_vector_t结构体里面,实际上就是extent_vector,因为分配的磁盘空间不一定是完全连续的,所以会有多个extent,而在往extent_vector 插入extent的时候会合并相邻的extent为一个extent。如果max_alloc_size设置了,且单个连续的分配大小超过了max_alloc_size,那么extent的length最大为max_alloc_size ,同时这次分配结果也会拆分会多个extent。
BlueRocksEnv类
这是RocksDB与BlueFS交互的接口;RocksDB提供了文件操作的接口EnvWrapper,用户可以通过继承实现该接口来自定义底层的读写操作,BlueRocksEnv就是继承自EnvWrapper实现对BlueFS的读写。
class BlueRocksEnv : public rocksdb::EnvWrapper
可以看出来BlueRocksEnv本质上是对rocksdb进行操作
RocksDB类
rocksdb是facebook基于leveldb开发的一款kv数据库,BlueStore将元数据全部存放至RocksDB中,这些元数据包括存储预写式日志、数据对象元数据、Ceph的omap数据信息、以及分配器的元数据 。
class BlueRocksEnv : public rocksdb::EnvWrapper
BlueFS
BlueFS是BlueStore针对RocksDB开发的轻量级文件系统,用于存放RocksDB产生的.sst和.log等文件。
BlueStore读写函数调用流程
从上面的分析,我们已经了解了osd是怎么调用到bluestore接口的,下面我们将从bluestore继续向下研究。
BlueStore::queue_transactions
----> BlueStore::_txc_add_transaction
----> BlueStore::_write
----> BlueStore::_do_write
----> BlueStore::_do_write_data
----> BlueStore::_do_write_small
BlueStore::_do_write_big
----> BlueStore::_do_alloc_write
----> bluestore_blob_t::allocated
----> BlockDevice::aio_write
BlueStore相关解释
Collection:PG在内存的数据结构。
bluestore_cnode_t:PG在磁盘的数据结构。
Onode:对象在内存的数据结构。
bluestore_onode_t:对象在磁盘的数据结构。
Extent:一段对象逻辑空间(lextent)。
extent_map_t:一个对象包含多段逻辑空间。
bluestore_pextent_t:一段连续磁盘物理空间。
bluestore_blob_t:一片不一定连续的磁盘物理空间,包含多段pextent。
Blob:包含一个bluestore_blob_t、引用计数、共享blob等信息。
https://blog.51cto.com/wendashuai/2500499
onode
struct Onode { // 对象在内存中的数据结构
bluestore_onode_t onode; /// 对象在磁盘中的数据结构
ExtentMap extent_map;
......
}
// 磁盘数据结构 onode: per-object metadata
struct bluestore_onode_t {
// 逻辑ID,单个BlueStore内部唯一。
uint64_t nid = 0;
// 对象大小
uint64_t size = 0;
// 对象扩展属性
map<mempool::bluestore_cache_other::string, bufferptr> attrs;
......
}
ExtentMap
/// a sharded extent map, mapping offsets to lextents to blobs
struct ExtentMap {
Onode *onode;
extent_map_t extent_map; ///< map of Extents to Blobs 一个对象包含多段逻辑空间
blob_map_t spanning_blob_map; ///< blobs that span shards
......
}
typedef boost::intrusive::set<Extent> extent_map_t;
// LExtent
struct Extent : public ExtentBase { // 一段对象逻辑空间(lextent)
MEMPOOL_CLASS_HELPERS();
uint32_t logical_offset = 0; ///< logical offset
uint32_t blob_offset = 0; ///< blob offset
uint32_t length = 0; ///< length
BlobRef blob; ///< the blob with our data
......
}
Blob
typedef boost::intrusive_ptr<Blob> BlobRef;
/// in-memory blob metadata and associated cached buffers (if any)
struct Blob {
MEMPOOL_CLASS_HELPERS();
mutable bluestore_blob_t blob; ///< decoded blob metadata
......
}
/// blob: a piece of data on disk
struct bluestore_blob_t {
PExtentVector extents; ///< raw data position on device
uint32_t logical_length = 0; ///< original length of data stored in the blob
uint32_t compressed_length = 0; ///< compressed length if any
......
}
pextent
typedef mempool::bluestore_cache_other::vector<bluestore_pextent_t> PExtentVector;
/// pextent: physical extent
struct bluestore_pextent_t : public bluestore_interval_t<uint64_t, uint32_t>
{
bluestore_pextent_t() {}
bluestore_pextent_t(uint64_t o, uint64_t l) : bluestore_interval_t(o, l) {}
bluestore_pextent_t(const bluestore_interval_t &ext) :
bluestore_interval_t(ext.offset, ext.length) {}
DENC(bluestore_pextent_t, v, p) {
denc_lba(v.offset, p);
denc_varint_lowz(v.length, p);
}
void dump(ceph::Formatter *f) const;
static void generate_test_instances(std::list<bluestore_pextent_t*>& ls);
};
WRITE_CLASS_DENC(bluestore_pextent_t)