ceph 源代码分析 — peering 过程

本人最近仔细研究ceph 恢复部分的源代码,这个阅读分析的过程比较艰难,分享在此,希望大家能互相交流学习,有不正确的地方,希望大家指正!

Peering的作用

Peer的过程,是使一个PG内的OSD达成一个一致状态,当主从副本完成达成一个一致的状态,peering 的状态就结束,PG处于active状态。但此时,该PG的三个OSD的数据副本并非完全一致。

基本概念

acting set 和 up set

acting set 就是一个pg对应的副本所在的osd 列表,列表是有序的,第一个osd 为 primary. 在通常情况下,up set和acting set 相同,要理解不同之处,需要理解pg_temp

pg temp

如果pg的acting set 为[0,1,2], 此时如果osd.0出现故障,导致CRUSH算法重新分配该pg的acting set 为[3,1,2]. 此时osd.3为该pg的主osd,但是osd.3并不能负担该pg的读操作,因为其上现在还没有数据。所以向monitor申请一个临时的pg,osd.1为临时主osd,这时acting set依然为[0,1,2],up set 变为[1,3,2],此时就出来acting set 和up set的不同。当osd.3 backfill完成之后,该pg的up set恢复为acting set, 也就是acting set 和 up set都为[0,1,2]

current interval 和 past_interval

都是epoch的一个序列,在这个序列之内,一个pg的acting set 和 up set不会变化。current是当前的序列,past是上一个阶段的序列。
last_epoch_started: pg peering完成之后的epoch
last_epoch_clean: pg recovery完成,处于clean状态的epoch

例如:
ceph 系统当前的epoch值为20, pg1.0 的 acting set 和 up set 都为[0,1,2]

  • osd.3失效导致了osd map变化,epoch变为 21
  • osd.5失效导致了osd map变化,epoch变为 22
  • osd.6失效导致了osd map变化,epoch变为 23
    上述三次epoch的变化都不会改变pg1.0的acting set和up set
  • osd.2失效导致了osd map变化,epoch变为 24
    此时导致pg1.0的acting set 和 up set变为 [0,1,8],若此时 peering过程成功完成,则last_epoch_started 为24
  • osd.12失效导致了osd map变化,epoch变为 25
    此时如果pg1.0完成了recovery,处于clean状态,last_epoch_clean就为25
  • osd13失效导致了osd map变化,epoch变为 26
    epoch 序列 21,22,23,23 就为pg1.0的past interval
    epoch 序列 24,25,26就为 pg1.0的current interval

pg log

保存了该pg内所有更新操作的日志记录,具体记录的字段,可以查看struct pg_log_entry_t,这里列出关键的字段:

  • __s32 op; 操作的类型
  • hobject_t soid; 操作的对象
  • eversion_t version, prior_version, reverting_to; 操作的版本

需要注意的是,pg log的记录是以整个PG 为单位,而不是以对象为单位

PG info

关于pg在osd上的一些元数据信息,具体保存struct pg_info_t 数据结构里
列举一些重要的字段:

  • eversion_t last_update; //pg 最后一次更新的eversion
  • eversion_t last_complete; //recovery完成后的最后一个everson,也就是pg处于clean状态的最后一次操作的 eversion
  • epoch_t last_epoch_started;// last epoch at which this pg started on this osd 这个pg在这个osd上的最近的的开始的epoch,也就是最近一次peering完成后的epoch
  • version_t last_user_version; // last user object version applied to store
  • eversion_t log_tail; // 日志的尾,也就是日志最老的日志记录
  • hobject_t last_backfill; // objects >= this and < last_complete may be missing
    最后一个backfill 操作的对象,如果该osd的backfill没有完成,那么last_backfill 和last_complete之间的对象就丢失

Authoritative History

权威日志,在代码里简写为olog. 某个pg的一个完整的,顺序的操作日志。
eversion_t head; // 日志头,保存最新写入的日志
eversion_t tail; // 日志尾,最后早写入的日志

up_thru

这个实在不适合特别理解的概念

Peering的时机

当系统初始化时,或者有OSD 失效,或者OSD增加或者删除,导致OSD map发生变化,这引起PG的 acting set的变化,该PG会发起Peering过程。

Peering的基本过程

Peering的过程,基本分为三个步骤

  • GetInfo : pg的主osd通过发送消息获取各个从OSD的pg_info信息
  • GetLog:根据pg_info的比较,选择一个拥有权威日志的osd(auth_log_shard) , 如果主osd不是拥有权威日志的osd,就去该osd上获取权威日志
  • GetMissing:拉取其它从OSD 的pg log(或者部分获取,或者全部获取FULL_LOG) , 通过本地的auth log对比,来判别从OSD 上缺失的object 信息。以用于后续recovery过程的依据.
  • Active: 激活主osd,并发想通知notify消息,激活相应的从osd

GetInfo

pg_info_t

struct pg_info_t {
  spg_t pgid;
  eversion_t last_update;  //pg 最后一次更新的eversion
  eversion_t last_complete;  //recovery完成后的最后一个everson,也就是pg处于clean状态的最后一次操作的 eversion
  epoch_t last_epoch_started;// last epoch at which this pg started on this osd 这个pg在这个osd上的最近的的开始的epoch,也就是最近一次peering完成后的epoch
  version_t last_user_version; // last user object version applied to store
  eversion_t log_tail;     // oldest log entry.
  hobject_t last_backfill;   // objects >= this and < last_complete may be missing

  interval_set<snapid_t> purged_snaps;  //pg的要删除的snap集合

  pg_stat_t stats;
  pg_history_t history;  //pg的历史信息
  pg_hit_set_history_t hit_set;  //这个是cache tie用的hit_set
  ....
  }

get_infos

generate_past_intervals

计算past intervals

build_prior

根据past inerval 计算probe_targes

  1. 首先把当前PG中的 acting 和 up 的OSD 加入到probe 列表中
  2. 检查每个past_intervals 里

    • 如果interval.last < info.history.last_epoch_started,这种情况下,根本就是不care
    • 如果该interval 的act为空
    • 如果该interval 没有rw操作

    Probe: 现在 up 和acting的osd,以及在past interval 期间up的osd,用于获取权威日志和后续数据恢复
    up_now 保存在该interval 内 up的osd
    down 保存现在已经down的osd

  3. 调用pcontdec 函数检查该interval的up_now 的OSD是否有足够的OSD,对应replicated类,就是否有一正常的OSD,对应EC(n+m),是否有n个OS

  4. 检查是否需要need_up_thru
  5. 设置probe_targets

get_infos

void PG::RecoveryState::GetInfo::get_infos()
函数get_infos 向prior_set的probe 集合中的每个osd发送pg_query_t::INFO的消息,来获取pg_info信息

context< RecoveryMachine >().send_query(
             peer, 
             pg_query_t(pg_query_t::INFO,
                        it->shard, pg->pg_whoami.shard,
                        pg->info.history,
                        pg->get_osdmap()->get_epoch())
             );
      peer_info_requested.insert(peer);
      pg->blocked_by.insert(peer.osd)

发送消息的过程调用RecoveryMachine类的send_query 函数

收到查询peer_info的ack处理

在主osd 收到pg_info的ack时,包装成MNotifyRec的事件发送给状态机。
boost::statechart::result PG::RecoveryState::GetInfo::react(const MNotifyRec& infoevt)

  1. 首先从peer_info_requested 里删除该peer,同时从blocked_by队列里删除
  2. 调用函数bool PG::proc_replica_info(pg_shard_t from, const pg_info_t &oinfo)来处理副本的pg_info

    • 首先检查该osd的pg_info是否已经存储,并且last_update参数相同,则说明已经处理过,返回false
    • 确定自己是primary,把该osd的peer_info信息保存peer_info数组,并加入might_have_unfound数组里,该数组的osd保存一些对象,在后续的恢复操作中使用
    • 调用函数unreg_next_scrub (目前不少特别清楚)
    • 调用info.history.merge 函数处理slave osd发过来的pg_info信息,基本就是更新为最新的字段。设置dirty_info 为true
    • 调用 reg_next_scrub()
    • 如果该osd既不在up数组中也不再acting数组中,那就就是加入的stray_set列表中,如果pg处于clean状态,就调用purge_strays函数删除stray状态的pg以及其上的对象数据
    • 如果是一个新的osd,就调用函数update_heartbeat_peers 更新需要heartbeat的osd列表
  3. old_start保存了调用proc_replica_info前主osd的pg->info.history.last_epoch_started,如果该epoch值小于合并后的值,说明该值更新,从osd上的epoch值比较新

    • 则调用pg->build_prior重新构建prior_set
    • 从peer_info_requested队列中去掉上次构建的prior_set中存在osd,这最新构建中不存在的osd
    • 调用get_infos函数重新发送查询peer_info请求
  4. 调用pg->apply_peer_features更新features(具体什么features,不太清楚)

  5. 当peer_info_requested队列为空,并且prior_set不处于pg_down的状态时,说明收到所有的osd的peer_info并处理完成
  6. 最后检查 past_interval阶段至少有一个osd处于up并且非incomplete的状态
  7. 最后完成处理 ,调用函数post_event(GotInfo())抛出GetInfo事件进入状态机的下一个状态。

PG_GetLog

当pg的主osd获取各个从osd(以及past interval期间的参与的osd)的pg_info的信息后选取一个权威的日志的osd

PG::RecoveryState::GetLog::GetLog

  1. 调用函数pg->choose_acting(auth_log_shard)选取拥有权威日志的osd,输出保存在auth_log_shard里
  2. 如果auth_log_shard == pg->pg_whoami,也就是选择的拥有权威日志的log就是自己,直接抛出事件GotLog()完成操作
  3. 如果自己不是,则需要去拥有权威日志的osd上去拉取权威日志,收到权威日志后,触发GetLog事件

boost::statechart::result PG::RecoveryState::GetLog::react(const GotLog&)
收到GetLog事件的处理

proc_master_log

void PG::proc_master_log
合并权威日志到本地pg log

find_best_info

函数find_best_info完成根据各个osd的pg_info_t的信息,选取一个拥有权威日志的osd,选择的优先顺序:

  • Prefer newer last_update
  • Prefer longer tail if it brings another info into contiguity
  • Prefer current primary

代码实现具体的过程如下:

  • 首先计算min_last_update_acceptable 和 max_last_epoch_started
  • 如果min_last_update_acceptable == eversion_t::max(),也就是没有效的min_last_update, 直接返回infos.end()的空iterator (这种情况下,pg可能处于初始化或者pg没有写操作)
  • 根据以下条件选择一个osd
    • 首先把last_update小于min_last_update_acceptable或者last_epoch_started小于 max_last_epoch_started_found 或者处于incomplete的osd去掉
    • 如是ec,选择最新的last_update, 如果是replicate,选择最大的last_update的osd
    • 选择longer tail和 current primary

calc_replicated_acting

  1. 首先选择primay osd. 如果up_primary 处于非incomplete状态,并且last_update >= auth_log_shard->second.log_tail, 那么优先选择up_pirmray为primray,否则选择auth_log_shard为primray
  2. 以下过程选择可用的size个和 primay完全,分别添加到队列:
    backfill:需要backfill的osd集合
    acting_backfill: backfill + acting
    want: 和acting_backfill 集合相同,主要是在compat_mode模式下兼容Erasure Code
    • 首先在up里面选择,如果处于incomplete的状态, cur_info.last_update小于primary和 auth_log_shard的最小值,该pg 无法根据pg log 来进行部分对象的的拷贝修复,需要backfill,把该osd分别加入backfill 和 acting_backfill集合,否则只加入acting_backfill集合
    • 同样的流程,在acting 和 all_info 中选择

choose_acting

  1. 首先调用find_best_info函数获取拥有权威日志的osd
  2. 如果没有选举出权威日志的osd,如何处理 ?目前这个逻辑不是特别清楚
  3. 如果当前的primary
  4. 计算是否是compat_mode
  5. 调用calc_replicated_acting,计算出backfill 和 acting_backfill(want)集合
  6. 计算num_want_acting数量,检查如果小于min_size,对于EC处于incomplete状态,对于Replicae 处于Peered状态,并返回flase
  7. 检查是否有足够的osd来进行recovery
  8. 检查如果acting不等于up,需要申请pg_temp
  9. 最后验证,如果backfill_targets为空,strary_set里面应该没有want_backfill的osd;否则如果backfill_targets不为空,需要backfill的osd不在strary_set中

PG_GetMissing

PG::RecoveryState::GetMissing::GetMissing(my_context ctx)

  1. 遍历pg->actingbackfill集合的osd
  2. 如果osd为backfill或者日志完整,只添加到pg->peer_missing
  3. 如果日志不全,向该osd发送请求,获取log+missing 或者 fulllog+missing

boost::statechart::result PG::RecoveryState::GetMissing::react(const MLogRec& logevt)
当收到获取日志的ack应对,包装成MLogRec事件,GetMissing状态处理该事件

  1. 调用proc_replica_log处理日志
  2. 如果peer_missing_requested为空,即所有的获取日志的请求返回并处理,如果需要pg->need_up_thru,抛出 post_event(NeedUpThru()); 否则,直接 post_event(Activate(pg->get_osdmap()->get_epoch())); 进入Activate状态

proc_replica_log

  1. 调用pg_log.proc_replica_log来处理日志,输出为omissing,也就是该osd缺失的对象
  2. 加入might_have_unfound.insert(from);
  3. 加入peer_missing[from].swap(omissing)

Activate

PG::RecoveryState::Active::Active(my_context ctx)

  1. 在构造函数里初始化了remote_shards_to_reserve_recovery 和remote_shards_to_reserve_backfill,需要recovery和backfill的
  2. pg->start_flush 这个目前不清楚干啥用的
  3. 调用pg->activate

Active

PG::activate 这个函数是peering过程的最后一步。 这个函数在ReplicaActive中激活slave osd时也调用本函数,所有里面有分别对primay osd和 slave osd 的两种情况的分别处理。

  1. 如果有replay,处理replay
  2. 如当前osd是primary, 并且acting.size() >= pool.info.min_size时更新info.last_epoch_started;如果是slave osd,确保该osd是acting状态,更新info.last_epoch_started。
  3. 更新一些字段

  4. find out when we commit

  5. initialize snap_trimq
  6. 处理complete pointer
    如果 missing.num_missing() == 0,表明没有miss的object,处于clean 状态。直接更新info.last_complete = info.last_update,并调用pg_log.reset_recovery_pointers() 调整指针
    否则pg_log.activate_not_complete(info)
  7. 以下都是primay的操作
    • pi.last_update == info.last_update
    • 否则需要backfill
    • Set up missing_loc
    • needs_recovery,把该pg加入到 osd->queue_for_recovery(this);
    • 标记pg的状态为 PG_STATE_ACTIVATING

AllReplicasActivated

boost::statechart::result PG::RecoveryState::Active::react(const AllReplicasActivated &evt)
当所有的replica 处于activated 状态时:

  1. 取消PG_STATE_ACTIVATING和PG_STATE_CREATING状态, 如果该pg的acting的osd的数量大于等于pool的min_size,设置该pg为PG_STATE_ACTIVE的状态,否则设置为PG_STATE_PEERED状
  2. share_pg_info
  3. ReplicatedPG::check_local,检查本地的stray对象都被删除了
  4. 如果有读写请求在等待peering完成,则把该请求添加到处理队列 pg->requeue_ops(pg->waiting_for_peered);
  5. 调用void ReplicatedPG::on_activate
  6. 如果需要recovery, 触发DoRecovery事件,如果需要backfill, 触发RequestBackfill事件,否则触发AllReplicasRecovered事件
  7. 初始化Cache Tier 需要的hist_set, hit_set_setup
  8. 初始化 Cache Tier需要的agent,agent_setup

你可能感兴趣的:(ceph-源代码)