Ceph BlueStore Write Analyse

概述

Ceph从Luminous开始,默认使用了全新的存储引擎BlueStore,来替代之前提供存储服务的FileStore,与FileStore不一样的是,BlueStore直接管理裸盘,分配数据块来存储RADOS里的objects。

Bluestore比较复杂,学习BlueStore的关键就是查看其实现的 ”object → block device“ 映射,所以本文先重点分析下该部分:一个Object写到Bluestore的处理过程。

Bluestore整体架构

下面借用Sage Weil的图描述下BlueStore的整体架构:

ceph-bluestore-arch

里面的几个关键组件介绍:

  1. RocksDB:存储元数据信息
  2. BlueRocksEnv:提供RocksDB的访问接口
  3. BlueFS:实现BlueRocksEnv里的访问接口
  4. Allocator:磁盘分配器

针对BlueStore的整体架构不做展开,大家只需对其几大组件有些了解即可,本文继续介绍Object写操作到底层BlockDevice的过程。

BlueStore中Object到底层Device的映射关系

这里先从整体上给出在BlueStore中,一个Object的数据映射到底层BlockDevice的实现,如下图:

bluestore-object-blob-disk-arch

首先详细介绍下与上诉映射关系相关的数据结构。

Bluestore相关数据结构

BlueStore里与Object 数据映射相关的数据结构罗列如下:

Onode

任何RADOS里的一个Object都对应Bluestore里的一个Onode(内存结构),定义如下:

1
2
3
4
5
6
struct Onode {
Collection *c; // 对应的Collection,对应PG
ghobject_t oid; // Object信息
bluestore_onode_t onode; // Object存到kv DB的元数据信息
ExtentMap extent_map; // 映射lextents到blobs
};

通过Onode里的ExtentMap来查询Object数据到底层的映射。

ExtentMap

ExtentMapExtent的set集合,是有序的,定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
struct ExtentMap {
Onode *onode; // 指向Onode指针
extent_map_t extent_map; // Extents到Blobs的map
blob_map_t spanning_blob_map; // 跨越shards的blobs

struct Shard {
bluestore_onode_t::shard_info *shard_info = nullptr;
unsigned extents = 0; ///< count extents in this shard
bool loaded = false; ///< true if shard is loaded
bool dirty = false; ///< true if shard is dirty and needs reencoding
};
mempool::bluestore_cache_other::vector<Shard> shards; ///< shards
};

ExtentMap还提供了分片功能,防止在文件碎片化严重,ExtentMap很大时,影响写RocksDB的性能。

ExtentMap会随着写入数据的变化而变化;

ExtentMap的连续小段会合并为大;

覆盖写也会导致ExtentMap分配新的Blob;

Extent

Extent是实现object的数据映射的关键数据结构,定义如下:

1
2
3
4
5
6
struct Extent : public ExtentBase {
uint32_t logical_offset = 0; // 对应Object的逻辑偏移
uint32_t blob_offset = 0; // 对应Blob上的偏移
uint32_t length = 0; // 数据段长度
BlobRef blob; // 指向对应Blob的指针
};

每个Extent都会映射到下一层的Blob上,Extent会依据 block_size 对齐,没写的地方填充全零。

Extent中的length值,最小:block_size,最大:max_blob_size

Blob

Blob是Bluestore里引入的处理块设备数据映射的中间层,定义如下:

1
2
3
4
5
6
7
8
9
10
struct Blob {
int16_t id = -1;
SharedBlobRef shared_blob; // 共享的blob状态
mutable bluestore_blob_t blob; // blob的元数据
};
struct bluestore_blob_t {
PExtentVector extents; // 对应磁盘上的一组数据段
uint32_t logical_length = 0; // blob的原始数据长度
uint32_t compressed_length = 0; // 压缩的数据长度
};

每个Blob会对应一组 PExtentVector,它就是 bluestore_pextent_t 的一个数组,指向从Disk中分配的物理空间。

Blob里可能对应一个磁盘pextent,也可能对应多个pextent;

Blob里的pextent个数最多为:max_blob_size / min_alloc_size;

Blob里的多个pextent映射的Blob offset可能不连续,中间有空洞;

AllocExtent

AllocExtent是管理物理磁盘上的数据段的,定义如下:

1
2
3
4
5
6
7
8
9
struct bluestore_pextent_t : public AllocExtent {
...
};
class AllocExtent {
public:
uint64_t offset; // 磁盘上的物理偏移
uint32_t length; // 数据段的长度
...
};

AllocExtent的 length值,最小:min_alloc_size,最大:max_blob_size

BlueStore写数据流程

BlueStore里的写数据入口是BlueStore::_do_write(),它会根据 min_alloc_size 来切分 [offset, length] 的写,然后分别依据 small write 和 big write 来处理,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 按照min_alloc_size大小切分,把写数据映射到不同的块上
[offset, length]
|==p1==|=======p2=======|=p3=|
|----------------|----------------|----------------|
| min_alloc_size | min_alloc_size | min_alloc_size |
|----------------|----------------|----------------|
small write: p1, p3
big write: p2


BlueStore::_do_write()
|-- BlueStore::_do_write_data()
| // 依据`min_alloc_size`把写切分为`small/big`写
| | -- BlueStore::_do_write_small()
| | | -- BlueStore::ExtentMap::seek_lextent()
| | | -- BlueStore::Blob::can_reuse_blob()
| | reuse blob? or new blob?
| | | -- insert to struct WriteContext {};
| | -- BlueStore::_do_write_big()
| | | -- BlueStore::ExtentMap::punch_hole()
| | | -- BlueStore::Blob::can_reuse_blob()
| | reuse blob? or new blob?
| | | -- insert to struct WriteContext {};
|-- BlueStore::_do_alloc_write()
| | -- StupidAllocator::allocate()
| | -- BlueStore::ExtentMap::set_lextent()
| | -- BlueStore::_buffer_cache_write()
|-- BlueStore::_wctx_finish()

BlueStore Log分析

可以通过开启Ceph bluestore debug来抓取其写过程中对数据的映射,具体步骤如下。

下面通过在CephFS上测试为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1. 创建一个文件
# touch tstfile


2. 查看该文件的inode numer
# ls -i
2199023255554 tstfile


3. 获取该文件的映射信息
上诉inode number转换为16进制:20000000002
查看文件的第一个默认4M Object的映射信息
# ceph osd map cephfs_data_ssd 20000000002.00000000
osdmap e2649 pool 'cephfs_data_ssd' (3) object '20000000002.00000000' -> pg 3.3ff3fe94 (3.94) -> up ([12,0], p12) acting ([12,0], p12)


4. 在osd 12上开启bluestroe debug信息
# ceph daemon /var/run/ceph/ceph-osd.12.asok config set debug_bluestore "30" // 开启debug
# ceph daemon /var/run/ceph/ceph-osd.12.asok config set debug_bluestore "1/5" // 恢复默认


5. 对测试文件的前4M内进行dd操作,收集log
# dd if=/dev/zero of=tstfile bs=4k count=1 oflag=direct
# grep -v "trim shard target" /var/log/ceph/ceph-osd.12.log | grep -v "collection_list" > bluestore-write-0-4k.log

通过上述方式可以搜集到Bluestore在写入数据时,object的数据分配和映射过程,可以帮助理解其实现。

BlueStore dd write各种case

为了更好的理解BlueStore里一个write的过程,我们通过dd命令写一个Object,然后抓取log后分析不同情况下的Object数据块映射情况,最后结果如下图所示:

bluestore-dd-tst-extentmap

注释:上图的数据块映射关系是通过抓取log后获取的。

最后一图中,写[100k, 200)的区域,查看Object对应的ExtentMap并不是与 min_alloc_size(16k)对齐的,只是保证是block_size(4k)对齐而已。

支持原创