本文主要分三个方面介绍BlueStore。 要了解BlueStore,需要先大概了解Ceph 的IO的多层结构。 也就是说,一个写入的IO,到达OSD后会经过哪些层进行处理。 第二点介绍本文主题:承载单机存储数据管理的存储引擎BlueStore。 这里也是会从写IO为例,介绍IO到达BlueStore后经过了BlueStore的哪些处理流程。 最后介绍BLueStore是如何进行IO对齐,并与pextent进行map。
Ceph IO的多层结构:
首先看下Ceph 一个写入IO到达OSD后,它会经过哪些层进行处理。
当一个IO到达OSD1时,首先会从网络层到达IO scheduler层。 本层是一个生产者消费者模型,消费者是多个 scheduler process线程。IO在本层中,由PG锁来保障归属于PG的IO顺序执行。也就是说,有几个 scheduler process线程,这里就会有几个PG进行并发执行。
IO经过Scheduler 层的分发后,到达PG 事务处理层。在本层中,首先会进行PG数据一致性的保障工作:也就是判定写入OP map的version,与本地map的version是否符合一致性要求, 当OSD确保自己可以保障本次写入OP的一致性要求时,会进一步确认PG是否是active的,也就是PG的数据已经完成了副本一致性的对齐准备工作。当一切准备就绪后,PG层会生成PG事务, 向PG的副本 OSD.2 发送这次事务, 并同时向本地存储引擎提交数据。
OSD单机存储引擎:BLueStore:
当数据到达BlueStore以后, 也会经过多层。
首先,进入BlueStore以后, BlueStore 会进行IO 切割,以及写入事务的生成。
在本层中, BlueStore首先会从RocksDB中拿本次操作对象的Onode,为了简化,这里没有画缓存,实际上元数据及数据都是有Cache的。
拿到Onode后,blueStore会按照最小分配单元min_alloc_size切分写入的逻辑段,未与min_alloc_size对齐的部分,走Bluestore的非对齐写逻辑,也就是网上讲的比较多的do_write_small流程。而与min_alloc_size对齐的逻辑段,就走对齐写的逻辑,也就是do_write_big流程,这两个流程最终会给对应的写入数据分配物理段,pextent,
相应的空间准备完成后,BlueStrore会生成操作的事务,也就是写入数据的准备、元数据的准备。
BlueStore进行事务处理
当写入事务准备就绪后,BlueStore就开始事务的处理了。在事务处理的第一阶段,BlueStore会将新写、或者是对齐写的数据直接写盘, 这是因为新写是新分配的物理段,而对齐的覆盖写,BlueStore采取COW,重定向写的方式,也是新分配的空间,不怕把原来的数据写花,所以在这儿,这两种数据是直接落盘的。
接着会发起sync,保障数据落盘,当数据落盘后, 将元数据事务conmit及sync到ROcksDB, 在这里新写及对齐的覆盖写流程基本就完成了。
而非对齐的覆盖写,由于是RMW,读取补全写的原空间的,为了防止数据写花,非对齐的覆盖写,到这儿才真正开始将元数据事务以及数据同时conmit及sync到RocksDB。给上层返回IO完成的消息后, 这儿会继续非对齐的覆盖写的这部分数据真正落到数据区。
BlueStore IO时序
至此,BlueStore大概的多层结构已讲完,接着看一下BlueStore 的写入IO时序图。
BlueStore的IO时序图如上图所示,本地PG被shard到多个SharededWq,多个SharededWq的线程并行地进行消息的处理(每个SharededWq也可以配置为多线程)。 这里也是之前说的IO sheduler层。
用户IO从SharededWq出队后。由tp_osd_tp线程开始处理。tp_osd_tp线程首先会进行一系列的可写入判定,例如之前说的,涉及PG一致性的map version,写入涉及的PG状态,传入的参数等。接着会将写入的数据封装为对象存储事务,调用BlueStore::queue_transactions函数,将事务向BlueStore提交。
在BlueStore::queue_transactions函数中,主要是将写入的数据进行对齐处理,元数据管理,空间的分配等。也就是之前说的IO切割、事务生成的阶段
在新写、覆盖对齐写 的IO流程中,tp_osd_tp线程生成对象一些元数据的事务(rocksdb:wirtebatch),将数据aio提交到盘,数据写盘成功后,触发bstore_kv_sync线程进行sync, 然后元数据submit, commit到rocksdb。bstore_kv_sync线程完成后会触发bstore_kv_finilize线程执行,此线程会最终触发tp_osd_tp线程判定所有副本的执行情况,当都落盘完成后,tp_osd_tp线程会向写入端返回msg告知。
而在延迟写的IO流程中,IO流程比非延迟写的长。tp_osd_tp在开始并不会将数据aio提交到盘,而是将数据同元数据一起封装为rocksdb的事务(目的是将小写写rocksdb的WAL),接着触发bstore_kv_sync线程进行数据、元数据submit \commit到 rocksdb。bstore_kv_sync线程完成后会触发bstore_kv_finilize线程执行。 此线程触发tp_osd_tp线程判定所有副本的执行情况,当都落盘完成后,tp_osd_tp线程会向写入端返回msg告知
而与新写及对齐覆盖写不同的是,这里,bstore_kv_finilize线程的执行流程会比非延迟写的长,它除了会触发tp_osd_tp线程向client返回消息,还会触发写了WAL的数据真正落盘到数据区(aio_submit)。
BlueStore IO对齐
接着,简单介绍一下BlueStore的IO对齐方式,
首先,说下新写, 例如,onode1对象。
当对onode1 进行大写 offset=32K, end=40K,也就是与 min_alloc_size = 8K对齐写的时候, BlueStore会走do_write_big流程.. bluestore在这种情况下, 会为这个extent 分配8K的物理段。紧接着就是之前说的落盘流程。
紧接着对onode1写入offset=11K,,length=1K的数据, 由于写入未与min_alloc_size 8K对齐,这个时候走do_write_snamll流程。 Bluestore在这种情况下,会为这个新写的extent的前3K补0,即补齐到磁盘的chunk size, 并按照min_alloc_size 分配 8 K的空间, 写入数据落到前4K的物理段, 而后4K暂时会标识为未使用。
接着对onode1 写入offset=15K,length=1k的数据,由于写入还是未与min_alloc_size 8K对齐,这个时候也走do_write_snamll流程。Bluestore在这种情况下,会为这个新写的extent的前3K补0,即补齐到磁盘的chunk size,然后根据写入的offset及已这个onode已经写入的extent发现,extent2中有后面4K空间之前分配了但是未使用,这个时候,直接复用extent1之前的blob1。
再次对onode1 写入chunk,16K—24K,此时写入是与min_alloc_size对齐的,走do_wirte_big大写流程,Bluestore在这种情况下,会为这个extent 分配8K的物理段,且复用blob1.
下面说下覆盖写的情况,还是onode1.
对onode1 的16K-24K部分发起对齐覆盖写。,此时写入是与min_alloc_size对齐的,走do_wirte_big大写流程,Bluestore在这种情况下,会为这个已经被写过的逻辑段重新分配8K的物理段,也就是COW, 并生成一个Old extent,指向原来需要释放的空间。当覆盖写完成后,BlueStore会释放这部分空间。
最后对onode1 的12K-16K部分发起非对齐覆盖写。此时写入是与min_alloc_size非对齐的,走do_wirte_small小写流程,与之前的写入不同的是,此时,BlueStore并不会为这个extent重新分配物理段空间, 而是读取相应的数据补齐到disk chunk size。 由于是覆盖原来数据的空间,因此,pextent写入采取延迟写方式,即写落到RocksDB的WAL,最后再更新到数据区。