阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

Ceph源码解析:PG peering

107次阅读
没有评论

共计 9688 个字符,预计需要花费 25 分钟才能阅读完成。

集群中的设备异常(异常 OSD 的添加删除操作),会导致 PG 的各个副本间出现数据的不一致现象,这时就需要进行数据的恢复,让所有的副本都达到一致的状态。

一、OSD 的故障和处理办法:

1. OSD 的故障种类:

故障 A:一个正常的 OSD 因为所在的设备发生异常,导致 OSD 不能正常工作,这样 OSD 超过设定的时间 就会被 out 出集群。

故障 B:一个正常的 OSD 因为所在的设备发生异常,导致 OSD 不能正常工作,但是在设定的时间内,它又可以正常的工作,这时会添加会集群中。

2. OSD 的故障处理:

故障 A:OSD 上所有的 PG,这些 PG 就会重新分配副本到其他 OSD 上。一个 PG 中包含的 object 数量是不限制的,这时会将 PG 中所有的 object 进行复制,可能会产生很大的数据复制。

故障 B:OSD 又重新回到 PG 当中去,这时需要判断一下,如果 OSD 能够进行增量恢复则进行增量恢复,否则进行全量恢复。(增量恢复:是指恢复 OSD 出现异常的期间,PG 内发生变化的 object。全量恢复:是指将 PG 内的全部 object 进行恢复,方法同故障 A 的处理)。

需要全量恢复的操作叫做 backfill 操作。需要增量恢复的操作叫做 recovery 操作。

二、概念解析:

1.osdmap:集群所有 osd 的集合,包括每个 osd 的 ip & state(up or down)

2.acting set & up set:每个 pg 都有这两个集合,acting set 中保存是该 pg 所有的副本所在 OSD 的集合,比如 acting[0,1,2],就表示这个 pg 的副本保存在 OSD.0、OSD.1、OSD.2 中,而且排在第一位的是 OSD.0,表示这个 OSD.0 是 PG 的 primary 副本。在通常情况下 up set 与 acting set 是相同的。区别不同之处需要先了解 pg_temp。

3.Epoch:osdmap 的版本号,单调递增,osdmap 每变化一次加 1

4.current_interval & past interval:一个 epoch 序列,在这个序列内,这个 PG 的 acting set 没有变化过,current 是当前的序列,past 是指过去的 interval。

last_epoch_started:上次经过 peering 后的 osdmap 版本号 epoch。

last_epoch_clean:上次经过 recovery 或者 backfill 后的 osdmap 版本号 epoch。

(注:peering 结束后,数据的恢复操作才刚开始,所以 last_epoch_started 与 last_epoch_clean 可能存在不同)。

例如:

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

5.authoritative history:完整的 pg log 操作序列

6.last epoch start:上次 peering 完成的 epoch

7.up_thru:一个 past interval 内,第一次完成 peering 的 epoch

8.pg_temp : 假设当一个 PG 的副本数量不够时,这时的副本情况为 acting/up  = [1,2]/[1,2]。这时添加一个 OSD.3 作为 PG 的副本。经过 crush 的计算发现,这个 OSD.3 应该为当前 PG 的 primary,但是呢,这 OSD.3 上面还没有 PG 的数据,所以无法承担 primary,所以需要申请一个 pg_temp,这个 pg_temp 就还采用 OSD.1 作为 primary,此时 pg 的集合为 acting,pg_temp 的集合为 up。当然 pg 与 pg_temp 是不一样的,所以这时 pg 的集合变成了[3,1,2]/[1,2,3]。当 OSD.3 上的数据全部都恢复完成后,就变成了[3,1,2]/[3,1,2]。

9.pg_log:pg_log 是用于恢复数据重要的结构,每个 pg 都有自己的 log。对于 pg 的每一个 object 操作都记录在 pg 当中。

  • __s32 op; 操作的类型

  • hobject_t soid; 操作的对象

  • eversion_t version, prior_version, reverting_to; 操作的版本

三、peering 具体流程

算法流程图:

                                  Ceph 源码解析:PG peering

Peering:互为副本的三个(此处为设置的副本个数,通常设置为 3)pg 的元数据达到一致的过程。官方解释如下:

the process of bringing all of the OSDs that store a Placement Group (PG) into agreement about the state of all of the objects (and their metadata) in that PG. Note that agreeing on the state does not mean that they all have the latest contents.

primary PG 和 raplica PG:互为副本的三个 pg 中,有一个主,另外两个为辅;其中为主的称为 primary PG,其他两个都称为 replica PG。

1、peering 过程的影响

故障 osd 重新上线后,primary PG 和 replica PG 会进入不同的处理流程。primary PG 会先进入 peering 状态,在这个状态的 pg 暂停处理 IO 请求,在生产环境中表现为集群部分 IO 不响应,甚至某些云主机因为等待 IO 造成应用无法正常处理。下面就 peering 过程的主要操作结合源码进行分析。

2、peering 过程分析

pg 是由 boost::statechart 实现的状态机,peering 经历以下主要过程:

          Ceph 源码解析:PG peering

1、GetInfo:

1.1、选取一个 epoch 区间,对区间内的每个 epoch 计算其对应的 acting set、acting primary、up set、up primary,将相同的结果作为一个 interval;

pg->generate_past_intervals();

调用 generate_past_intervals()函数,生成 past_interval 序列。首先确定查找 interval 的 start_epoch(history.last_epoch_clean 上次恢复数据完成的 epoch)和 end_epoch(history.same_interval_since 最近一次 interval 的起始 epoch)。确定了 start_epoch 和 end_epoch 之后,循环这个两个版本间的所有 osdmap,确定 pg 成员变化的区间 interval。

1.2、判断每个 interval,将 up 状态的 osd 加入到 prior set;同时将当前的 acting set 和 up set 加入到 prior set;

pg->build_prior(prior_set);

据 past_interval 生成 prior set 集合。确定 prior set 集合,如果处于当前的 acting 和 up 集合中的成员,循环遍历 past_interval 中的每一个 interval,interval.last  >=  info.history.last_epoch_started、! interval.acting.empty()、interval.maybe_went_rw,在该 interval 中的 acting 集合中,并且在集群中仍然是 up 状态的。

1.3、向 prior_set 中的每个 up 状态的 osd 发送 Query INFO 请求,并等待接收应答,将接收到的请求保存到 peer_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()));

 

根据 priorset 集合,开始获取集合中的所有 osd 的 info。这里会向所有的 osd 发送请求 info 的 req(PG::RecoveryState::GetInfo::get_infos())。发送请求后等待回复。

1.4、收到最后一个应答后,状态机 post event 到 GotInfo 状态;如果在此期间有一个接收请求的 osd down 掉,这个 PG 的状态将持续等待,直到对应的 osd 恢复;

boost::statechart::result PG::RecoveryState::GetInfo::react(const MNotifyRec &infoevt)

回复处理函数。主要调用了 pg->proc_replica_info 进行处理:1. 将 info 放入 peerinfo 数组中。2. 合并 history 记录。在这里会等待所有的副本都回复 info 信息。进入下一个状态 GetLog。

2、GetLog:

2.1、遍历 peer_info,查找 best info,将其作为 authoritative log;将 acting set/peer_info 中将处于 complete 状态的 pg 以及 up set 的所有 pg 存入 acting_backfill;

pg->choose_acting(auth_log_shard,
                          &context< Peering >().history_les_bound)

 

通过 pg->choose_acting(auth_log_shard)选择 acting 集合和 auth_osd.

choose_acting 中主要进行了两项重要的措施:

  • find_best_info,查找一个最优的 osd。在 find_best_info 中查找最优的 osd 时,判断的条件的优先级有三个:最大的 last_update、最小的 log_tail、当前的 primary。

    map<pg_shard_t, pg_info_t>::const_iterator auth_log_shard =
            find_best_info(all_info, history_les_bound);

  • calc_replicated_acting,选择参与 peering、recovering 的 osd 集合。

    • up 集合中的成员。所有的成员都是加入到 acting_backfilling 中,如果是 incomplete 状态的成员或者 日志衔接不上的成员 (cur.last_update<auth.log_tail) 则添加到 backfill 中,否则添加到 want 成员中。

    • acting 集合中的成员,该组内的成员不会添加到 backfill 中,所以只需要判断 如果状态是 complete 并且 日志能够衔接的上,则添加到 want 和 acting_backfilling 中。

    • 其他 prior 中的 osd 成员 处理同 acting 一致。

    • 经过这一步可知,acting_backfilling 的成员(可用日志恢复数据,或者帮助恢复数据的成员),backfill 的成员(只能通过其他的 osd 上 pg 的数据进行全量拷贝恢复),want 的成员(同样在 acting_backfill 中,但是不同于 backfill 的成员)。

  • calc_ec_acting。ceph 有两种 pool,一种是副本类型 pool,一种是纠删码类型 pool(类似 RAID)。具体实现后续补充,今天太晚了,有空看代码补补。

2.2、如果计算出的 authoritative log 对应的 pg 是自身,直接 post event 到 GotLog;否则,向其所在的 osd 发送 Query Log 请求;

context<RecoveryMachine>().send_query(
        auth_log_shard,
        pg_query_t(
            pg_query_t::LOG,
            auth_log_shard.shard, pg->pg_whoami.shard,
            request_log_from, pg->info.history,
            pg->get_osdmap()->get_epoch()));

2.3、接收请求的 osd 应答,并将获取的 log merge 到本地,状态机 post event 到 GetMissing;如果收不到应答,状态将持续等待;

boost::statechart::result PG::RecoveryState::GetLog::react(const GotLog &)

{
    dout(10) << “leaving GetLog” << dendl;
    PG *pg = context< RecoveryMachine >().pg;
    if (msg)
    {
        dout(10) << “processing master log” << dendl;
       
pg->proc_master_log(*context<RecoveryMachine>().get_cur_transaction(),
                            msg->info, msg->log, msg->missing,
                            auth_log_shard);//log 处理函数
    }
    pg->start_flush(
        context< RecoveryMachine >().get_cur_transaction(),
        context< RecoveryMachine >().get_on_applied_context_list(),
        context< RecoveryMachine >().get_on_safe_context_list());
 
return transit< GetMissing >();// 跳转到 GetMissing
}

 

void PG::proc_master_log(
    ObjectStore::Transaction &t, pg_info_t &oinfo,
    pg_log_t &olog, pg_missing_t &omissing, pg_shard_t from)
{
    dout(10) << “proc_master_log for osd.” << from << “: ”
            << olog << ” ” << omissing << dendl;
    assert(!is_peered() && is_primary());

    // merge log into our own log to build master log.  no need to
    // make any adjustments to their missing map; we are taking their
    // log to be authoritative (i.e., their entries are by definitely
    // non-divergent).
   
merge_log(t, oinfo, olog, from);// 该函数对 log 进行合并,形成一个权威顺序完整的一个 log。包括日志前后的修补,而且最重要的是修补的过程中,统计了本地副本中需要恢复 object 的情况 missing.add_next_event(ne)。这里已经开始统计 missing 结构了。
    peer_info[from] = oinfo;// 保存来自 best_log 的 oinfo 到本地的 peer-info 数组中。
    dout(10) << ” peer osd.” << from << ” now ” << oinfo << ” ” << omissing << dendl;
    might_have_unfound.insert(from);

    // See doc/dev/osd_internals/last_epoch_started
    if (oinfo.last_epoch_started > info.last_epoch_started)
    {
        info.last_epoch_started = oinfo.last_epoch_started;
        dirty_info = true;
    }
    if (info.history.merge(oinfo.history))  // 对 history 信息进行合并。
        dirty_info = true;
    assert(cct->_conf->osd_find_best_info_ignore_history_les ||
          info.last_epoch_started >= info.history.last_epoch_started);

    peer_missing[from].swap(omissing);// 将 missing 结构统计到本地的 peer_missing 结构中。
}

  • auth_log:一个是 auth_log 的合并,最大最权威的 log,恢复数据要根据这里进行。

  • missing:另外就是合并 log 过程中发现本地副本需要恢复的 object 集合。

  • omissing:auth_osd 需要进行恢复的 object 集合。

3、GetMissing:

3.1、遍历 acting_backfill,向与 primary pg log 有交集的 pg 所在的 osd 发送 Query Log 请求;将剩余没有交集的 pg 放入 peer_missing,生成 missing set 用于后续 recovery;

context< RecoveryMachine >().send_query(
                *i,
                pg_query_t(
                    pg_query_t::LOG,
                    i->shard, pg->pg_whoami.shard,
                    since, pg->info.history,
                    pg->get_osdmap()->get_epoch()));

3.2、将收到的每一个应答 merge 到本地,如果在此期间有 osd down 掉,这个 PG 的状态将持续等待;收到所有的应答后,当前 pg 的状态机进入 Activate 状态,peering 过程结束;

boost::statechart::result PG::RecoveryState::GetMissing::react(const MLogRec &logevt)

{
    PG *pg = context< RecoveryMachine >().pg;

    peer_missing_requested.erase(logevt.from);
   
pg->proc_replica_log(*context<RecoveryMachine>().get_cur_transaction(),
                        logevt.msg->info, logevt.msg->log, logevt.msg->missing, logevt.from);// 接收到其他 osd 发回的 log 信息并且进行处理。在 proc_replica_log 中对 peer_log 进行修剪,丢弃那些不完整不可用的 log。整理接收到的 oinfo 到 peerinfo 中,omissing 到 peer_missing 中。直接来到 active 状态。

    if (peer_missing_requested.empty())
    {
        if (pg->need_up_thru)
        {
            dout(10) << ” still need up_thru update before going active” << dendl;
            post_event(NeedUpThru());
        }
        else
        {
            dout(10) << “Got last missing, don’t need missing ”
                    << “posting Activate” << dendl;
           
post_event(Activate(pg->get_osdmap()->get_epoch()));
        }
    }
    return discard_event();
}

3、总结

从以上分析来看,整个 peering 过程主要分为三个阶段,GetInfo -> GetLog -> GetMissing,首先向 prior set、acting set、up set 中的每个 osd 请求 pg infos, 选出 authoritative log 对应的 pg;其次向 authoritative log 所在的 osd 请求 authoritative log;最后获取 recovery 过程需要的 missing set;

peering 时间的长短并不可控,主要是在于请求的 osd 是否能够及时响应;如果这个阶段某个 osd down 掉,很可能导致部分 pg 一直处在 peering 状态,即所有分布到这个 pg 上的 IO 都会阻塞。

 

此文仅讲述了 peering 过程,peering 之后还会进行 recovery 操作,recovery 操作由处理线程直接调用函数 void OSD::do_recovery(PG *pg, ThreadPool::TPHandle &handle)进行,后续再总结总结 recovery 过程和 PG 的状态机。

先附两张 PG 状态机的类型以及流程图:

                                                    Ceph 源码解析:PG peering

                                      Ceph 源码解析:PG peering

在 CentOS 7.1 上安装分布式存储系统 Ceph  http://www.linuxidc.com/Linux/2015-08/120990.htm

Ceph 环境配置文档 PDF http://www.linuxidc.com/Linux/2013-05/85212.htm 

CentOS7 下部署 Ceph 集群(版本 10.2.2)http://www.linuxidc.com/Linux/2017-02/140728.htm

Ceph 的安装过程 http://www.linuxidc.com/Linux/2013-05/85210.htm 

如何升级 Ceph 版本及注意事项  http://www.linuxidc.com/Linux/2017-02/140631.htm

HOWTO Install Ceph On FC12, FC 上安装 Ceph 分布式文件系统 http://www.linuxidc.com/Linux/2013-05/85209.htm 

实验环境 Ceph 9.2.1 部署笔记 http://www.linuxidc.com/Linux/2016-11/137094.htm

Ubuntu 16.04 快速安装 Ceph 集群  http://www.linuxidc.com/Linux/2016-09/135261.htm

Ceph 的详细介绍:请点这里
Ceph 的下载地址:请点这里

本文永久更新链接地址:http://www.linuxidc.com/Linux/2017-03/141874.htm

正文完
星哥说事-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2022-01-21发表,共计9688字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中