bluestore调研
前言
这是之前调研bluestore时写的随笔,放在夏天的风的博客上,现在搬到CSDN上来和大家分享,转载请注明出处,谢谢!
现在ceph已经出到K版本了,虽然未在生产中部署,但已可用。
背景
Ceph是一种软件定义存储解决方案,因此Ceph的主要目标是保障存储数据的安全。为了达到数据安全的目的,我们需要原子特性。目前没有一种文件存储系统能够提供写和更新操作的原子特性(Btrfs拥有原子特性的事务, 这是人们为了解决上述问题作出的尝试,但事实上并不成功)。
Ceph开发者不得不探索其他的解决方案。这个解决方案就是大家非常熟悉的Ceph日志。ceph后端的存储引擎目前使用的是filestore,写采用的是WAL的方式,但是写前记录日志这种技术有一个主要缺陷就是它把你的硬盘性能降低到原来的二分之一(仅当日志和OSD数据共享同一个硬盘时),因为filestore在写数据前需要先写journal,所以有一倍的写放大。
同时filestore一开始只是对于机械盘进行设计的,没有专门针对ssd做优化考虑。bluestore的初衷就是为了减少写放大,并针对ssd做优化,而且直接管理裸盘,从理论上进一步减少文件系统如ext4/xfs等部分的开销。
整体架构
模块名称 | 模块功能 |
---|---|
RocksDB | 存储预写式日志、数据对象元数据、Ceph的omap数据信息、以及分配器的元数据(分配器负责决定真正的数据应在什么地方存储) |
BlueRocksEnv | 与RocksDB交互的接口 |
BlueFS | 小的文件系统,解决元数据、文件空间及磁盘空间的分配和管理,并实现了rocksdb::Env 接口(存储RocksDB日志和sst文件)。因为rocksdb常规来说是运行在文件系统的顶层,下面是BlueFS。 它是数据存储后端层,RocksDB的数据和BlueStore中的真正数据被存储在同一个块物理设备 |
HDD | 物理块设备,存储实际的数据 |
rocksdb本身是基于文件系统的,不是直接操作裸设备。它将系统相关的处理抽象成Env,用户可用实现相应的接口(rocksdb默认的Env是PosixEnv,直接对接本地文件系统)。
BlueRocksEnv是bluestore实现的一个类,继承自rocksdb::EnvWrapper,来为rocksdb提供底层系统的封装。
为了对接BlueRocksEnv,实现了一个小的文件系统BlueFS,只实现rocksdb Env需要的接口。所有的元数据的修改都记录在BlueFS的日志中,也就是对于BlueFS,元数据的持久化保存在日志中。在系统启动mount这个文件系统时,只需replay日志,就可将所有的元数据都加载到内存中。BluesFS的数据和日志文件都通过块设备保存到裸设备上(BlueFS和BlueStore可以共享裸设备,也可以分别指定不同的设备)。
bluestore不使用本地文件系统,直接接管裸设备,并且只使用一个原始分区,HDD所在的物理块设备实现在用户态下使用linux aio直接对裸设备进行I/O操作。
读:目前块设备的读操作是同步的;
写:由于操作系统支持的aio操作只支持directIO,所以对BlockDevice的写操作直接写入磁盘,并且需要按照page对齐。其内部有一个aio_thread 线程,用来检查aio是否完成。其完成后,通过回调函数aio_callback 通知调用方。
Ceph数据对象(真正的‘数据’)直接被写入块物理设备。因为是直接管理裸设备,所以需要分配器(Allocator)来进行裸设备的空间管理。OSD附带的数据对象元数据被存储到键值数据库RocksDB中。
存储模型
这里把一个硬盘分了两个分区:
- 第一个迷你小分区使用了XFS或ext4文件系统。它存储了Ceph文件(像初始系统描述符,状态,id,fsid,钥匙串等),和RocksDB文件(RocksDB元数据和预写式日志)。
- 第二个分区是没有文件系统的原始分区。
每一个组件都可以存储在一个不同的物理设备上。在这张图中,RocksDB的预写式日志和数据可以被存储在不同的物理设备也可以存储在迷你小分区上。
元数据
数据结构onode 保存了BlueStore中一个对象的数据结构,这是一个常驻内存的数据结构,持久化的时候会以kv的形式存到rocksdb里。一个onode里会存在多个lextent(逻辑的数据块),用一个map来记录。lextent通过blob的id对应到blob(bluestore_blob_t ),blob里通过pextent对应到实际物理盘上的区域(pextent里就是offset和length来定位物理盘的位置区域)。blob和pextent是多对多的关系。
数据结构Enode定义了一个共享的extent,也就是这段数据被多个对象共享,一个对象的onode里保存一个enode数据结构,记录该对象被共享的extent。
I/O处理
到达bluestore的I/O的offset和length都是对象内(onode)的,offset是相对于这个对象起始位置的偏移,在执行写操作时,首先就会根据最小分配单位min_alloc_size进行判断,并按照min_alloc_size进行拆分,从而将I/O分为对齐和非对齐的。落到某一个min_alloc_size区间的写I/O执行非对齐写,min_alloc_size整数倍的写I/O执行对齐写。
整块写(对齐写)
也就是对齐到min_alloc_size的写请求,在处理时会根据实际大小新生成lextent和blob,这个lextent跨越的区域是min_alloc_size的整数倍。如果这段区间是之前写过的,会将之前的lextent记录下来便于后续的空间回收。(PS:回收这部分还没看)
部分写(非对齐写)
在处理落到某个min_alloc_size区间的写请求时,会首先根据offset去查找有没有可以复用的blob,因为最小分配单元是min_alloc_size,默认64KB,如果一个4KB的写I/O就只会用到blob的一部分,blob里剩余的还能放其他的。
1、没有找到可以复用的blob,新生成blob
在处理还还需要根据offset和len是否对齐到block_size(默认是4KB)进行补零对齐的操,然后再把对齐后的offset和len作为lextent,进而放到blob里。之所以需要进行补零操作是因为采用Direct I/O的方式写盘要求偏移和缓冲区都对齐的。
2、找到可以复用的blob
先按照block_size进行对齐补零的动作,然后再判断是否可以直接使用blob里空闲的空间进行区分做不同的处理。
a) 直接写在blob未使用的空间上
这种情况下直接新生成lextent放到blob里。
b) 覆盖写的情况
通过WAL写到rocksdb。
小结
从BlueStore 的设计和实现上看,可以将其理解为用户态下的一个文件系统,同时使用RocksDB来实现BlueStore所有元数据的管理,简化实现。
优点
对于整块数据的写入,数据直接以aio的方式写入磁盘,再更新RocksDB中数据对象的元数据,避免了filestore的先写日志,后apply到实际磁盘的两次写盘。同时避免了日志元数据的冗余存储占用,因为传统文件系统有他们自己内部的日志和元数据管理机制。
对于随机IO,直接WAL的形式,写入RocksDB 高性能的KV存储中。