Ceph学习记录4-本地对象存储

参考:ceph源码分析(常涛)


1. 对象的元数据就是用于对象描述信息的数据,以简单的key-value形式存在,在RADOS存储系统中有两种实现:xattrs和omap.

xattrs:保存在对象对应文件的扩展属性中,这要求支持对象存储的本地文件系统支持扩展属性。

omap:就是object map的简称,是一些键值对,保存在本地文件系统之外的独立key-value存储系统中,例如leveldb,rocksdb等。

xattrs保存一些比较小而经常访问的信息,omap保存一些大而不是经常访问的数据。

2.为什么要引入事务和日志

假设磁盘正在执行一个操作,由于发生磁盘错误,或者系统宕机,或者断电等其他原因,导致只有部分数据写入成功。这种情况下就会出现磁盘上的数据有一部分是旧数据,部分是新写入的数据,使得磁盘数据不一致。

当一个操作要么全部成功,要么全部失败,不允许只有部分操作成功,就称这个操作具有原子性。引入事务和日志就是为了实现操作的原子性,解决数据不一致的问题。

3.引入日志后,数据怎样写入?

引入日志后,数据写入变为两步:先把要写入的数据全部封装成一个事务,其整体作为一条日志,先写入日志文件并持久化的磁盘,这个过程称为日志的提交(journal submit)。然后再把数据写入对象文件中,这称为日志的应用(journal apply)。

当系统在日志提交的过程中出错,系统重启后,直接丢弃不完整的日志条目即可,该条日志对应的实际对象数据并没有修改,数据可以保持一致。当在日志应用的过程中出错,由于数据已经写入并刷回到日志盘中,系统重启后,重放(replay)日志,就可以保证数据重新完整写入,保证了数据的一致性。

4.什么是幂等操作?

所谓幂等操作就是数据的更新可以多次写入,不会产生任何副作用。对象存储一般都具有幂等操作。

5.在事务的提交过程中,一条日志可以对应一个事务。为了提高日志提交的性能,一般都允许多条事务并发提交,这样一条日志记录可以对应多个事务,批量提交。所以事务的提交过程,一般和日志的提交过程是一个概念。

6.日志的三个处理阶段?

日志提交(journal submit): 日志写入日志磁盘。

日志的应用(journal apply):日志对应的修改更新到对象的磁盘文件中。这个修改不一定写入磁盘,可能缓存在本地文件系统的页缓存(page cache)中。

日志的同步(journal sync或者journal commit):当确定日志对应的修改操作已经刷回到磁盘中,就可以把日志记录删除,释放出所占的日志空间。

7.事务中对应的回调函数,分别在事务的不同阶段调用。当事务完成相应的阶段工作后,就调用相应的回调函数来通知事件完成。

on_commit是事务提交完成后调用

on_applied_sync:事务应用完成后被同步调用

on_applied:事务应用完成后,在Finisher线程里异步调用

8.ObjectStore是对象存储系统抽象操作接口,列举几个代表性的接口?

mkfs:创建objectstore相关的系统信息

mount:加载objectstore相关的系统信息

statfs:获取objectstore相关的系统信息

getattr:获取对象的扩展属性xattr

omap_get:获取对象的omap信息

queue_transactions:objectstore更新操作的接口

9.queue_transactions被重载成不同的接口,其参数为:

Sequencer *osr:用于保证在同一个Sequencer的操作是顺序的

list<Transaction *>& tls:要提交的事务,或者事务的列表

Context *onreadablehe:对应事务的on_apply,当事务的apply完成后,修改后的数据就可以被读取了

Context *onreadable_sync:对应事务的on_apply_sync,当事务的apply完成后,修改后的数据就可以被读取了

Context *ondisk:事务进行on_commit后的回调函数

10.日志的格式

entry_header_t + journal_data + entry_header_t

每条日志数据的头部和尾部都添加了entry_header_t结构。此外,日志在每次同步完成的时候设置must_write_header为true时会强制插入一个日志头header_t的结构,用于持久化存储header中变化了的字段。

11.write_thread_entry函数调用prepare_mutli_write函数,把多个write_item合并到一个bufferlist中打包写入,可以提高日志写入磁盘的性能。

g_conf->journal_max_write_entries:一次写入的最大日志条目数量

g_conf->journal_max_write_bytes为一次合并写入的最大字节数

12.prepare_single_write函数实现了把日志数据封装成日志格式的数据结构。

13.当日志被封装日志格式后,就需要把数据写入日志磁盘。写入方式分为两种:一种时同步写入,一种时linux提供的异步IO方式。linux内核实现的aio,必须以directIO的方式写入,由于aio是内核实现,数据不经过缓存直接落盘,性能要比同步方式高很多。

14.函数committed_thru用来实现日志的同步,它只是完成了日志的同步功能,同步必须保证日志应用的完成,这个逻辑在filestore里完成。

commited_thru具体实现:

确保当前同步的seq比上次last_commited_seq大。

用seq更新last_commited_seq的值。

把journal中小于等于seq的记录删除掉,这些记录已经没有用了。

删除相应的日志。这里只是逻辑上的删除,只是修改header.start指针就可以了。当journal为空,就删除到设置write_pos的位置。如果不空,就设置为队列的第一条记录的偏移值,这就是最新日志的起始地址,也就是上一条日志的结尾。

设置must_write_header为true,由于header更新了,日志必须把header写入磁盘。

日志可能因为日志空间满了而阻塞写线程,此时由于释放出更多的空间,就调用commit_cond.Signal()唤醒写线程。

15.FileStore中三种类型的日志?

journal writehead:这种数据先提交并写入日志磁盘上,然后再完成日志的应用(更新实际对象操作)。这种方式适合XFS,EXT4等不支持快照的本地文件系统使用这种方式。

journal parallel:日志提交到日志磁盘上和日志应用到实际对象中并进行,没有先后顺序。这种方式适用于BTRFS和ZFS等实现了快照操作的文件系统。由于具有文件系统级别快照功能,当日志的应用过程出错,导致数据出现不一致的情况下,文件系统只需要回滚到上一次快照,并replay从上次快照开始的日志就可以了。显然,这种方式比writehead方式性能更高。但是由于btrfs和zfs目前在Linux都不稳定,这种方式很少用。

不使用日志:数据直接写入磁盘后才返回客户端应答。这种方式目前FileStore实现了,但是性能太差,一般不使用。

16.类OpSequencer用于实现请求的顺序执行。在同一个Sequenecer类的请求,保证执行的顺序,包括日志commit的顺序和apply的顺序。一般情况下,一个PG对应一个Sequencer类,所以一个PG里的操作都是顺序执行。

17.类ObjectMap定义omap的抽象接口。类DBObjectMap实现了以KeyValueDB的本地存储实现的ObjectMap接口。目前,实现KeyValueDB的本地存储分别为:Facebook开源的levelDB存储,对应类为LevelDBStore;Google开源的RocksDB存储,对应类RocksDBStore实现,KineticStore存储对应的类KnieticStore.默认采用LevelDB实现。

18.LevelDB中,保存键值对:

key: HOBJECT_TO_SEQ + ghobject_key(oid)

value: header

HOBJECT_TO_SEQ是固定的前缀标识字符串,函数ghobject_key获取对应的对象唯一的key字符串。

header保存对象在LevelDB中唯一标识seq,以及支持快照的父对象的信息,同时保存了对象的collection和oid(这里冗余保存,因为从key信息里就可以获取)。

对象的属性保存格式:

Key: USER_PREFIX + header_key(header->seq) + XATTR_PREFIX + key

Value: value(omap的值)

所以设置和获取对象的属性,需要两步:现根据对象的oid,构造键(HOBJECT_TO_SEQ + ghobject_key(oid)),获取hedaer;根据对象的header中的seq,拼接在levelDB中的key值(USER_PREFIX + header_key(header->seq) + XATTR_PREFIX + key ),获取value值。

19.omap的克隆

当克隆一个新的对象old_new时,仅创建一个对应的new_header,并不是把该对象的所有属性都在levelDB中拷贝一遍,同时在new_header的parent字段保存header的seq号,从而建立他们之间的父子联系。当读取一个子对象的属性时,如果子对象不存在该属性,需要去父对象获取。

换句话说:omap的clone机制也实现了copy_on_write机制。

20. CollectionIndex

其概念对应到本地文件系统中就是一个目录,用于存储一个PG里所有的对象。一个PG对应于一个Collection,该PG的所有对象都保存在这个目录里,定义在类coll_t中。

collection有三种不同的类型:TYPE_META类型表示这个PG里保存的是元数据(meta)相关的对象,TYPE_PG表示该collection保存的是PG相关的数据,TYPE_PG_TEMP保存临时对象。

当一个PG的对象数量比较多时,就会在一个目录里保存大量的文件。对于底层文件系统来说,如果一个目录里保存大量文件,当达到一定程度后,性能就会急剧下降。那么就需要一个collection里对应多个层级的子目录来存储大量文件,从而提升性能。

当保存文件或者子目录达到一定数量,就需要分裂成两个目录。

CollectionIndex提供了对象到其对应文件保存的目录路径的映射管理。

猜你喜欢

转载自blog.csdn.net/qq_20283969/article/details/82054560