ceph存储 集群恢复流程详解

来源:互联网 发布:广州天拓网络待遇 编辑:程序博客网 时间:2024/06/06 12:32

1. 基本概念

基于版本10.2.1


pg状态机转换:

 


1.1 epoch

    epoch是一个单调递增序列,其序列由monitor负责维护,当集群中的配置及osd状态发生变更时,其数值向上加一。这一机制等同于时间轴,每次序列变化是时间轴上的点。

1.2 intervals

    由于epoch反映的是整个集群的状态,故从单个pg角度来看,某些epoch序列段变更与本pg是无关的。(例如存在的某一osd挂掉了,但这一osd并不负载本pg的情况),故在epoch的时间轴上,pg的变更实际上是分段的,每个分段称之为一个intervals。具体如下图示

 

1.3 pglog

PGLog在代码实现中有3个主要的数据结构来维护:pg_info_tpg_log_tpg_log_entry_t。三者的关系示意图如下。

 

 

 

上图中的eversion列来源于pg_log_entry_t.version字段,object列来源于pg_log_

entry_t.soid字段,op列即为pg_log_entry_t.op字段.

last_complete,last_update字段即来源于pg_info_t中的相应字段。

其中:

last_complete:在该指针之前的版本都已经在所有的OSD上完成更新(只表示内存更新完成);
last_update:PG内最近一次更新的对象的版本,还没有在所有OSD上完成更新,在last_update与last_complete之间的操作表示该操作已在部分OSD上完成但是还没有全部完成;
log_tail:指向pg log最老的那条记录;
head:最新的pg log记录;
tail:指向最老的pg log记录的前一个;
log:存放实际的pglog记录的list;

 

1.3.1 主要数据结构分析

l pg_log_t

pg里保存的是object,pg自已的一些状态信息,保存在pginfo中,数据结构

struct pg_info_t {

  spg_t pgid;//指出是哪个pg

 

  eversion_t last_update;   //pg最后一次更新时版本,此数据在写日志时更新

 

  eversion_t last_complete; //此序列之前的在本pg中已确保无丢失,普通情况下,如果last_complete与last_update一样时,均在写日志时更新。

 

  epoch_t last_epoch_started;  //每到达一次active,就将此值设置为到达active状态是osdmap表的版本号,此值表明本地的missing集,log均合并完成。

 

  version_t last_user_version; //用户自定义的版本,写日志时更新成user_version

 

  eversion_t log_tail;      //最旧的pglog版本。

  hobject_t last_backfill; //指出恢复位置,< objects >= this and < last_complete可以处于缺失。

  bool last_backfill_bitwise;  //一种对象间比对的方法,采用bitwise方式还是nibblewise方式进行比对。

 

  interval_set<snapid_t> purged_snaps;//于快照相关暂未分析。

 

  pg_stat_t stats;//状态信息,与恢复无关,暂未分析。

 

  pg_history_t history;//集群状态历史

  pg_hit_set_history_t hit_set;//暂未涉及到,未分析

};

 

pg_history_t结构体细节如下示:

struct pg_history_t {

  epoch_t epoch_created;    //pg被创建的时间点

   epoch_t last_epoch_started;   //集群最后一次进入start的时间,不一定和本地相等。

 

 

  epoch_t last_epoch_clean;  //集群中最后一次进入clean状态的时间

  epoch_t last_epoch_split; // 集群中最后一次分裂的时间

  epoch_t last_epoch_marked_full;  // 标记为满,暂未分析

 

  /**

   * In the event of a map discontinuity, same_*_since may reflect the first

   * map the osd has seen in the new map sequence rather than the actual start

   * of the interval.  This is ok since a discontinuity at epoch e means there

   * must have been a clean interval between e and now and that we cannot be

   * in the active set during the interval containing e.

   */

  epoch_t same_up_since;    //自此时间点开始至今up集无变化

  epoch_t same_interval_since;   //自此时间点开始up与acting集无变化

  epoch_t same_primary_since;  //自此时间点开始primary无变化.

 

 //清洗相关,暂未分析

  eversion_t last_scrub;

  eversion_t last_deep_scrub;

  utime_t last_scrub_stamp;

  utime_t last_deep_scrub_stamp;

  utime_t last_clean_scrub_stamp;

};

 

上面的两个数据结构,比较重要的pg_info_t.last_update,这个数据项在pglog被写入时由Primary节点产生,由Replicate节点负责写入。故它代表了整个集群中的最新状态。

 

pg_info_t.last_epoch_started,这个数据项在每次状态机达到active状态时,由osd自主写入,此值写入时,本地及对端的missing集均已计算完成,故它表示在本osd上,pg_info_t.last_epoch_started之前的对象可能确失,但log,missing集是完全的。

 

 

l pg_log_t

pglog在pg层面记录pg执行过的操作。pglog结构体定义如下

 

struct pg_log_t {

  eversion_t head; //最新的版本

  eversion_t tail; //最旧的版本

 

  eversion_t can_rollback_to; //可以rollback的最小版本,执行log_operation时更新,由于这个版本对应的数据已经落盘,故这个版本之后的数据是可以被rollback的。

 

  eversion_t rollback_info_trimmed_to;//可以移除的版本(可以理解为已固化,小于此的log可以trim掉)

 

  list<pg_log_entry_t> log;  //存储log的集合。

};

 

l pg_log_entry_t

pg_log_entry_t数据结构体定义如下

struct pg_log_entry_t {

  ObjectModDesc mod_desc; //暂未分析

  bufferlist snaps;   // 快照clone时需要,暂未分析

  hobject_t  soid;//pg对应的对象

  osd_reqid_t reqid;  // 唯一标记一个请求

  vector<pair<osd_reqid_t, version_t> > extra_reqids;//目的暂不明确,和客户端有关

  eversion_t version, prior_version, reverting_to;//本操作版本,本操作的前一版本,回退版本

  version_t user_version; //用户为此对象安排的版本

  utime_t mtime;  //用户定义的时间

  int32_t return_code; //针用于失败时的返回值,此值用于重复请求检测。

 

  __s32   op;//操作类型(见类枚举)

  bool invalid_hash; //兼容性产物,在编码时用,目前默认为false

  bool invalid_pool; //兼容性产物,在编码时用,目前默认为false

};

 

1.3.2 pglog的存储方式

总体来说,PGLog也是封装到transaction中,在写journal的时候一起写到日志盘上,最后在写本地缓存的时候遍历transaction里的内容,将PGLog相关的东西写到Leveldb里,从而完成该OSD上PGLog的更新操作。

1)  PGLog更新到journal

写I/O序列化到transaction

在ReplicatedPG::do_osd_ops函数里根据类型CEPH_OSD_OP_WRITE就会进行封装写I/O到transaction的操作(即将要写的数据encode到ObjectStore::Transaction::tbl里,这是个bufferlist,encode时都先将op编码进去,这样后续在处理时就可以根据op来操作。注意这里的encode其实就是序列化操作)。

这个transaction经过的过程如下:
ReplicatedPG::OpContext::op_t –> PGBackend::PGTransaction::write(即t->write)–>RPGTransaction::write –> ObjectStore::Transaction::write(encode到ObjectStore::Transacti

on::tbl)后面调用ReplicatedBackend::submit_transaction时传入的PGTransaction _t就是上面这个,通过转换成RPGTransaction t,然后这个函数里用到的ObjectStore::Transaction op_t就是对应到RPGTransaction里的ObjectStore::Transaction t。

 

PGLog序列化到transaction

在ReplicatedPG::prepare_transaction里调用ReplicatedPG::finish_ctx,然后在finish_ctx函数里就会调用ctx->log.push_back就会构造pg_log_entry_t插入到vector log里;
在ReplicatedBackend::submit_transaction里调用parent->log_operation将PGLog序列化到transaction里。在PG::append_log里将PGLog相关信息序列化到transaction里。
主要序列化到transaction里的内容包括:pg_info_t,pg_log_entry_t,这两种数据结构都是以map的形式encode到transaction的bufferlist里。其中不同的map的value对应的就是pg_info和pg_log_entry的bufferlist。而map的key就是epoch version构成的字符串”epoch.version”。另外需要注意的是这些map是附带上op和oid作为对象的omap(Object的属性会利用文件的xattr属性存取,因为有些文件系统对xattr的长度有限制,因此超出长度的Metadata会被存储在DBObjectMap里。而Object的omap则直接利用DBObjectMap实现。)来序列化到transaction里;

Transaction里的内容

从上面的分析得知,写I/O和PGLog都会序列化到transaction里的bufferlist里,这里就对这个bufferlist里的主要内容以图的形式展示出来。transaction的bufflist里就是按照操作类型op来序列化不同的内容,如OP_WRITE表示写I/O,而OP_OMAP_SETKEYS就表示设置对象的omap,其中的attrset就是一个kv的map。

 

Trim Log

前面说到PGLog的记录数是有限制的,正常情况是默认是3000条(由参数osd_min_pg_

log_entries控制),PG降级情况下默认增加到10000条(由参数osd_max_pg_log_entries控制)。当达到限制时,就会trim log进行截断。

在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盘
PGLog写到journal盘上就是写journal一样的流程,具体如下:在ReplicatedBackend

::submit_transaction调用log_operation将PGLog序列化到transaction里,然后调用queue_transaction将这个transaction传到后续进行处理;
调用到了FileStore::queue_transactions里,就将list构造成一个FileStore::Op,对应的list放到FileStore::Op::tls里;
接着在JournalingObjectStore::_op_journal_transactions函数里遍历list& tls,将ObjectStore::Transaction encode到一个bufferlist里(记为tbl);然后FileJournal::submit_entry里将bufferlist构造成write_item放到writeq;
接着在FileJournal::write_thread_entry会从writeq里取出write_item,放到另外一个bufferlist里;
最后调用do_aio_write将bufferlist的内容异步写到磁盘上(也就是写journal);

2) PGLog写入leveldb
在《OSD读写流程》里描述到是在FileStore::_do_op里进行写数据到本地缓存的操作。将pglog写入到leveldb里的操作也是从这里出发的,会根据不同的op类型来进行不同的操作。
比如OP_OMAP_SETKEYS(PGLog写入leveldb就是根据这个key):

FileStore::_do_op –> FileStore::_do_transactions –> FileStore::_do_transaction根据不同的Transaction类型来进行不同的操作–>case Transaction::OP_OMAP_SETKEYS –> FileS

tore::_omap_setkeys –> object_map->rm_keys,即DBObjectMap::set_keys –> KeyValueDB

::TransactionImpl::set,遍历map,然后调用set(prefix, it->first, it->second),即调用到LevelDBStore::LevelDBTransactionImpl::set –>最后调用db->submit_transaction提交事务写到盘上

再比如以OP_OMAP_RMKEYS(trim pglog的时候就是用到了这个key)为例:FileStore

::_do_op –> FileStore::_do_transactions –> FileStore::_do_transaction根据不同的Transaction类型来进行不同的操作–> case Transaction::OP_OMAP_RMKEYS –> FileStore::_omap_rmk

eys –>object_map->rm_keys,后面就是调用到LevelDB里的rm_keys去删除keys。

PGLog封装到transaction里面和journal一起写到盘上的好处:如果osd异常崩溃时,journal写完成了,但是数据有可能没有写到磁盘上,相应的pg log也没有写到leveldb里,这样在osd再启动起来时,就会进行journal replay,这样从journal里就能读出完整的transaction,然后再进行事务的处理,也就是将数据写到盘上,pglog写到leveldb里。

 

2.恢复流程

2.1 PGLOG如何参与恢复

PGLog参与恢复主要体现在ceph进行peering的时候建立missing列表来标记过时数据,以便于进行对这些数据进行修复。
故障的OSD重新上线后,PG就会标记为peering状态并暂停处理请求。

· 对于故障OSD所拥有的Primary PG 

o 它作为这部分数据”权责”主体,需要发送查询PG元数据请求给所有属于该PG的Replicate角色节点;

o 该 PG 的 Replicate角色节点实际上在故障OSD下线时期间成为了Primary角色并维护了“权威”的PGLog,该PG在得到故障OSD的Primary PG的查询请求后会发送回应;

o Primary PG 通过对比 Replicate PG 发送的元数据和PG版本信息后发现处于落后状态,因此它会合并得到的PGLog并建立“权威” PGLog,同时会建立missing列表来标记过时数据;

o Primary PG 在完成“权威” PGLog的建立后就可以标志自己处于Active状态;

· 对于故障OSD所拥有的Replicate PG 

o 这时上线后故障 OSD 的 Replicate PG会得到Primary PG的查询请求,发送自己这份“过时”的元数据和PGLog;

o Primary PG 对比数据后发现该 PG 落后并且过时,比通过PGLog建立了missing列表;

o Primary PG 标记自己处于 Active 状态;
Peering过程中涉及到PGLog(pg_info和pg_log)的步骤主要包括:

· GetInfo : PG的Primary OSD通过发送消息获取各个Replicate OSD的pg_info信息。在收到各个Replicate OSD的pg_info后,会调用PG::proc_replica_info处理副本OSD的pg_info,在这里面会调用info.history.merge合并Replicate OSD发过来的pg_info信息,合并的原则就是更新为最新的字段(比如last_epoch_started和last_epoch_clean都变成最新的);

· GetLog:根据pg_info的比较,选择一个拥有权威日志的OSD(auth_log_shard),如果Primary OSD不是拥有权威日志的OSD,就去该OSD上获取权威日志;

o 选取拥有权威日志的OSD时,遵循3个原则(在find_best_info里):

§ Prefer newer last_update

§ Prefer longer tail if it brings another info into contiguity

§ Prefer current primary

o 也就是说对比各个OSD的pg_info_t,谁的last_update大,就选谁,如果last_update都一样,则谁的log_tail小,就选谁,如果log_tail也一样,就选当前的Primary OSD;

o 如果Primary OSD不是拥有权威日志的OSD,则需要去拥有权威日志的osd上去拉取权威日志,收到权威日志后,会调用proc_master_log将权威日志合并到本地pg log;

o 在merge权威log到本地pg log的过程中,会将merge的pg_log_entry_t对应的oid和eversion放到missing列表里,这个missing列表里的对象就是Primary OSD所缺失的对象,后续在recovery的时候需要从其他osd pull的。

· GetMissing:拉取其它Replicate OSD的pg log(或者部分获取,或者全部获取FULL_LOG) ,通过本地的auth log对比,调用proc_replica_log处理日志,会将Replicate OSD里缺失的对象放到peer_missing列表里,以用于后续recovery过程的依据;注意:实际上是在PG:activate里更新peer_missing列表的,在proc_replica_log处理的只是从replica传过来它本地的missing(就是replica重启后根据自身的last_update和last_complete构造的missing列表),一般情况下这个missing列表是空

 

2.2 恢复流程图


0 0
原创粉丝点击