Ceph源码解析:PGpeering
集群中的设备异常(异常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 t & up t:每个pg都有这两个集合,acting t中保存是该pg所有的副本所在OSD的集合,⽐如acting[0,1,2],就表⽰这个pg的副本保存在OSD.0 、OSD.1、OSD.2中,⽽且排在第⼀位的是OSD.0 ,表⽰这个OSD.0是PG的primary副本。在通常情况下 up t 与 acting t是相同的。区别不同之处需要先了解pg_temp。
3.Epoch:osdmap的版本号,单调递增,osdmap每变化⼀次加1
4.current_interval & past interval:⼀个epoch序列,在这个序列内,这个PG的acting t没有变化过,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 t 和 up t 都为[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 t和up t
osd.2失效导致了osd map变化,epoch变为 24
此时导致pg1.0的acting t 和 up t变为 [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
lpt15.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具体流程
算法流程图:
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经历以下主要过程:
1、GetInfo:
咖啡瘦身1.1、选取⼀个epoch区间,对区间内的每个epoch计算其对应的acting t、acting primary、up t、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 t;同时将当前的acting t和up t加⼊到prior t;
pg->build_prior(prior_t);
据past_interval⽣成prior t集合。确定prior t集合,如果处于当前的acting和up集合中的成员,循环遍历past_interval中的每⼀个interval,interval.last >= info.history.last_epoch_started、! pty()、interval.maybe_went_rw,在该interval 中的acting集合中,并且在集群中仍然是up状态的。
1.3、向prior_t中的每个up状态的osd发送Query INFO请求,并等待接收应答,将接收到的请求保存到peer_info中;
context< RecoveryMachine >().nd_query(
peer, pg_query_t(pg_query_t::INFO,
it->shard, pg->pg_whoami.shard,
pg->info.history,
pg->get_osdmap()->get_epoch()));
根据priort 集合,开始获取集合中的所有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 t/peer_info中将处于complete状态的pg以及up t的所有pg存⼊acting_backfill;
pg->choo_acting(auth_log_shard,
&context< Peering >().history_les_bound)
通过pg->choo_acting(auth_log_shard)选择acting集合和auth_osd.
choo_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>().nd_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(360浏览器医生
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;
asrt(!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.inrt(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 ((oinfo.history)) //对history信息进⾏合并。
dirty_info = true;
asrt(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 t⽤于后续recovery;
context< RecoveryMachine >().nd_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_a(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_pty())
{
if (pg->need_up_thru)
{
dout(10) << " still need up_thru update before going active" << dendl;
post_event(NeedUpThru());
}
el
{
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 t、acting t、up t中的每个osd 请求pg infos, 选出authoritative log对应的pg;其次向authoritative log所在的osd请求authoritative log;最后获取recovery过程需要的missing t;
peering时间的长短并不可控,主要是在于请求的osd是否能够及时响应;如果这个阶段某个osd down
掉,很可能导致部分pg⼀直处在peering状态,即所有分布到这个pg上的IO都会阻塞。
此⽂仅讲述了peering过程,peering之后还会进⾏recovery操作,recovery操作由处理线程直接调⽤函数void OSD::do_recovery(PG *pg, ThreadPool::TPHandle &handle)进⾏,后续再总结总结recovery过程和PG的状态机。
先附两张PG状态机的类型以及流程图: