接着上一章,我们这里结合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相关的日志信息:
下面我们就针对这4种场景分别来分析。
1. PG 11.4分析
这里我们过滤osd3_watch.txt日志文件,找出所有与PG 11.4相关的日志(如下日志经过了适当的修改)。
1.1 收到e2223版本osdmap
1) osd3接收到CEPH_MSG_OSD_MAP消息
2) handle_osd_map()解析osdmap消息,并写入superblock
在handle_osd_map()函数中解析收到的MOSDMap消息,然后写入superblock中,提交成功后回调C_OnMapCommit(),并触发调用_committed_osd_maps().
3) _committed_osd_maps()
本函数用于消费被保存的osdmap,其具体实现为consume_map():
程序运行到这里,就构造了一个NullEvt到OSD的消息队列,从而触发相应的Peering流程。
1.2 进入peering流程
在上面将PG加入到OSD的peering_wq之后,OSD对应的线程池就会调用process_peering_events()函数来对相应的PG进行处理:
1.2.1 OSD::advance_pg()函数
在advance_pg()函数中,PG对每一个epoch的osdmap进行处理: 首先调用pg_to_up_acting_osds()来计算PG11.4
所对应的up set和acting set;然后调用handle_advance_map()来触发peering; 之后会调用handle_activate_map()来触发peering完成后
的一些其他工作。
1.2.2 PG::handle_advance_map()函数
这里PG11.4
对应的up set为[3], acting set也为[3],up_primary为3,acting_primary也为3。之后生成一个AdvMap
事件,投递到recovery_state
中,最终出发新的Peering过程。
1.3 peering状态机运转
1.3.1 ReplicaActive状态对AdvMap事件的处理
由于PG 11.4最开始的的up set为[0,3], acting set为[0,3],因此在active + clean状态时,该PG在OSD3上是一个replica,其所属的状态也应该是ReplicaActive
状态。下面我们来看一下,当处于ReplicaActive状态时,下时收到AdvMap事件的处理:在ReplicaActive状态下没有对AdvMap事件的直接的处理函数,因此会调用其父状态Started
状态的处理函数:
在对应的react()函数中调用should_restart_peering()来检查是否需要重新启动peering操作。下面我们来看一下该函数的实现:
这里old_acting_primary为0, new_acting_primary为3; old_acting为[0,3],new_acting为[3]; old_up_primary为0,new_up_primary为3; old_up为[0,3],new_up为[3]。参看代码,满足如何任何一个条件即可判断为new_interval:
注:关于sort bitwise,请参看sort bitwise是什么意思,下面是一个对sort bitwise作用的一个大体描述
After upgrading, users should set the ‘sortbitwise’ flag to enable the new internal object sort order: ceph osd set sortbitwise
This flag is important for the new object enumeration API and for new backends like BlueStore
因此,这里上面should_restart_peering()返回值肯定为true,因此将AdvMap
事件继续投递,进入Reset状态。由于原来PG 11.4处于RepNotRecovering状态,因此这里转入Reset状态,会首先退出子状态,打印出如下语句:
1.3.2 Reset状态对AdvMap事件的处理
在上面ReplicaActive接收到AdvMap事件,从而引发进入到Reset状态后:
在Reset状态的构造函数中,会调用set_last_peering_reset()清空上一次的peering信息。之后再调用Reset::react()来处理post_event()投递进来的AdvMap事件:
1) generate_past_intervals()函数
在Reset::react(const AdvMap&)函数中,首先调用generate_past_interval():
函数_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。
查询_calc_past_interval_range(),发现只有在如下几个地方会被调用到:
-
Reset::react(AdvMap)函数中generate_past_intervals()
-
GetInfo::GetInfo(my_context)构造函数中调用generate_past_intervals()
-
OSD::build_past_intervals_parallel()函数中(注: 本函数由OSD::load_pgs()在启动时所调用)
从上面三种情况得知,past_interval只会在OSD启动时、或者触发Peering操作时被调用。这里我们结合osd3_watch_pg_11.4.txt
日志,来看一下这里的执行过程:
由此,我们在这里计算past_interval返回false:
_calc_past_interval_range: already have past intervals back to 2222
2) start_peering_interval()函数
在收到osdmap,从而触发peering操作之前,调用start_peering_interval()函数,完成相关状态的重新设置。
-
set_last_peering_reset()用于清空上一次的peering数据;
-
init_primary_up_acting()用于设置当前新的acting set以及up set
-
check_new_interval(): 用于检查是否是一个新的interval。如果是新interval,则计算出该新的past_interval:
generate_past_intervals interval(2221-2222 up 0,3 acting 0,3): not rw, up_thru 2221 up_from 2220 last_epoch_clean 2222
在该interval[2221,2222]下,up set为[0,3],acting set为[0,3],并且up_thru为2221。而当前在epoch 2223下,up set变为[3],acting set为[3],因此对于该PG来说,OSD3的角色由1
变为0
,即由replica变为primary。
- 清除PG的相关状态。关于PG的状态是由一个
unsigned
类型的整数来表示的
这里将active
状态清除了,因此变为inactive
状态。
-
clear_primary_state(): 用于清空peering状态
-
PG 11.4所在OSD3从replica变为primary,因此这里还要处理大量因角色变化而产生的操作;
-
cancel_recovery(): 取消PG当前正在进行中的recovery动作;
到此为止,由OSD::advance_pg()函数中pg->handle_advance_map()所触发的AdvMap事件就已经处理完成。
1.3.3 进入Started状态
在完成上面的Reset状态的操作之后,接着在OSD::advance_pg()函数中通过pg->handle_activate_map()函数,从而引发进入Started状态。如下是这一过程的一个日志片段:
1) handle_activate_map()函数
这里handle_activate_map()函数构造了一个ActMap事件,并调用recovery_state.handle_event()来进行处理。
2) Reset::react(const ActMap &)函数
到此为止,我们其实还没有退出上一个步骤的Reset
阶段,因此这个对于ActMap事件的处理,是通过如下函数来进行的:
通常在osdmap发送变动,从而引发PG的acting set、up set发生变动,对于一个PG的副本OSD通常会发送一个通知消息到PG的Primary OSD。这里由于osd3已经是PG11.4的primary OSD,因此这里不需要发送通知消息。
注: 关于send_notify变量的设置,是在Initial::react(const Load&)函数中。
PG的Primary OSD会与Replica OSDs保持心跳,并且这个心跳是由Primary OSD来主动发出并维护的。这里调用pg->update_heartbeat_peers()来更新心跳信息。
最后,通过transit< Started >()直接进入Started状态
3) Started::Started()函数
进入Started状态,首先调用相应的构造函数。
1.3.4 进入Started/Start状态
Started的默认初始状态是Start
状态。这里我们来看Started/Start状态的构造函数:
进入Start状态后,根据PG所在OSD是否为Primary OSD,从而决定是进入Started/Primary状态,还是Started/Stray状态。这里对于PG 11.4而言,在当前epoch 2223时osd3是Primary OSD,因此进入的是Primary状态。
1.3.5 进入Started/Primary状态
如下是Primary状态的构造函数:
这里代码较为简单,不做介绍。
1.3.6 进入Started/Primary/Peering状态
Primary的默认初始子状态为Peering状态,因此这里直接进入Peering状态:
进入Peering状态后,就将PG的状态通过pg->state_set()设置为了PEERING了。
1.3.7 进入Started/Primary/Peering/GetInfo状态
Peering的默认初始子状态为GetInfo状态,因此这里直接进入GetInfo状态:
进入GetInfo状态后,首先调用generate_past_intervals()计算past_intervals。这里我们在前面Reset阶段就已经计算出了新的past_interval为[2221,2222]。
1) build_prior()函数
关于PriorSet,我们这里不做介绍,后面我们会再开辟专门的章节来进行说明(参见up_thru)。这里我们给出针对PG 11.4所构建的PriorSet的结果:
当前osdmap的epoch为e2223,对于osd3来说其上一次申请的up_thru为e2221,而当前的same_interval_since为e2223,因此这里need_up_thru需要设置为true。
2) GetInfo::get_infos()
这里由上面构造的PriorSet为:
GetInfo()是由PG的主OSD向从OSD发送pg_query_t::INFO消息以获取从OSD的pg_into_t信息。这里对于PG11.4而言,因为目前其prior_set->probe为[3],因此这里这里在for循环中第一个continue就已经退出了。但在这里我们还是讲述一下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()流程执行完毕。
1.3.8 进入Started/Primary/Peering/GetLog状态
对于我们当前PG11.4
而言,由于其不存在副本OSD了,因此其并没有走完整个GetInfo(),而是直接在GetInfo()构造函数中构造了一个GotInfo()事件,就直接进入GetLog()状态:
因为Peering完成之后,我们是想通过接受一个Activate
事件,从而进入Active状态(注: 进入Active状态,就意味着我们可以重新进行数据的读写操作了),因此这里我们搜索Activate
关键字,看在哪些情况下会产生该事件:
这里我们先不理会在Stray状态下产生Activate事件,从而进入ReplicaActive状态的情形,我们主要关注在Peering状态下接收到Activate事件,从而进入Active状态的情形。因此在当前GetLog()状态下,结合状态转换图,我们主要需要了解如何先进入GetMissing状态以及WaitUpThru状态。
注:GetMissing、WaitUpThru状态接收到Activate事件,都是会调用其父状态Peering对Activate事件的处理。在Peering中,接收到Activate事件,是会直接进入到Active状态的。
如下我们再来看GetLog构造函数:
这里结合osd3_watch_pg11.4.txt
日志类分析一下:
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的过程是一样的:
接下来我们再来看看其中的几个函数的实现:
1.3.8.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需要的列表
5) 下面是对PG做一些检查:
a) 计算num_want_acting数量,检查如果小于min_size,进行如下操作:
b) 调用IsPGRecoverablePredicate来判断PG现有的OSD列表是否可以恢复,如果不能恢复,清空want_acting,返回false值
6) 检查如果want不等于acting,设置want_acting为want:
a) 如果want_acting等于up,申请empty为pg_temp的OSD列表;
b) 否则申请want为pg_temp的OSD列表;
7) 最后设置PG的actingbackfill为want_acting_backfill,设置backfill_targets为want_backfill,并检查backfill_targets里的pg_shard应该不在stray_set里面;
8) 最终返回true;
注: PG的acting set的设置主要会是在如下几个地方
1) OSD::load_pgs()函数中进行初始化时设置acting set
2) OSD::advance_pg()函数中,通过调用pg->handle_advance_map()产生AdvMap事件,从而引发PG进入Reset,在Reset的react(AdvMap)函数中start_peering_interval(),然后接着调用init_primary_up_acting()触发
3) OSD::handle_pg_peering_evt()函数中恢复一个正在删除的PG(do we need to resurrect a deleting pg?)
4) PG分裂
关于PG中的acting set、up set、want等我们可以这样理解: acting set是根据osdmap pg_temp的存在情况来确定的,反应的是当前阶段所希望达成的状态; want是当前实际能够工作的一个排列;up set初始是根据osdmap算出来的一个排列,但是假如当前acting不能满足需求的情况下,就会将up set设置为want。
下面举例说明需要申请pg_temp的场景:
1) 当前PG1.0
,其acting列表和up列表都为[0,1,2],PG处于clean状态;
2) 此时,OSD0崩溃,导致该PG经过CRUSH算法重新获得acting和up列表都为[3, 1, 2];
3) 选择出拥有权威日志的OSD为1,经过PG::calc_replicated_acting()算法,want列表为[1, 3, 2],acting_backfill为[1,3,2],want_backfill为[3]。特别注意want列表第一个为主OSD,如果up_primary无法恢复,就选择权威日志的OSD为主OSD;
4) want[1,3,2]不等于acting[3,1,2],并且不等于up[3,1,2],需要向monitor申请pg_temp为want;
5) 申请成功pg_temp以后,acting为[3,1,2],up为[1,3,2],osd1作为临时的主OSD,处理读写请求。当PG恢复到clean状态时,pg_temp取消,acting和up都恢复为[3,1,2]。
1.3.8.2 PG::find_best_info()
函数find_best_info()用于选取一个拥有权威日志的OSD。根据last_epoch_clean到目前为止,各个past_interval期间参与该PG的所有目前还处于up状态的OSD上的pg_info_t
信息,来选取一个拥有权威日志的OSD,选择的优先顺序如下:
1) 具有最新的last_update的OSD;
2) 如果条件1相同,选择日志更长的OSD;
3) 如果1,2条件相同,选择当前的主OSD;
1) 首先在所有OSD中计算max_last_epoch_started,然后在拥有最大的last_epoch_started的OSD中计算min_last_update_acceptable的值;
2) 如果min_last_update_acceptable为eversion_t::max(),返回infos.end(),选取失败;
3) 根据以下条件选择一个OSD:
a) 首先过滤掉last_update小于min_last_update_acceptable,或者last_epoch_started小于max_last_epoch_started_found,或者incomplete的OSD;
b) 如果PG类型是EC,选择最小的last_update;如果PG类型是副本,选择最大的last_update的OSD;
c) 如果上述条件相同,选择log_tail最小的,也就是日志最长的OSD;
d) 如果上述条件都相同,选择当前的主OSD;
综上的选择过程可知,拥有权威日志的OSD特征如下:必须是非incomplete的OSD;必须有最大的last_epoch_started;last_update有可能是最大,但至少是min_last_update_acceptable,有可能是日志最长的OSD,有可能是主OSD。
1.3.8.3 PG::calc_replicated_acting
本函数用于计算本PG相关的OSD列表:
-
want_primary: 主OSD,如果它不是up_primary,就需要申请pg_temp;
-
backfill: 需要进行Backfill操作的OSD;
-
acting_backfill: 所有进行acting和Backfill的OSD的集合;
-
want和acting_backfill的OSD相同,前者类型是pg_shard_t,后者是int类型;
具体处理过程如下:
1) 首先选择want_primary:
a) 如果up_primary处于非incomplete状态,并且last_update大于等于权威日志的log_tail,说明up_primary的日志和权威日志有重叠,可通过日志记录恢复,优先选择up_primary为主OSD;
b) 否则选择auth_log_shard,也就是拥有权威日志的OSD为主OSD;
c) 把主OSD加入到want和acting_backfill列表中;
2) 函数的输入参数size为要选择的副本数,依次从up、acting、all_info里选择size个副本OSD:
a) 如果该OSD上的PG处于incomplete的状态,或者cur_info.last_update小于主OSD和auth_log_shard的最小值,则该PG副本无法通过日志修复,只能通过Backfill操作来进行修复。把该OSD分别加入backfill和acting_backfill集合中;
b) 否则就可以根据PG日志来恢复,只加入acting_backfill集合和want列表中,不用加入到Backfill列表中;
1.3.8.3 GetLog阶段日志分析
下面我们就结合PG 11.4
在这一阶段的日志来进行分析:
在上面3593行开始进入GetLog阶段,之后在PG::choose_acting()中打印出获取到的所有pg_info信息(此时只有osd.3上具有PG 11.4的信息了),对于PG 11.4的主OSD3来说Peering尚未完成,因此其pg_info.last_epoch_started为e2222(当前osdmap的epoch为e2223),pg_info.history.last_epoch_started的值也为e2222。
注: 上面les是last_epoch_started的缩写, c是last_epoch_clean的缩写, f是last_epoch_marked_full的缩写
在PG::calc_replicated_acting()函数中计算出来的actingbackfill值为[3],want值也为[3]。此外,由于acting的值也为[3](注: 在osdmap发生变化时已经计算过了),因此want等于acting,不必申请pg_temp。
1.3.9 收到缺失的权威日志
如果主OSD不是拥有权威日志的OSD,就需要去拥有权威日志的OSD上拉取权威日志。当收到权威日志后的处理流程如下:
上面将logevt.msg赋值给msg(注:msg的类型为MLogRec)并抛出GotLog()事件。本函数就用于处理该事件。它首先确认是从auth_log_shard端发送的消息,然后抛出GotLog()事件:
本函数捕获GotLog()事件,处理过程如下:
1) 如果msg不为空,就调用函数PG::proc_master_log()合并自己缺失的权威日志,并更新自己的pg_info相关的信息。从此,作为主OSD,也是拥有权威日志的OSD。
2) 调用函数pg->start_flush()添加一个空操作;
3) 状态转移到GetMissing状态;
经过GetLog阶段的处理后,该PG的主OSD已经获取了权威日志,以及pg_info的权威信息。
1.3.10 Started/Primary/Peering/GetMissing状态
在GetLog()状态中,如果Primary获取到了权威日志,在抛出的GotLog事件中调用PG::proc_master_log()处理了权威日志后,就会直接转换进入到GetMissing状态。
GetMissing的处理过程为: 首先,拉取各个从OSD上的有效日志。其次,用主OSD上的权威日志与各个从OSD上的日志进行对比,从而计算出各个从OSD上不一致的对象并保存在对应的pg_missing_t结构中,作为后续数据修复的依据。
主OSD的不一致的对象信息,已经在调用PG::proc_master_log()合并权威日志的过程中计算出来,所以这里只计算从OSD上不一致对象。
1.3.10.1 拉取从副本上的日志
在GetMissing()的构造函数里,通过对比主OSD上的权威pg_info信息,来获取从OSD上的日志信息:
上述代码具体处理过程为遍历pg->actingbackfill的OSD列表,然后做如下的处理:
1) 不需要获取PG日志的情况:
a) pi.is_empty()为true,没有任何信息,需要Backfill过程来修复,不需要获取日志;
b) pi.last_update小于pg->pg_log.get_tail(),该OSD的pg_info记录中,last_update小于权威日志的尾部,该OSD的日志和权威日志不重叠,该OSD操作已经远远落后于权威OSD,已经无法根据日志来修复,需要继续Backfill的过程;
c) pi.last_backfill为hobject_t(),说明在past_interval期间,该OSD标记需要Backfill操作,实际并没开始Backfill的工作,需要继续Backfill过程;
d) pi.last_update等于pi.last_complete,说明该PG没有丢失的对象,已经完成Recovery操作阶段,并且pi.last_update等于pg->info.last_update,说明日志和权威日志的最后更新一致,说明该PG数据完整,不需要恢复。
2) 获取日志的情况:当pi.last_update大于等于pg->info.log_tail,说明该OSD的日志记录和权威日志记录重叠,可以通过日志来修复。变量since是从last_epoch_started开始的版本值:
a) 如果该PG的日志记录pi.log_tail小于等于版本值since,那就发送消息pg_query_t::LOG,从since开始获取日志记录;
b) 如果该PG的日志记录pi.log_tail大于版本值since,就发送消息pg_query_t::FULLLOG来获取该OSD的全部日志记录。
3) 最后检查如果peer_missing_requested为空,说明所有获取日志的请求返回并处理完成。如果需要pg->need_up_thru,抛出post_event(NeedUpThru());否则,直接调用post_event(Activate(pg->get_osdmap()->get_epoch()))进入Activate
状态。
下面举例说明获取日志的两种情况:
当前last_epoch_started的值为10, since是last_epoch_started后的首个日志版本值。当前需要恢复的有效日志是经过since操作之后的日志,之前的日志已经没有了。
对应OSD0,其日志log_tail大于since,全部拷贝osd0上的日志;对应osd1,其日志log_tail小于since,只拷贝从since开始的日志记录。
1.3.10.2 收到从副本上的日志记录处理
当一个PG的主OSD收到从OSD返回的获取日志ACK应答后,就把该消息封装成MLogRec事件。状态GetMissing接收到该事件后,在下列事件函数里处理该事件:
具体处理过程如下:
1) 调用PG::proc_replica_log()处理日志。通过日志的对比,获取该OSD上处于missing状态的对象列表;
2) 如果peering_missing_requested为空,即所有的获取日志请求返回并处理。如果需要pg->need_up_thru,抛出NeedUpThru()事件;否则,直接调用函数post_event(Activate(pg->get_osdmap()->get_epoch()))进入Activate
状态。
函数PG::proc_replica_log()处理各个从OSD上发过来的日志。它通过比较该OSD的日志和本地权威日志,来计算OSD上处于missing状态的对象列表。具体处理过程调用pg_log.proc_replica_log()来处理日志,输出为omissing,也就是OSD缺失的对象。
1.4 Started/Primary/Peering/WaitUpThru状态
在前面Started/Primary/Peering/GetInfo状态时,我们通过调用PG::build_prior()已经判断出PG 11.4
的need_up_thru值为true,因此会进入WaitUpThru状态:
之后获取到相应的UpThru之后,就会通过如下退出WaitUpThru状态:
1.5 Active操作
由上可知,如果GetMissing()处理成功,就产生Activate()事件,该事件对于Primary OSD来说会跳转到Active状态:
其实到了本阶段为止,可以说peering主要工作已经完成,主OSD已经进入了Active状态。但还需要后续的处理,激活各个副本,如下所示:
状态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],因此显示的状态为active+undersized+degraded。
注: 进入Active状态后,默认进入其初始子状态Started/Primary/Active/Activating
1.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。
1.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的相关分析;
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
状态;
1.5.3 收到从OSD的MOSDPGLog的应答
当收到从OSD发送的对MOSDPGLog的ACK消息后,触发MInfoRec事件,下面这个函数处理该事件:
处理过程比较简单:检查该请求的源OSD在本PG的actingbackfill列表中,以及在等待列表中删除该OSD。最后检查,当收集到所有从OSD发送的ACK,就调用函数all_activated_and_committed():
该函数产生一个AllReplicasActivated()事件。
对应主OSD在事务的回调函数C_PG_ActivateCommitted()里实现,最终调用PG::_activate_committed()加入peer_activated集合里:
注: Replica收到主OSD发送的MOSDPGLog消息,会进入ReplicaAcitve状态,然后也会调用PG::activate()函数,从而也会调用到PG::_activate_committed()函数,在该函数里向主OSD发出ACK响应。
1.5.4 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->share_pg_info()函数向actingbackfill列表中的Replicas发送最新的pg_info_t信息;
3) 调用ReplicatedPG::check_local()检查本地的stray objects是否被删除;
4) 如果有读写请求在等待peering操作完成,则把该请求添加到处理队列pg->requeue_ops(pg->waiting_for_peered);
5) 调用函数ReplicatedPG::on_activate(),如果需要Recovery操作,触发DoRecovery事件;如果需要Backfill操作,触发RequestBackfill事件;否则,触发AllReplicasRecovered事件。
6) 初始化Cache Tier需要的hit_set对象;
7) 初始化Cache Tier需要的agent对象;
注: 在ReplicatedPG::do_request()函数中,如果发现当前PG没有peering成功,那么将会将相应的请求保存到waiting_for_peered队列中。详细请参看OSD的读写流程。
在本例子PG 11.4中,我们不需要进行Recovery操作,也不需要进行Backfill操作,因此触发AllReplicasRecovered事件进入Recovered
状态:
参看如下代码:
进入Started/Primary/Active/Recovered状态后,调用OSDMap::get_pg_size()来计算PG的副本个数:
从上面可以看到其实是计算该PG所在pool的副本数。在这里PG 11.4所在的pool的副本数为2,而当前pg->actingbackfill.size()为1,因此仍处于DEGRADED
状态。
最后判断all_replicas_activated变量,如果所有副本均已激活,那么进入Clean状态。
1.6 副本端的状态转移
当创建PG后,根据不同的角色,如果是主OSD,PG对应的状态机就进入Primary状态; 如果不是主OSD,就进入Stray状态。
1.6.1 Stray状态
Stray状态有两种情况:
情况1: 只接收到PGINFO的处理
从PG接收到主PG发送的MInfoRec
事件,也就是接收到主OSD发送的pg_info信息。其判断如果当前pg->info.last_update大于infoevt.info.last_update,说明当前的日志有divergent
的日志,调用函数PG::rewind_divergent_log()清理日志即可。最后抛出Activate(infoevt.info.last_epoch_started)事件,进入ReplicaActive状态。
情况2: 接收到MOSDPGLog消息
当从PG接收到MLogRec事件,就对应着接收到主PG发送的MOSDPGLog消息,其通知PG处于activate状态,具体处理过程如下:
1) 如果msg->info.last_backfill为hobject_t(),需要Backfill操作的OSD;
2) 否则就是需要Recovery操作的OSD,调用merge_log()把主OSD发送过来的日志合并
抛出Activate(logevt.msg->info.last_eopch_started)事件,使副本转移到ReplicaActive状态
1.6.2 ReplicaActive状态
ReplicaActive状态如下:
当处于ReplicaActive状态,接收到Activate事件,就调用PG::activate()函数。在函数PG::_activate_committed()中给主PG发送应答信息,告诉主OSD自己处于activate状态,设置PG为activate状态。
4. 状态机异常处理
在上面的流程介绍中,只介绍了正常状态机的转换流程。Ceph之所以用状态机来实现PG的状态转移,就是可以实现任何异常情况下的处理。下面介绍当OSD失效时,导致相关的PG重新Peering的机制。
当一个OSD失效,Monitor会通过heartbeat检测到,导致osdmap发生了变化,Monitor会把最新的osdmap推送给OSD,导致OSD上受影响PG重新进行Peering操作。
具体流程如下:
1) 在函数OSD::handle_osd_map()处理osdmap的变化,该函数调用consume_map(),对每一个PG调用pg->queue_null(),把PG加入到peering_wq中;
2) peering_wq的处理函数process_peering_events()调用OSD::advance_pg()函数,在该函数里调用PG::handle_advance_map()给PG的状态机RecoveryMachine发送AdvMap事件:
当处于Started状态,接收到AdvMap事件,调用函数pg->should_restart_peering()检查,如果是new_interval,就跳转到Reset状态,重新开始一次Peering过程。
- ceph存储 PG的状态机和peering过程