【osd】Ceph中的数据一致性
流程细化
-
写流程:以 FileStore 后端存储为例
- 1)client把写请求发到Primary OSD上,Primary OSD上将写请求序列化到一个事务中(在内存里),然后构造一条pglog记录,也序列化到这个事务中,然后将这个事务以directIO的方式异步写入journal,同时Primary OSD把写请求和pglog(pglog_entry是由primary生成)发送到Replicas上;
- 2)在Primary OSD将事务写到journal上后,会通过一系列的线程和回调处理,然后将这个事务里的数据写入filesystem(只是写到文件系统的缓存里,会有线程定期刷数据),这个事务里的pglog记录(也包括pginfo的last_complete和last_update)会写到leveldb,还有一些扩展属性相关的也在这个事务里,在遍历这个事务时也会写到leveldb;
- 3)在Replicas上,也是进行类似于Primary的动作,先写journal,写成功会给Primary发送一个committed ack,然后将这个事务里的数据写到filesystem,pglog与pginfo写到leveldb里,写完后会给Primary发送另外一个applied ack;
- 4)Primary在自己完成journal的写入时,以及在收到Replica的committed ack时都会检查是否多个副本都写入journal成功了,如果是则向client端发送ack通知写完成;Primary在自己完成事务写到文件系统和leveldb后,以及在收到replica的applied ack时都会检查是否多个副本都写文件系统成功,如果是则向client端发送ack通知数据可读;
-
读流程:由于实现了强一致性,主节点和从节点的数据基本完全一致,故在读取时采用了随机的方式进行OSD的选取,然后读取对应的数据。
-
PGLog封装到transaction里面和journal一起写到盘上的好处:如果osd异常崩溃时,journal写完成了,但是数据有可能没有写到磁盘上,相应的pg log也没有写到leveldb里,这样在osd再启动起来时,就会进行journal replay,这样从journal里就能读出完整的transaction,然后再进行事务的处理,也就是将数据写到盘上,pglog写到leveldb里。
- 1)正常情况下,都是由Primary处理client端的I/O请求,这时,Primary和Replicas上的
last_update
和last_complete
都会指向pglog最新记录; - 2)当Primary挂掉后,会选出一个Replica作为“临时主”,这个“临时主”负责处理新的读写请求,并且这个时候“临时主”和剩下的Replicas上的
last_complete
和last_update
都更新到该副本上的pglog的最新记录; - 3)当原来的Primary又重启时,会从本地读出pginfo和pglog,当发现
last_complete
<last_update
时,last_complete
和last_update
之间就可能存在丢失的对象,遍历last_complete
到last_update
之间的pglog记录,对于每一条记录,从本地读出该记录里对象的属性(包含本地持久化过的版本),对比pglog记录里的对象版本与读出来的版本,如果读出来的对象版本小于pglog记录里的版本,说明该对象不是最新的,需要进行恢复,因此将该对象加到missing列表里; - 4)Primary发起peering过程,即“抢回原来的主”,选出权威日志,一般就是“临时主”的pglog,将该权威日志获取过来,与自己的pglog进行
merge_log
的步骤,构建出missing列表,并且更新自己的last_update
为最新的pglog记录(与各个副本一致),这个时候last_complete
与last_update
之间的就会加到missing列表,并且peering完成后会持久化last_complete
和last_update
; - 5)当有新的写入时,仍然是由Primary负责处理,会更新
last_update
,副本上会同时更新last_complete
,与此同时,Primary会进行恢复,就是从其他副本上拉取对象数据到自己这里进行恢复,每当恢复完一个时,就会更新自己的last_complete
(会持久化的),当所有对象都恢复完成后,last_complete
就会追上last_update
了。 - 6)当恢复过程中,Primary又挂了再起来恢复时,先读出本地pglog时就会根据自己的
last_complete
和last_update
构建出missing列表,而在peering的时候对比权威日志和本地的pglog发现权威与自己的last_update
都一样,peering的过程中就没有新的对象加到missing列表里,总的来说,missing列表就是由两个地方进行构建的:一个是osd启动的时候read_log
里构建的,另一个是peering的时候对比权威日志构建的;
Replica 故障恢复
与Primary的恢复类似,peering都是由Primary发起的,Replica起来后也会根据pglog的 last_complete
和 last_update
构建出replica自己的missing,然后Primary进行peering的时候对比权威日志(即自身)与故障replica的日志,结合replica的missing,构建出 peer_missing
,然后就遍历 peer_missing
来恢复对象。然后新的写入时会在各个副本上更新 last_complete
和 last_update
,其中故障replica上只更新 last_update
,恢复过程中,每恢复完一个对象,故障replica会更新 last_complete
,这样所有对象都恢复完成后,replica的 last_complete
就会追上 last_update
。
如果恢复过程中,故障replica又挂掉,然后重启后进行恢复的时候,也是先读出本地log,对比 last_complete
与 last_update
之间的pglog记录里的对象版本与本地读出来的该对象版本,如果本地不是最新的,就会加到missing列表里,然后Primary发起peering的时候发现replica的 last_update
是最新的,peering过程就没有新的对象加到 peer_missing
列表里,peer_missing
里就是replica自己的missing里的对象。
参考资料