ceph的PGLog是由PG来维护,记录了该PG的全部操做,其做用相似于数据库里的undo log。PGLog一般只保存近千条的操做记录(默认是3000条),可是当PG处于降级状态时,就会保存更多的日志(默认是10000条),这样就能够在故障的PG重现上线后用来恢复PG的数据。本文主要从PGLog的格式、存储方式、如何参与恢复来解析PGLog。数据库
ceph使用版本控制的方式来标记一个PG内的每一次更新,每一个版本包括一个(epoch,version)来组成:其中epoch是osdmap的版本,每当有OSD状态变化如增长删除等时,epoch就递增;version是PG内每次更新操做的版本号,递增的,由PG内的Primary OSD进行分配的。缓存
PGLog在代码实现中有3个主要的数据结构来维护:pg_info_t,pg_log_t,pg_log_entry_t。三者的关系示意图以下。从结构上能够得知,PGLog里只有对象更新操做相关的内容,没有具体的数据以及偏移大小等,因此后续以PGLog来进行恢复时都是按照整个对象来进行恢复的(默认对象大小是4MB)。
其中:数据结构
了解了PGLog的格式以后,咱们就来分析一下PGLog的存储方式。在ceph的实现里,对于写I/O的处理,都是先封装成一个transaction,而后将这个transaction写到journal里,在journal写完成后,触发回调流程,通过多个线程及回调的处理后再进行写数据到buffer cache的操做,从而完成整个写journal和写本地缓存的流程(具体的流程在《OSD读写处理流程》一文中有详细描述)。app
整体来讲,PGLog也是封装到transaction中,在写journal的时候一块儿写到日志盘上,最后在写本地缓存的时候遍历transaction里的内容,将PGLog相关的东西写到Leveldb里,从而完成该OSD上PGLog的更新操做。异步
在《OSD读写流程》里描述了主OSD上的读写处理流程,这里就不作说明。在ReplicatedPG::do_osd_ops函数里根据类型CEPH_OSD_OP_WRITE就会进行封装写I/O到transaction的操做(即将要写的数据encode到ObjectStore::Transaction::tbl里,这是个bufferlist,encode时都先将op编码进去,这样后续在处理时就能够根据op来操做。注意这里的encode其实就是序列化操做)。函数
这个transaction通过的过程以下:ui
1 2 |
ReplicatedPG::OpContext::op_t –> PGBackend::PGTransaction::write(即t->write) –> RPGTransaction::write –> ObjectStore::Transaction::write(encode到ObjectStore::Transaction::tbl) 后面调用ReplicatedBackend::submit_transaction时传入的PGTransaction *_t就是上面这个,经过转换成RPGTransaction *t,而后这个函数里用到的ObjectStore::Transaction *op_t就是对应到RPGTransaction里的ObjectStore::Transaction *t。 |
从上面的分析得知,写I/O和PGLog都会序列化到transaction里的bufferlist里,这里就对这个bufferlist里的主要内容以图的形式展现出来。transaction的bufflist里就是按照操做类型op来序列化不一样的内容,如OP_WRITE表示写I/O,而OP_OMAPSETKEYS就表示设置对象的omap,其中的attrset就是一个kv的map。 注意这里面的oid,对于pglog来讲,每一个pg在建立的时候就会生成一个logoid,会加上pglog构造的一个对象,对于pginfo来讲,是pginfo_构造的一个对象,而对于真正的数据对象来讲,attrset就是其属性。
编码
前面说到PGLog的记录数是有限制的,正常状况是默认是3000条(由参数osd_min_pg_log_entries控制),PG降级状况下默认增长到10000条(由参数osd_max_pg_log_entries控制)。当达到限制时,就会trim log进行截断。spa
在ReplicatedPG::execute_ctx里调用ReplicatedPG::calc_trim_to来进行计算。计算的时候从log的tail(tail指向最老的记录)开始,须要trim的条数=log.head-log.tail-max_entries。可是trim的时候须要考虑到min_last_complete_ondisk(这个表示各个副本上last_complete的最小版本,是主osd在收到3副本都完成时再进行计算的,也就是计算last_complete_ondisk和其余副本osd上的last_complete_ondisk–即peer_last_complete_ondisk的最小值获得min_last_complete_ondisk),也就是说trim的时候不能超过min_last_complete_ondisk,由于超过了的也trim掉的话就会致使没有更新到磁盘上的pg log丢失。因此说可能存在某个时候pglog的记录数超过max_entries。
在ReplicatedPG::log_operation里的trim_to就是pg_trim_to,trim_rollback_to就是min_last_complete_on_disk。log_operation里调用pg_log.trim(&handler, trim_to, info)进行trim,会将须要trim的key加入到PGLog::trimmed这个set里。而后在_write_log里将trimmed里插入到to_remove里,最后在调用t.omap_rmkeys序列化到transaction的bufferlist里。线程
PGLog写到journal盘上就是写journal同样的流程,具体以下:
在《OSD读写流程》里描述到是在FileStore::_do_op里进行写数据到本地缓存的操做。将pglog写入到leveldb里的操做也是从这里出发的,会根据不一样的op类型来进行不一样的操做。
好比OP_OMAP_SETKEYS(PGLog写入leveldb就是根据这个key):
1 |
FileStore::_do_op --> FileStore::_do_transactions --> FileStore::_do_transaction 根据不一样的Transaction类型来进行不一样的操做 --> case Transaction::OP_OMAP_SETKEYS --> FileStore::_omap_setkeys --> object_map->rm_keys,即DBObjectMap::set_keys --> KeyValueDB::TransactionImpl::set,遍历map<string, bufferlist>,而后调用set(prefix, it->first, it->second),即调用到LevelDBStore::LevelDBTransactionImpl::set --> 最后调用db->submit_transaction提交事务写到盘上 |
再好比以OP_OMAP_RMKEYS(trim pglog的时候就是用到了这个key)为例:
1 |
FileStore::_do_op --> FileStore::_do_transactions --> FileStore::_do_transaction 根据不一样的Transaction类型来进行不一样的操做 --> case Transaction::OP_OMAP_RMKEYS --> FileStore::_omap_rmkeys --> object_map->rm_keys,后面就是调用到LevelDB里的rm_keys去删除keys。 |
PGLog封装到transaction里面和journal一块儿写到盘上的好处:若是osd异常崩溃时,journal写完成了,可是数据有可能没有写到磁盘上,相应的pg log也没有写到leveldb里,这样在osd再启动起来时,就会进行journal replay,这样从journal里就能读出完整的transaction,而后再进行事务的处理,也就是将数据写到盘上,pglog写到leveldb里。
PGLog参与恢复主要体如今ceph进行peering的时候创建missing列表来标记过期数据,以便于进行对这些数据进行修复。
故障的OSD从新上线后,PG就会标记为peering状态并暂停处理请求。