在前面的章节中,我们从OSD接收到新的OSDMap开始讲起,然后讲述到其会向状态机投递两个事件:
-
AdvMap事件(advance map)
-
ActMap事件(activate map)
本章承接上文,从这两个事件入手,引出本文重点: PG Peering。但是在这里我还是想要先给出一张整体架构图,以更好的理解Peering流程。
1. PG状态机接收到AdvMap事件
我们先来看看AdvMap事件数据结构:
各字段含义如下:
-
osdmap: 当前PG advance到的osdmap
-
lastmap: 当前PG上一版的osdmap
-
newup: AdvMap::osdmap版本下的up set
-
newacting: AdvMap.osdmap版本下的acting set
-
up_primary: AdvMap.osdmap版本下的up primary
-
acting_primary: AdvMap.osdmap版本下的acting primary
我们在代码中搜索AdvMap,发现在如下状态下都会接受AdvMap事件:
-
Started状态
-
Reset状态
-
Peering状态
-
Active状态
-
GetLog状态
-
WaitActingChange状态
-
InComplete状态
查看对应的代码,发现大部分都是先判断是否要重新启动Peering,如果要,则跳转到Reset状态,然后继续处理AdvMap事件。
1.1 Active状态下对AdvMap事件的处理
这里我们假设集群的初始状态为active+clean
,因此这里收到AdvMap
事件后是调用如下函数来进行处理:
下面我们来看具体实现步骤:
1) 检查pool size是否发生改变
通常情况下OSDMap的变化不会导致pool的OSD size发生变化,因为一个pool的OSD个数一般是在设置rule时就已经确定了。因此,在这里如果前后两个OSDMap中,PG所在pool的osd size发生改变,则很有可能是通过执行如下命令进行了更改:
# ceph osd pool set {pool-name} size 2
因此如果pg->get_osdmap()->get_pg_size(pg->info.pgid.pgid)小于等于pg->actingset.size(),说明当前已经对pool的osd size进行了降低,因此会先清除PG_STATE_UNDERSIZED
标识;否则说明当前已经对pool的osd size进行了增加,因此需要将PG状态设置为undersized + degraded
。
2)将AdvMap事件继续往父状态机投递
在函数的最后我们看到通过forward_event()将事件继续往父状态机投递:
Active状态的父状态为Primary,而Primary的父状态为Started。因此这里会投递到Started状态来进行处理。
1.2 Started处理Active抛上来的AdvMap事件
现在我们来看Started状态机是如何处理Active抛上来的AdvMap事件:
1) 检查OSDMap及pool是否由not full
转变为full
代码实现较为简单,这里不做过多说明。
2) 检查是否需要重新发起新的peering
这里我们看到,只要产生了一个新的interval,PG::should_restart_peering()就会返回true,否则返回false。
这里因为我们要探讨PG的Peering流程,因此我们假设PG的一个副本OSD由up状态
转变为down状态
,从而会产生一个新的interval,因此会转到Reset状态。
1.3 Reset状态对AdvMap事件的处理
接着上面的假设,PG产生了新的interval,因此会转到Reset状态,并向其投递AdvMap事件:
我们简单分析一下实现流程:
1)调用PG::generate_past_intervals()来产生past_intervals,如果后续需要进行peering操作,那么就可以利用past_intervals的相关信息
2)检查是否需要重新发起新的peering
我们仍然按照上面的假设,PG会进行新的Peering操作,因此会调用到PG::start_peering_interval()
1.4 启动一个peering interval
此函数会在初始化一个peering之前被调用:
PG::start_peering_interval()处理主流程如下图所示:
注:如果要进行Peering,那么PG的所有副本(主副本以及从副本)都会调用到此函数
1.4.1 进行Peering重置
因为这里我们要开启一个新的Peering流程,因此需要把前面未完成的Peering进行重置:
如上图所示,假设我们当前是属于第二次Peering
阶段。如果该PG还有一些FileStore上的事务未处理完成,则会创建一个BufferedRecoveryMessages
,然后将本次Peering所产生的RecoveryMessage都放入该Buffer中。等到FileStore中的事务处理完成就会回调一个Context,然后向OSD::peering_wq中投递一个IntervalFlush事件,这时就会将上一次所缓冲的RecoveryMessage发送出去。
注:之所以要把RecoveryMessages放到Buffer中缓存起来,猜测可能的原因是FileStore上的事务可能会产生相关的回调,从而将破坏PG本次Peering的状态机。
1.4.2 初始化新的up、acting信息,并更新pginfo信息
我们在OSD::advance_pg()中调用OSD::pg_to_up_acting_osds()算出了最新的up set、up_primary、acting set、primary,但是还没有真正设置到PG。在这里PG::start_peering_interval()中调用init_primary_up_acting(),这时就将这些真正设置到PG中。
接着更新PG的状态信息(info.stats)。之后根据up set与acting set是否相同,来设置PG的REMAPPED
标识(注:一般出现pg_temp时会导致up set与acting set不一致)。
1.4.3 处理PG角色变化相关问题
我们来看实现过程:
1) 调用pg_interval_t::check_new_interval()检查是否产生了一个新的interval,如果是则更新info.history.same_interval_since。
2) 如果old_up_primary不等于当前的up_primary,或者oldup不等于当前的up set,那么更新info.history.same_up_since
3) 如果old_acting_primary不等于当前的Primary,那么更新info.history.same_primary_since
4) 清除PG的相关状态标志
需要说明的是,因为这里我们会停止Recovery操作,因此将PG_STATE_RECOVERING
标志清除。
5) 根据PG当前副本role的变化情况,进行以下处理
首先我们先来看看role的计算:
总结:
到此为止,OSD::advance_pg()中调用pg->handle_advance_map()所产生的AdvMap事件就已经处理完成。
仔细观察上面的流程,PG Replicas也是通过Reset::react(AdvMap)进入到PG::start_peering_interval()阶段。
2. PG状态机接收到ActMap事件
在OSD::advance_pg()中,调用pg->handle_advance_map()产生AdvMap事件并处理完成之后,接着又会调用pg->handle_activate_map()产生ActMap事件:
我们看到其向recovery_state投递ActMap
事件。
我们搜索ActMap
,发现在如下状态下会接受ActMap事件:
-
Reset状态
-
Primary状态
-
Active状态
-
ReplicaActive状态
-
Stray状态
-
WaitUpThru状态
在我们前面的假设中,PG开始是处于active + clean
状态,后面经过AdvMap
事件进入到Reset状态。下面我们就从Reset状态下对ActMap
事件的处理开始讲起。
2.1 Reset状态下对ActMap事件的处理
1)非Primary PG
向Primary PG
发送pg_notify_t请求
如果PG::should_send_notify()为true,且我们知道PG当前的主OSD是什么,就会调用RecoveryMachine::send_notify()来向主OSD发送pg_notify_t消息。
在PG::start_peering_interval()函数中:
从上面我们看到,对于非PG Primary(包括PG Replicas、PG Strays),其都会将send_notify置为true。
2)PG的状态由Reset状态转到Started状态
2.2 进入Started状态
上面看到,Started的默认初始子状态为Start
状态,因此这里会马上进入Start状态。
2.3 进入Start状态
从上面我们看到,对于PG Primary来说产生MakePrimary
事件,进入Primary
状态;否则产生MakeStray
事件,进入Stray
状态。
到此,PG对相应事件的处理就明显分为不同的路径:
这里因为PG Primary主导整个Peering过程,因此我们先从Primary路径开始讲起。对于Stray路径,我们后面会讲解到。
2.4 进入Primary状态
Primary的默认初始子状态为Peering
,因此我们这里会马上进入到Peering状态。
2.5 进入Peering状态
Peering的默认初始子状态为GetInfo状态,因此这里会马上进入GetInfo
状态。
3. Peering状态详细处理
我们接着上面,对于PG Primary进入Peering状态后,马上进入Peering的默认初始子状态GetInfo。下图是Peering过程的一个整体状态转换图:
现在我们从GetInfo
状态开始讲起。
3.1 GetInfo状态
在GetInfo构造函数中,首先调用PG::generate_past_intervals()产生past_intervals,之后调用PG::build_prior()来生成Recovery过程中所需要依赖的OSD列表。
之后调用get_infos()来获取PG各副本的pg info信息。我们来看该函数实现。
3.1.1 获取PG各副本pg info信息
1)清理PG::blocked_by
PG::blocked_by用于记录当前PG被哪些OSD所阻塞,这样在执行pg stats时我们就知道这些阻塞信息。一般在一个状态退出,就会将其进行清理。比如在GetInfo状态退出时,就有如下:
2) 向相应的Peer发送pg_query_t查询请求
我们在GetInfo构造函数中创建了PriorSet,其中就告诉了我们需要向哪些OSD发送查询信息。这里遍历PriorSet,向当前仍处于Up状态
的OSD发送pg_query_t
查询请求。
3.1.2 重要提醒
到目前为止,在OSD::advance_pg()中所触发的AdvMap以及ActMap事件就已经处理完成。接下来就是接受Peer返回过来的pg info信息了。
3.1.3 接收Peer返回过来的pg info信息
这里我们先大概给出一张PG Primary发送pg_query_t查询请求,然后Peer返回响应的一个大体流程图:
MNotifyRec数据结构
这里我们先来看一下查询pg_info的响应的数据结构:
GetInfo状态下对MNotifyRec事件的处理
因为我们实在GetInfo状态下发送的pg_info查询请求,假设没有新的Peering事件触发状态改变的话,我们会使用GetInfo::react(MNotifyRec)函数来处理。在讲解该函数之前,我们先来看一下PG::proc_replica_info()函数:
1) 首先检查如果该OSD的pg_info信息已经存在,并且last_update参数相同,则说明已经处理过,返回false。
2) 调用函数OSDMap::has_been_up_since()检查该OSD在send_epoch时已经处于up状态
3) 确保自己是主OSD,把从from返回来的pg_info信息保存到peer_info数组,并加入might_have_unfound数组里
注:PG::might_have_unfound数组里的OSD可能存放了PG的一些对象,我们后续需要利用这些OSD来进行数据恢复
4) 调用函数unreg_next_scrub()使该PG不在scrub操作的队列里
5) 调用info.history.merge()函数处理副本OSD
发送过来的pg_info信息。处理的方法是:更新为最新的字段。
6) 调用函数reg_next_scrub()注册PG下一次scrub的时间
7) 如果该OSD既不在up数组中也不在acting数组中,那就加入stray_set列表中。当PG处于clean状态时,就会调用purge_strays()函数删除stray状态的PG及其上的对象数据。
8) 如果是一个新的OSD,就调用函数update_heartbeat_peers()更新需要heartbeat的OSD列表
接下来我们看GetInfo状态下对MNotifyRec
事件的处理:
下面我们来看具体的处理过程:
1)首先从peer_info_requested里删除该peer,同时从blocked_by队列里删除
2)调用函数PG::proc_replica_info()来处理副本的pg_info信息。如果获取到了一个新的有效的pg_info,则PG::proc_replica_info()返回true,继续下面的步骤3);否则丢弃该事件
3)在变量old_start里保存了调用PG::proc_replica_info()前主OSD的pg->info.history.last_epoch_started,如果该epoch值小于合并后的值,说明该值被更新了,副本OSD上的epoch值比较新,需要进行如下操作:
a) 调用PG::build_prior()重新构建prior_set对象
b) 从peer_info_requested队列中去掉上次构建的prior_set中存在的OSD,这里最新构建上次不存在的OSD列表;
c) 调用get_infos()函数重新发送查询peer_info请求
4)调用pg->apply_peer_features()更新相关的features值
5) 当peer_info_requested队列为空,并且prior_set不处于pg_down状态时,说明收到所有OSD的peer_info并处理完成
6)之后检查past_interval阶段至少有一个OSD处于up状态且非incomplete
状态;否则该PG无法恢复,标记状态为PG_STATE_DOWN
并直接返回。如下图所示:
7)最后完成处理,调用函数post_event(GotInfo())抛出GotInfo事件进入状态机的下一个状态。
在GetInfo状态里直接定义了当前状态接收到GotInfo事件后,直接跳转到下一个状态GetLog里:
[参看]
-
ceph博客
-
ceph官网
-
PEERING
-
分布式系统