接着上一章,我们这里结合osd3_watch.txt
日志文件,以及pgmap_active_clean.txt、pgmap_in_down.txt、pgmap_out_down.txt,从中选出4
个具有代表性的PG,来分析一下osd0从in+up
到in+down
再到out+down
这一整个过程中PG所执行的动作。
选取的4个PG如下:
in + up in + down out + down
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
pg_stat up up_primary acting acting_primary pg_stat up up_primary acting acting_primary pg_stat up up_primary acting acting_primary
11.4 [0,3] 0 [0,3] 0 11.4 [3] 3 [3] 3 11.4 [3,2] 3 [3,2] 3
22.2c [0,3] 0 [0,3] 0 22.2c [3] 3 [3] 3 22.2c [5,7] 5 [5,7] 5
22.2a [3,0] 3 [3,0] 3 22.2a [3] 3 [3] 3 22.2a [3,6] 3 [3,6] 3
22.16 [3,7] 3 [3,7] 3 22.16 [3,7] 3 [3,7] 3 22.16 [3,7] 3 [3,7] 3
上面4个PG代表4种典型的场景:
-
PG 11.4 : PG的主osd关闭,在osd out之后,PG的其中一个副本进行remap
-
PG 22.2c: PG的主OSD关闭,在osd out之后,PG的两个副本进行remap
-
PG 22.2a: PG的副本OSD关闭,在osd out之后,PG的其中一个副本进行remap
-
PG 22.16: 关闭的OSD并不是PG的任何副本
使用如下命令从osd3_watch.txt中分别导出该PG相关的日志信息:
如下我们接着上一篇文章,分析在将osd0踢出osdmap之后,PG 11.4所执行的相关动作。
1. PG 11.4接收到新的osdmap
这里我们首先给出在这一阶段PG 11.4恢复过程的日志:
1.1 收到新的osdmap
在将osd0踢出osdmap之后,osd3会接收到这一变化,从而触发新的Peering进程。此时osd3接收到e2226
版本的osdmap:
然后调用如下函数来进行处理:
程序运行到这里,就构造了一个NullEvt到OSD的消息队列,从而触发相应的Peering流程。
2. 进入Peering流程
进入Peering流程的第一个处理函数为:
在上面的OSD::process_peering_events()函数中,遍历该OSD上的每一个PG:调用OSD::advance_pg()来进行osdmap追赶。如果OSD::advance_pg()返回值为true,表明当前已经追赶上最新的osdmap,已经可以处理peering event了;否则,表明当前还没有追赶上,因此会将该pg重新放入peering_wq中。
在这里针对PG 11.4而言,当前osd3的osdmap版本为e2226,而pg 11.4的osdmap版本为e2225,因此会执行上面的for循环。
1) 函数pg_to_up_acting_osds()
函数OSDMap::pg_to_up_acting_osds()根据osdmap来计算指定PG所映射到的osd。这里针对PG 11.4
而言,计算出的newup为[3,2], newacting为[3].
2) 函数handle_advance_map()
通过上面第一行的打印信息,我们了解到:PG11.4
对应的up set为[3,2], acting set也为[3],up_primary为3,acting_primary也为3。
之后会调用PG::update_osdmap_ref()将PG当前的osdmap进行更新;最后产生一个AdvMap事件,交由recovery_state来进行处理,从而触发peering进程。
3) 函数pg_update_epoch()
函数pg_update_epoch()更新PG的pg_epoch值
4) 函数handle_activate_map()
函数handle_activate_map()用于激活当前阶段的osdmap,通常是用于触发向Replica发送通知消息,以推动Peering的进程。
注: 通常来说,AdvMap是同步完成的,而ActMap是异步完成的。
2.1 Clean状态对AdvMap事件的处理
由于在当前状态,PG 11.4已经处于clean状态,且osd3对于PG 11.4而言是主OSD,因此接收到AdvMap事件时,处理流程如下:
从以上代码可以看出,其最终会调用pg->should_restart_peering()来检查是否需要触发新的peering操作。通过以前的代码分析,我们知道只要满足如下条件之一即需要重新peering:
这里针对PG 11.4,其up set发生了变化,因此会触发新的peering状态。此时state_machine进入Reset状态。
2.2 进入Reset状态
2.2.1 Reset构造函数
在Reset构造函数中,调用pg->set_last_peering_reset将last_peering_reset设置为e2226.
2.2.2 处理Adv事件
在进入Reset状态之前,我们通过post_event()向Reset投递了AdvMap事件,这里我们来看对该事件的处理:
上面调用pg->should_restart_peering()再一次检查是否需要重新peering。然后再调用pg->start_peering_interval()来启动peering流程。
1) 函数generate_past_intervals()
函数_calc_past_interval_range()用于计算一个past_interval的范围。所谓一个past_interval,是指一个连续的epoch段[epoch_start, epoch_end],在该epoch段内:
-
PG的acting primary保持不变;
-
PG的acting set保持不变;
-
PG的up primary保持不变;
-
PG的up set保持不变;
-
PG的min_size保持不变;
-
PG的副本size值保持不变;
-
PG没有进行分裂;
-
PG的sort bitwise保持不变;
函数_calc_past_interval_range()如果返回true,表示成功计算到一个past_interval;如果返回false,则表示该interval已经计算过,不必再计算,或者不是一个有效的past_interval。
结合当前的打印日志消息:
当前info.history.same_interval_since的值为e2223,而info.history.last_epoch_clean的值为e2224,因此函数_calc_past_interval_range()的返回值为false,表明不是一个有效的past_interval。
2) 函数start_peering_interval()
在收到新的osdmap,调用advance_map(),然后触发新的Peering之前,会调用PG::start_peering_interval(),完成相关状态的重新设置。
-
set_last_peering_reset()用于清空上一次的peering数据;
-
init_primary_up_acting()用于设置当前新的acting set以及up set
-
设置info.stats的up set、acting set的值
-
将当前PG的状态设置为REMAPPED
由于当前PG的up set的值为[3,2],acting set的值为[3],因此这里设置PG的状态为REMAPPED
这里osd3的角色是primary。
- check_new_interval(): 用于检查是否是一个新的interval。如果是新interval,则计算出该新的past_interval:
42242:2020-09-11 14:10:18.974135 7fba3d925700 10 osd.3 pg_epoch: 2226 pg[11.4( v 201’1 (0’0,201’1] local-les=2224 n=1 ec=132 les/c/f 2224/2224/0 2223/2223/2223) [3,2]/[3] r=0 lpr=2226 pi=2223-2225/1 luod=0’0 crt=201’1 lcod 0’0 mlcod 0’0 active+remapped] start_peering_interval: check_new_interval output: generate_past_intervals interval(2223-2225 up 3 acting 3): not rw, up_thru 2223 up_from 2123 last_epoch_clean 2224
当前的PG osdmap的epoch为e2226,当前的up set为[3,2], acting set为[3],计算出的一个past interval为[e2223,e2225],在此一interval期间,up set为[3], acting set也为[3],up_thru为e2223, up_from为e2123, last_epoch_clean为e2224.
- 清除PG的相关状态。关于PG的状态是由一个
unsigned
类型的整数来表示的
这里将active
状态清除了,因此变为inactive
状态。
- PG 11.4所在OSD3的角色未发生变化,仍然为primary,因此有如下输出
- cancel_recovery(): 取消PG当前正在进行中的recovery动作;
到此为止,由OSD::advance_pg()函数中pg->handle_advance_map()所触发的AdvMap事件就已经处理完成。
AdvMap事件主要作用是: 触发检查是否需要重新Peering,如果需要,并完成Peering的相关初始化操作。
2.2.3 Reset状态下对ActMap事件的处理
这里针对PG 11.4而言,通过AdvMap事件完成了Peering的相关检查操作。然后在OSD::advance_pg()函数中调用PG::handle_activate_map()来激活该OSDMap。这里是对该ActMap事件的处理,参看如下日志片段:
如下是相关的处理函数:
通常在osdmap发送变动,从而引发PG的acting set、up set发生变动,对于一个PG的副本OSD通常会发送一个通知消息到PG的Primary OSD。这里由于osd3已经是PG11.4的primary OSD,因此这里不需要发送通知消息。
注: 关于send_notify变量的设置,是在Initial::react(const Load&)函数中。另外send_notify()最终的发送函数为OSD::do_notifies()
2.3 进入Started状态
2.3.1 进入Started/Start状态
Started
的默认初始子状态为Start
状态:
这里osd3对于PG 11.4而言为主OSD,因此产生MakePrimary()事件,进入Started/Primary状态。
2.3.2 进入Started/Primary状态
2.4 进入Peering状态
Started/Primary的默认初始子状态为Started/Primary/Peering,我们来看如下Peering的构造函数:
进入Peering状态后,会将PG的状态设置为Peering
。然后直接进入Peering的默认初始子状态GetInfo.
2.4.1 进入Peering/GetInfo状态
进入GetInfo状态后,首先调用generate_past_intervals()计算past_intervals。这里我们在前面Reset阶段就已经计算出了新的past_interval为[2223,2225]。从如下日志片段中也能看出:
1) 函数PG::build_prior()
关于PriorSet,我们这里不做介绍,后面我们会再开辟专门的章节来进行说明(参见up_thru)。这里我们给出针对PG 11.4所构建的PriorSet的结果:
当前osdmap的epoch为e2226,对于osd3来说其上一次申请的up_thru为e2223,而当前的same_interval_since为e2226,因此这里need_up_thru需要设置为true。
2) 函数PG::get_infos()
这里由上面构造的PriorSet为:
GetInfo()是由PG的主OSD向从OSD发送pg_query_t::INFO消息以获取从OSD的pg_into_t信息。这里对于PG11.4而言,因为目前其prior_set->probe为[2,3]。因此这里会向osd2发出pg_query_t::INFO请求。这里我们还是讲述一下GetInfo究竟是要获取哪些info信息,下面我们先来看一下pg_query_t结构体的定义:
如下则是pg_info_t结构体的定义(src/osd/osd_types.h):
上面send_query()函数将要查询的pg_query_t::INFO放入RecoveryMachine的state中,然后会由OSD::process_peering_events()中的dispatch_context()来将消息发送出去:
接着在从OSD接收到MSG_OSD_PG_QUERY消息后:
从上面我们可以看到查询pg_query_t::INFO时返回的是一个pg_notify_t类型的包装,其封装了pg_info_t数据结构。
注: 从上面OSD::handle_pg_query()的注释可看出,handle_pg_query()用于Primary OSD向replica/stray发送查询信息
之后,PG主OSD接收到pg_query_t::INFO的响应信息(MSG_OSD_PG_NOTIFY):
上面获取到PG info之后交由GetInfo::react(const MNotifyRec&)进行处理。到此为止,GetInfo()流程执行完毕。
参看如下日志片段:
2.4.2 进入Peering/GetLog状态
我们先给出此一阶段的一个日志片段:
GetLog构造函数如下:
1) 调用函数pg->choose_acting()来选择出拥有权威日志的OSD,并计算出acting_backfill
和backfill_targets
两个OSD列表。选择出来的权威OSD通过auth_log_shard参数返回;
2) 如果选择失败,并且want_acting不为空,就抛出NeedActingChange事件,状态机转移到Primary/WaitActingChange状态,等待申请临时PG返回结果;如果want_acting为空,就抛出IsIncomplete事件,PG的状态机转移到Primary/Peering/Incomplete状态,表明失败,PG就处于InComplete状态。
3) 如果auth_log_shard等于pg->pg_whoami,也就是选出的拥有权威日志的OSD为当前主OSD,直接抛出GotLog()完成GetLog过程;
4) 如果pg->info.last_update小于best.log_tail,也就是本OSD的日志和权威日志不重叠,那么本OSD无法恢复,抛出IsInComplete事件。经过函数choose_acting()的选择后,主OSD必须是可恢复的。如果主OSD不可恢复,必须申请临时PG,选择拥有权威日志的OSD为临时主OSD;
5)如果自己不是权威日志的OSD,则需要去拥有权威日志的OSD上去拉取权威日志,并与本地合并。发送pg_query_t::LOG请求的过程与pg_query_t::INFO的过程是一样的。
2.4.2.1 PG::choose_acting()函数
函数choose_acting()用来计算PG的acting_backfill和backfill_targets两个OSD列表。acting_backfill保存了当前PG的acting列表,包括需要进行Backfill操作的OSD列表;backfill_targets列表保存了需要进行Backfill的OSD列表。
1) 首先调用函数PG::find_best_info()来选举出一个拥有权威日志的OSD,保存在变量auth_log_shard里;
2) 如果没有选举出拥有权威日志的OSD,则进入如下流程:
a) 如果up不等于acting,申请临时PG,返回false值;
b) 否则确保want_acting列表为空,返回false值;
注: 在osdmap发生变化时,OSD::advance_pg()中会计算acting以及up,最终调用到OSDMap::_pg_to_up_acting_osds()来进行计算;
3) 计算是否是compat_mode模式,检查是,如果所有的OSD都支持纠删码,就设置compat_mode值为true;
4) 根据PG的不同类型,调用不同的函数。对应ReplicatedPG调用函数calc_replicated_acting()来计算PG需要的列表
在这里针对PG 11.4而言,我们选择出来的权威日志OSD为osd3,PG的up set为[3,2], acting set为[3]。调用ReplicatedPG::calc_replicated_acting()计算出的want为[3,2],因此这里want不等于acting,需要申请产生pg_temp。
OSDService::send_pg_temp()请求会在OSD::process_peering_events()的最后发出。通过下面的日志,我们可以看出发出去的pg_temp请求中PG 11.4的pg_temp为[]:
上面我们可以看到,但want_acting
等于up
时,调用PG::queue_want_pg_temp()时传递了一个empty,这个是什么意思呢? 其实是用于向OsdMonitor发送一个请求,要求删除PG 11.4对应的PG temp,参看如下日志(mon-node7-1.txt):
代码调用流程如下:
2.4.3 进入Peering/WaitActingChange状态
在上面Peering/GetLog构造函数中,由于PG::choose_acting()函数返回false,并且pg->want_acting不为空,因此产生NeedActingChange()事件,从而进入WaitActingChange状态。
1) 收到AdvMap事件
在进入WaitActingChange状态之前,由于osd3向OSDMonitor申请pg temp,导致osdmap发生变更,从而触发如下调用(此时osdmap版本为e2227):
由于此时up set为[3,2],acting set也由[3]变为了[3,2],因此会再一次触发Peering操作。这里重新进入Reset状态。
如下是本阶段的一个日志片段:
3. 重新进入Peering流程
在上面最后收到的osdmap版本为e2227,然后重新进入Reset阶段,触发新的Peering过程。
3.1 Reset状态
调用pg::set_last_peering_reset()设置osdmap的epoch为e2227.
3.1 Reset状态下对AdvMap事件的处理
在进入Reset状态之前,我们向其投递了AdvMap事件,这里我们来看一下对该事件的处理:
如下是此一过程的日志片段:
具体的代码分析,我们在前面已经讲解过,这里不再赘述。
3.2 ActMap事件的处理
在接收到新的OSDMap,处理完成AdvMap事件之后,接着就会调用PG::handle_activate_map()来激活osdmap:
如下是相关日志片段,具体代码分析这里将不再赘述:
3.3 进入Started状态
Started
构造函数如下:
3.3.1 进入Started/Start状态
进入Started状态后,会进去其默认子状态Start
:
在Start
状态的构造函数中,根据PG对应OSD的角色不同,选择进入Primary
状态或者Stray
状态。
这里osd3对于PG 11.4而言为主OSD,因此产生MakePrimary()事件,进入Started/Primary状态。
3.3.2 进入Started/Primary状态
Primary构造函数如下:
注意: 在上一次退出Primary
状态时,我们就已经清空了pg->want_acting。
3.4 进入Peering状态
Started/Primary的默认初始子状态为Started/Primary/Peering,现在我们来看一下Peering的构造函数:
3.4.1 进入Started/Primary/Peering/GetInfo状态
进入Peering状态后,默认会跳转进入Peering的子状态GetInfo,如下是此过程的一个日志片段:
GetInfo
状态的构造函数如下:
1) 函数PG::generate_past_intervals()
当前的osdmap版本为e2227,在e2226时PG 11.4还未进入Active状态即又开始触发Peering过程,因此在上面PG::_calc_past_interval_range()函数中info.history.last_epoch_clean的值仍为e2224。参看如下日志片段:
2) 函数PG::build_prior()
PriorSet
用于记录PG前一阶段的状态信息,主要用于辅助当前阶段的恢复工作。在当前阶段osdmap版本为e2227,up set为[3,2], acting set为[3,2],info.history.last_epoch_clean为e2224。在构建PriorSet时,参看如下日志片段:
从上面可以看到,在[e2226,e2226]阶段对应的up set为[3,2], acting set为[3]; 在[e2223, e2225]阶段对应的up set为[3], acting set为[3],并且可能执行了写操作。在当前e2227阶段,probe set为[2,3], down set为[],并且获取到OSD3的up_thru为e2226,需要通知monitor。
通知monitor会在OSD::process_peering_events()函数的最后来进行:
关于PriorSet
更为详细的介绍,我们会在后续up_thru相关文章中进一步说明。
3) PG::get_infos()
在PG::get_infos()函数中会遍历prior_set,然后向对应的从OSD发送pg_query_t::INFO消息,以获取从OSD的pg_info_t信息。这里对于PG 11.4而言,因为在前面PG::build_prior()所构建的probe为[2,3],因此这里会向osd2发送pg_query_t::INFO信息。
3) 收到MNotifyRec信息
在上面步骤2) 中向OSD2发送了pg_query_t::INFO消息,这里收到了对该请求的响应,然后调用GetInfo::react(const MNotifyRec &)来进行处理:
下面给出此一过程相应的日志片段:
在上面react(const MNotifyRec&)函数中,首先调用PG::proc_replica_info()来处理从OSD返回过来的pg_info信息。如果PG::proc_replica_info()返回true,表明成功返回的pg_info信息有效,然后执行如下流程:
3.1) 检查pg->info.history.last_epoch_started,如果主OSD的old_start
小于返回的history.last_epoch_started,表明当前主OSD过于老旧,需要重新build_prior(),然后重新get_infos();
注: 当前pg->info.history.last_epoch_started的值为e2224??
3.2) 判断我们已经收到了pg->peer_info_requested中所有请求的响应信息。在这些响应中,我们要求在最后一个rw interval中,至少要有一个OSD处于complete状态,这样才能准确获取到该PG在最后所做的修改,后续才能进行恢复
这里针对PG 11.4而言,最后一个past_interval为[e2226,e2226],倒数第二个past_interval为[e2223, e2225]。
4) 函数PG::proc_replica_info()
PG::proc_replica_info()首先判断收到的pg_info信息是否有效,如下两种情况都被判定为无效,直接返回false
-
收到了重复的pg_info
-
pg_info源osd当前是否处于down状态
接着调用pg_history_t::merge()来合并history,这是一个十分重要的函数:
从上面可以看到,如果相应的history信息发生了改变,则merge()成功,返回true,否则返回false。pg_history_t::merge()函数主要用于合并如下字段:
-
history.epoch_created
-
history.last_epoch_started
-
history.last_epoch_clean
-
history.last_epoch_split
-
history.last_epoch_marked_full
-
history.last_scrub
-
history.last_scrub_stamp
-
history.last_deep_scrub
-
history.last_deep_scrub_stamp
-
history.last_clean_scrub_stamp
结合如下打印日志:
如下是打印pg_info的函数实现(src/osd/osd_types.h):
从这里我们可以知道,对于PG 11.4,从osd2获取到的pg_info信息为:
-
info.last_epoch_started为e0
-
info.stats.stats.sum.num_objects为0
-
info.history.epoch_created为0
-
info.history.last_epoch_started为e0
-
info.history.last_epoch_clean为e0
-
info.history.last_epoch_marked_full为e0
-
info.history.same_up_since为e0
-
info.history.same_interval_since为e0
-
info.history.same_primary_since为e0
3.4.2 进入Started/Primary/Peering/GetLog状态
在上面成功获取到Info信息之后,接着就会进入GetLog状态:
执行步骤如下:
1) 调用函数pg->choose_acting()来选择出拥有权威日志的OSD,并计算出acting_backfill
和backfill_targets
两个OSD列表。选择出来的权威OSD通过auth_log_shard参数返回;
2) 如果选择失败,并且want_acting不为空,就抛出NeedActingChange事件,状态机转移到Primary/WaitActingChange状态,等待申请临时PG返回结果;如果want_acting为空,就抛出IsIncomplete事件,PG的状态机转移到Primary/Peering/Incomplete状态,表明失败,PG就处于InComplete状态。
3) 如果auth_log_shard等于pg->pg_whoami,也就是选出的拥有权威日志的OSD为当前主OSD,直接抛出GotLog()完成GetLog过程;
4) 如果pg->info.last_update小于best.log_tail,也就是本OSD的日志和权威日志不重叠,那么本OSD无法恢复,抛出IsInComplete事件。经过函数choose_acting()的选择后,主OSD必须是可恢复的。如果主OSD不可恢复,必须申请临时PG,选择拥有权威日志的OSD为临时主OSD;
5)如果自己不是权威日志的OSD,则需要去拥有权威日志的OSD上去拉取权威日志,并与本地合并。发送pg_query_t::LOG请求的过程与pg_query_t::INFO的过程是一样的。
如下是此一阶段的日志片段:
3.4.1.1 函数PG::choose_acting()
函数choose_acting()用来计算PG的acting_backfill和backfill_targets两个OSD列表。acting_backfill保存了当前PG的acting列表,包括需要进行Backfill操作的OSD列表;backfill_targets列表保存了需要进行Backfill的OSD列表。
当前acting set为[3,2],获取到的权威日志信息如下:
因此这里权威肯定是存在于osd3上。
接着计算出来的actingbackfill为[2,3],want为[3,2],acting为[3,2]。
在GetLog()构造函数中选择出来的拥有权威日志的OSD为osd3,就是当前的主OSD,因此直接抛出GotLog()事件,从而进入GetMissing状态。
3.4.3 进入Started/Primary/Peering/GetMissing状态
进入GetMissing状态后,会遍历actingbackfill列表,当前针对PG 11.4而言其actingbackfill为[2,3]。由于PG 11.4中osd2返回来的pg_info.last_update为0,因此这里直接退出循环遍历。
之后由于在GetInfo()构造函数中,调用build_prior()计算得到PG 11.4需要进行up_thru操作,因此这里产生NeedUpThru事件,从而进入WaitUpThru状态。
如下是此一阶段的日志片段:
3.4.4 进入Started/Primary/Peering/WaitUpThru状态
如下是此一阶段的日志片段:
1) AdvMap事件的处理
由于上面need_up_thru,因此会向OSDMonitor发起up_thru请求,导致osdmap发生改变,因此调用OSD::process_peering_events()、OSD::advance_pg()、 PG::handle_advance_map()来处理这一变化,然后产生AdvMap事件。到此,我们获取到的最新的osdmap版本为e2228.
由于当前所处的状态为WaitUpThru,其本身并不能直接处理AdvMap事件,因此会调用其父状态的相关函数来进行处理,我们来看相应的代码:
在当前阶段,prior_set->probe为[2,3], prior_set->down为[],prior_set->blocked_by为{}。
PG::PriorSet::affected_by_map()函数的作用是判断指定的osdmap会不会影响当前的prior_set,如果返回true,表明会影响,此时应该进入Reset状态,重新peering;否则返回false。
在这里针对PG 11.4,osdmap e2228并不会影响到当前的prior_set,因此调用PG::adjust_need_up_thru()来调整need_up_thru:
由于当前up_thru为e2227,而info.history.same_interval_since为e2223,因此这里need_up_thru的值设置为false。
之后AdvMap事件会继续向上抛出,调用Started::react(const AdvMap&)来进行处理。这里并不需要重新触发Peering。
2) 处理ActMap事件
收到了osdmap变动的消息,处理完成AdvMap事件后,接着就会产生一个ActMap事件,如下是对该事件的处理:
上面我们可以看到,如果pg->need_up_thru为false,那么产生Activate()事件,从而进入Active状态。这里针对PG 11.4而言,其并不需要再一次进行up_thru了,因此这里进入Active状态。
3.5 进入Active状态
在上面Active状态的构造函数中,调用pg->activate()来激活PG,如下是此一阶段的一个日志片段:
状态Active的构造函数里处理过程如下:
1) 在构造函数里初始化了remote_shards_to_reserve_recovery和remote_shards_to_reserve_backfill,需要Recovery操作和Backfill操作的OSD;
2) 调用函数pg->start_flush()来完成相关数据的flush工作;
3) 调用函数pg->activate()完成最后的激活工作。
此时,由于PG 11.4
当前的映射为[3,2],但是osd2缺失了一个object,因此这里显示的状态为inactive
.
注: 进入Active状态后,默认进入其初始子状态Started/Primary/Active/Activating
3.5.1 MissingLoc
在讲解pg->activate()之前,我们先介绍一下其中使用到的MissingLoc。类MIssingLoc用来记录处于missing状态对象的位置,也就是缺失对象的正确版本分别在哪些OSD上。恢复时就去这些OSD上去拉取正确对象的对象数据:
下面介绍一些MissingLoc处理函数,作用是添加相应的missing对象列表。其对应两个函数:add_active_missing()函数和add_source_info()函数。
- add_active_missing()函数: 用于把一个副本中的所有缺失对象添加到MissingLoc的needs_recovery_map结构里
- add_source_info()函数: 用于计算每个缺失对象是否在本OSD上
具体实现如下:
遍历needs_recovery_map里的所有对象,对每个对象做如下处理:
1) 如果oinfo.last_update < need(所缺失的对象版本),就跳过;
2) 如果该PG正常的last_backfill指针小于MAX值,说明还处于Backfill阶段,但是sort_bitwise不正确,跳过;
3) 如果该对象大于last_backfill,显然该对象不存在,跳过;
4) 如果该对象大于last_complete,说明该对象或者是上次Peering之后缺失的对象,还没有来得及修复;或者是新创建的对象。检查如果missing记录已存在,就是上次缺失的对象,直接跳过;否则就是新创建的对象,存在该OSD中;
5)经过上述检查后,确认该对象在本OSD上,在missing_loc添加该对象的location为本OSD。
3.5.2 activate操作
PG::activate()函数是主OSD进入Active状态后执行的第一步操作:
该函数完成以下功能:
具体的处理过程如下:
1) 如果需要客户回答,就把PG添加到replay_queue队列里;
2) 更新info.last_epoch_started变量,info.last_epoch_started指的是本OSD在完成目前Peering进程后的更新,而info.history.last_epoch_started是PG的所有OSD都确认完成Peering的更新。
3) 更新一些相关的字段;
4) 注册C_PG_ActivateCommitted()回调函数,该函数最终完成activate的工作;
5) 初始化snap_trimq快照相关的变量;
6) 设置info.last_complete指针:
-
如果missing.num_missing()等于0,表明处于clean状态。直接更新info.last_complete等于info.last_update,并调用pg_log.reset_recovery_pointers()调整log的complete_to指针;
-
否则,如果有需要恢复的对象,就调用函数pg_log.activate_not_complete(info),设置info.last_complete为缺失的第一个对象的前一个版本。
7) 以下都是主OSD的操作,给每个从OSD发送MOSDPGLog类型的消息,激活该PG的从OSD上的副本。分别对应三种不同处理:
-
如果pi.last_update等于info.last_update,这种情况下,该OSD本身就是clean的,不需要给该OSD发送其他信息。添加到activator_map只发送pg_info来激活从OSD。其最终的执行在PeeringWQ的线程执行完状态机的事件处理后,在函数OSD::dispatch_context()里调用OSD::do_info()函数实现;
-
需要Backfill操作的OSD,发送pg_info,以及osd_min_pg_log_entries数量的PG日志;
-
需要Recovery操作的OSD,发送pg_info,以及从缺失的日志;
8) 设置MissingLoc,也就是统计缺失的对象,以及缺失的对象所在的OSD,核心就是调用MissingLoc的add_source_info()函数,见MissingLoc的相关分析;
在这里针对PG 11.4而言,osd2是需要进行恢复的,因此会加入missing_loc列表中。
9) 如果需要恢复,把该PG加入到osd->queue_for_recovery(this)的恢复队列中;
10) 如果PG当前acting set的size小于该PG所在pool设置的副本size,也就是当前的OSD不够,就标记PG的状态为PG_STATE_DEGRADED
和PG_STATE_UNDERSIZED
,最后标记PG为PG_STATE_ACTIVATING
状态;
这里针对PG 11.4而言,osd2的pg_info.last_update为e0,这里会进入最后的else分支,进行Recovery操作: 发送pg_info以及从缺失的日志信息。并把osd2加入Recovery列表,以进行数据恢复。
3.5.3 Primary激活成功的回调
在上面PG::activate()函数中为对应的RecoveryCtx.transaction注册了一个提交的回调函数:
对于RecoveryCtx.transaction事务的提交会在如下函数中进行:
如下是此一过程的一个日志片段:
3.5.4 收到从OSD的MOSDPGLog的应答
当收到从OSD发送的对MOSDPGLog的ACK消息后,触发MInfoRec事件,下面这个函数处理该事件:
处理过程比较简单:检查该请求的源OSD在本PG的actingbackfill列表中,以及在等待列表中删除该OSD。最后检查,当收集到所有从OSD发送的ACK,就调用函数all_activated_and_committed():
该函数产生一个AllReplicasActivated()事件。
如下是此一过程的一个日志片段:
注: Replica收到主OSD发送的MOSDPGLog消息,会进入ReplicaAcitve状态,然后也会调用PG::activate()函数,从而也会调用到PG::_activate_committed()函数,在该函数里向主OSD发出ACK响应。
3.5.5 AllReplicasActivated
如下函数用于处理AllReplicasActivated事件:
当所有的replica处于activated状态时,进行如下处理:
1) 取消PG_STATE_ACTIVATING
和PG_STATE_CREATING
状态,如果该PG上acting状态的OSD数量大于等于pool的min_size,那么设置该PG为PG_STATE_ACTIVE
状态,否则设置为PG_STATE_PEERED
状态;
2) 设置pg->info.history.last_epoch_started为pg.info.last_epoch_started;
3) 调用pg->share_pg_info()函数向actingbackfill列表中的Replicas发送最新的pg_info_t信息;
4) 调用ReplicatedPG::check_local()检查本地的stray objects是否被删除;
5) 如果有读写请求在等待peering操作完成,则把该请求添加到处理队列pg->requeue_ops(pg->waiting_for_peered);
注: 在ReplicatedPG::do_request()函数中,如果发现当前PG没有peering成功,那么将会将相应的请求保存到waiting_for_peered队列中。详细请参看OSD的读写流程。
6) 调用函数ReplicatedPG::on_activate(),如果需要Recovery操作,触发DoRecovery事件;如果需要Backfill操作,触发RequestBackfill事件;否则,触发AllReplicasRecovered事件。
在on_activate()函数中,如果需要Recovery操作,触发DoRecovery事件;如果需要Backfill操作,触发RequestBackfill事件;否则,触发AllReplicasRecovered事件。之后再调用hit_set_setup()来初始化Cache Tier需要的hit_set对象,调用agent_setup()来初始化Cache Tier需要的agent对象。
如下是此阶段的一个日志片段:
3.5.6 进入Active/Activating状态
其实在Active构造函数执行完成之后,默认就进入了Activating状态,并不需要等到上面副本OSD响应MOSDPGLog请求。如下我们简单给出Activating的构造函数:
3.5.7 进入Active/WaitLocalRecoveryReserved状态
在上面ReplicatedPG::on_activate()函数中,由于PG 11.4的osd2需要恢复,因此会产生DoRecovery()事件,从而进入WaitLocalRecoveryReserved状态,如下是此一阶段的日志片段:
WaitLocalRecoveryReserved构造函数如下:
在WaitLocalRecoveryReserved构造函数中,首先设置PG的状态为Recovery wait
,之后调用AsyncReserver::request_reservation()来请求分配Recovery的资源。
注1: 每一次PG状态发生改变,都会调用pg::publish_stats_to_osd()来告知osd。
注2: 上面的日志片段中又出现了多次的handle_advance_map(),可能的原因是其他PG申请pg_temp引起的,这里对于PG 11.4仅仅只会在OSD::advance_pg()中影响pg_epoch,我们可以暂时忽略
1) 函数AsyncReserver::request_reservation()
可以看到request_reservation()仅仅是把要获取资源的请求放入队列。
2) LocalRecoveryReserved事件
当Recovery资源预约好之后,就会产生LocalRecoveryReserved事件,然后直接进入WaitRemoteRecoveryReserved状态。
3.5.8 进入Active/WaitRemoteRecoveryReserved状态
在预约好本地的Recovery资源后,还需要预约远程OSD上的Recovery资源。如下是此一阶段的一个日志片段:
如下是WaitRemoteRecoveryReserved构造函数:
构造函数中产生一个RemoteRecoveryReserved()事件。
1) RemoteRecoveryReserved事件处理
在我们进入Active状态的时候(参看Active构造函数),就把需要进行Recovery操作的添加到了Active::remote_shards_to_reserve_recovery中了。这里对于PG 11.4而言,需要向osd.2发送MRecoveryReserve::REQUEST,以获取Recovery所需要的资源。
2) 对MRecoveryReserve::REQUEST请求的处理
对于PG 11.4而言,osd2接收到MRecoveryReserve::REQUEST后,处理流程如下:
handle_pg_recovery_reserve()函数产生RequestRecovery()事件。
对于PG 11.4的osd2而言,在进入ReplicaActive状态后默认会进入其子状态RepNotRecovering,因此这里是RepNotRecovering处理RequestRecovery事件:
RepNotRecovering接收到RequestRecovery
事件直接进入RepWaitRecoveryReserved状态:
在RepWaitRecoveryReserved构造函数中调用request_reservation()预约Recovery资源,预约成功会回调QueuePeeringEvt,产生RemoteRecoveryReserved事件。
如下是对RemoteRecoveryReserved事件的处理:
上面函数中会向主OSD(对于PG 11.4而言其主OSD为osd3)发送MRecoveryReserve::GRANT,然后自己直接进入RepRecovering状态。
3) 主OSD对MRecoveryReserve::GRANT的处理
主OSD收到响应后,构造一个RemoteRecoveryReserved
事件。如下是对该事件的处理:
可以看到,这里如果所有Remote资源都预约成功,则会产生一个AllRemotesReserved事件,从而进入Recovering状态。
-
ceph存储 PG的状态机和peering过程
-
Ceph OSDMap 机制浅析