建设银行武威分行网站花钱推广的网络平台
本章介绍ceph中比较复杂的模块:
Peering机制。该过程保障PG内各个副本之间数据的一致性,并实现PG的各种状态的维护和转换。本章首先介绍boost库的statechart状态机基本知识,Ceph使用它来管理PG的状态转换。其次介绍PG的创建过程以及相应的状态机创建和初始化。然后详细介绍peering机制三个具体的实现阶段:GetInfo、GetLog、GetMissing。
statechart状态机
 1.1 状态
 1.2 事件
 1.3 状态机的响应
 1.4 状态机的定义
 1.5 context函数
 1.6 事件的特殊处理
 1.7 PG状态机
 1.8 PG状态机的总体状态转换图
 1.9 OSD启动加载PG状态机转换
 1.10 PG创建后状态机的状态转换
 1.11 PG在触发Peering过程时机
 1. statechart状态机
 
 Ceph在处理PG的状态转换时,使用了boost库提供的statechart状态机。因此先简单介绍一下statechart状态机的基本概念和涉及的相关知识,以便更好地理解Peering过程PG的状态机转换流程。下面例举时截取了PG状态机的部分代码。
1.1 状态
没有子状态情况下的状态定义
 在statechart里,一个状态的定义方式有两种:
-  
struct Reset : boost::statechart::state< Reset, RecoveryMachine >, NamedState { -  
... -  
}; 
这里定义了状态Reset,它需要继承boost::statechart::state类。该类的模板参数中,第一个参数为状态自己的名字Reset,第二个参数为该状态所属状态机的名字,表明Reset是状态机RecoveryMachine的一个状态。
有子状态情况下的状态定义
-  
struct Start; -  
struct Started : boost::statechart::state< Started, RecoveryMachine, Start >, NamedState { -  
... -  
} -  
struct Start : boost::statechart::state< Start, Started >, NamedState { -  
}; 
状态Started也是状态机RecoveryMachine的一个状态,模板参数中多了一个参数Start,它是状态Started的默认初始子状态。
 这里定义的Start是状态Started的子状态。第一个模板参数是自己的名字,第二个模板参数是该子状态所属父状态的名字。
综上所述,一个状态,要么属于一个状态机,要么属于一个状态,成为该状态的子状态。其定义的模板参数是自己,第二个参数是拥有者,第三个参数是它的起始子状态。
1.2 事件
状态能够接收并处理事件。事件可以改变状态,促使状态发生转移。在boost库的statechart状态机中定义事件的方式如下所示:
-  
struct QueryState : boost::statechart::event< QueryState > { -  
Formatter *f; -  
explicit QueryState(Formatter *f) : f(f) {} -  
void print(std::ostream *out) const { -  
*out << "Query"; -  
} -  
}; -  
}; 
QueryState为一个事件,需要继承boost::statechart::event类,模板参数为自己的名字。
1.3 状态机的响应
在一个状态内部,需要定义状态机处于当前状态时,可以接受的事件以及如何处理这些事件的方法:
-  
#define TrivialEvent(T) struct T : boost::statechart::event< T > { \ -  
T() : boost::statechart::event< T >() {} \ -  
void print(std::ostream *out) const { \ -  
*out << #T; \ -  
} \ -  
}; -  
TrivialEvent(Initialize) -  
TrivialEvent(Load) -  
TrivialEvent(GotInfo) -  
TrivialEvent(NeedUpThru) -  
TrivialEvent(NullEvt) -  
TrivialEvent(FlushedEvt) -  
TrivialEvent(Backfilled) -  
TrivialEvent(LocalBackfillReserved) -  
TrivialEvent(RemoteBackfillReserved) -  
TrivialEvent(RejectRemoteReservation) -  
TrivialEvent(RemoteReservationRejected) -  
TrivialEvent(RemoteReservationCanceled) -  
TrivialEvent(RequestBackfill) -  
TrivialEvent(RequestRecovery) -  
TrivialEvent(RecoveryDone) -  
TrivialEvent(BackfillTooFull) -  
TrivialEvent(RecoveryTooFull) -  
TrivialEvent(MakePrimary) -  
TrivialEvent(MakeStray) -  
TrivialEvent(NeedActingChange) -  
TrivialEvent(IsIncomplete) -  
TrivialEvent(IsDown) -  
TrivialEvent(AllReplicasRecovered) -  
TrivialEvent(DoRecovery) -  
TrivialEvent(LocalRecoveryReserved) -  
TrivialEvent(RemoteRecoveryReserved) -  
TrivialEvent(AllRemotesReserved) -  
TrivialEvent(AllBackfillsReserved) -  
TrivialEvent(GoClean) -  
TrivialEvent(AllReplicasActivated) -  
TrivialEvent(IntervalFlush) 
-  
struct Initial : boost::statechart::state< Initial, RecoveryMachine >, NamedState { -  
explicit Initial(my_context ctx); -  
void exit(); -  
typedef boost::mpl::list < -  
boost::statechart::transition< Initialize, Reset >, -  
boost::statechart::custom_reaction< Load >, -  
boost::statechart::custom_reaction< NullEvt >, -  
boost::statechart::transition< boost::statechart::event_base, Crashed > -  
> reactions; -  
boost::statechart::result react(const Load&); -  
boost::statechart::result react(const MNotifyRec&); -  
boost::statechart::result react(const MInfoRec&); -  
boost::statechart::result react(const MLogRec&); -  
boost::statechart::result react(const boost::statechart::event_base&) { -  
return discard_event(); -  
} -  
}; 
状态机的7种事件处理方法

上述代码列出了状态RecoveryMachine/Initial可以处理的事件列表和处理对应事件的方法:
1) 通过boost::mpl::list定义该状态可以处理多个事件类型。本例中可以处理Initialize、Load、NullEvt和event_base事件。
2) 简单事件处理
boost::statechart::transition< Initialize, Reset > 
定义了状态Initial接收到事件Initialize后,无条件直接跳转到Reset状态;
3) 用户自定义事件处理: 当接收到事件后,需要根据一些条件来决定状态如何转移,这个逻辑需要用户自己定义实现
boost::statechart::custom_reaction< Load > 
custom_reaction 定义了一个用户自定义的事件处理方法,必须有一个react()的处理函数处理对应该事件。状态转移的逻辑需要用户自己在react函数里实现:
boost::statechart::result react(const Load&); 
 4)NullEvt事件用户自定义处理,但是没有实现react()函数来处理,最终事件匹配了boost::statechart::event_base事件,直接调用函数discard_event把事件丢弃掉。
-  
boost::statechart::custom_reaction< NullEvt > -  
 -  
boost::statechart::result react(const boost::statechart::event_base&) { -  
return discard_event(); -  
} 
1.4 状态机的定义
 RecoveryMachine为定义的状态机,需要继承boost::statechart::state_machine类:
-  
struct Initial; -  
class RecoveryMachine : public boost::statechart::state_machine< RecoveryMachine, Initial > { -  
RecoveryState *state; -  
public: -  
PG *pg; -  
} 
模板参数第一个参数为自己的名字,第二个参数为状态机默认的初始状态Initial。
状态机的基本操作有两个:
-  
RecoveryMachine machine; -  
PG *pg; -  
explicit RecoveryState(PG *pg) -  
: machine(this, pg), pg(pg), orig_ctx(0) { -  
machine.initiate();//a--- -  
} -  
void handle_event(const boost::statechart::event_base &evt, -  
RecoveryCtx *rctx) { -  
start_handle(rctx); -  
machine.process_event(evt);//b--- -  
end_handle(); -  
} -  
void handle_event(CephPeeringEvtRef evt, -  
RecoveryCtx *rctx) { -  
start_handle(rctx); -  
machine.process_event(evt->get_event());/b--- -  
end_handle(); -  
} 
 a.状态机的初始化
initiate()是继承自boost::statechart::state_machine的成员函数。
b.函数process_event()用来向状态机投递事件,从而触发状态机接收并处理该事件
process_event()也是继承自boost::statechart::state_machine的成员函数。
1.5 context函数
 context是状态机的一个比较有用的函数,它可以获取当前状态的所有祖先状态的指针。通过它可以获取父状态以及祖先状态的一些内部参数和状态值。context()函数是实现在boost::statechart::state_machine中的:
context()函数在boost::statechart::simple_state中有实现:
-  
//boost_1_73_0/boost/statechart/simple_state.hpp -  
234 template< class OtherContext > -  
235 OtherContext & context() -  
236 { -  
237 typedef typename mpl::if_< -  
238 is_base_of< OtherContext, MostDerived >, -  
239 context_impl_this_context, -  
240 context_impl_other_context -  
241 >::type impl; -  
242 return impl::template context_impl< OtherContext >( *this ); -  
243 } -  
244 -  
245 template< class OtherContext > -  
246 const OtherContext & context() const -  
247 { -  
248 typedef typename mpl::if_< -  
249 is_base_of< OtherContext, MostDerived >, -  
250 context_impl_this_context, -  
251 context_impl_other_context -  
252 >::type impl; -  
253 return impl::template context_impl< OtherContext >( *this ); -  
254 } 
从simple_state的实现来看,context()可以获取当前状态的祖先状态指针,也可以获取当前状态所属状态机的指针。
例如状态Started是RecoveryMachine的一个状态,状态Start是Started状态的一个子状态,那么如果当前状态是Start,就可以通过该函数获取它的父状态Started的指针:
Started * parent = context< Started >(); 
同时也可以获取其祖先状态RecoveryMachine的指针:
RecoveryMachine *machine = context< RecoveryMachine >(); 
在状态机实现中,大量了使用该函数来获取相应的指针。Eg:
-  
PG *pg = context< RecoveryMachine >().pg; -  
context< RecoveryMachine >().get_cur_transaction(), -  
context< RecoveryMachine >().get_on_applied_context_list(), -  
context< RecoveryMachine >().get_on_safe_context_list()); 
综上所述,context()函数为获取当前状态的祖先状态上下文提供了一种方法。
<span id = “1.6事件的特殊处理”></span>
1.6 事件的特殊处理
 事件除了在状态转移列表中触发状态转移,或者进入用户自定义的状态处理函数,还可以有下列特殊的处理方式:
在用户自定义的函数里,可以直接调用函数transit来直接跳转到目标状态。例如:
-  
boost::statechart::result PG::RecoveryState::Initial::react(const MLogRec& i) -  
{ -  
PG *pg = context< RecoveryMachine >().pg; -  
assert(!pg->is_primary()); -  
post_event(i); -  
return transit< Stray >();//go--- -  
} 
可以直接跳转到状态Stray。在用户自定义的函数里,可以调用函数post_event()直接产生相应的事件,并投递给状态机
-  
PG::RecoveryState::Start::Start(my_context ctx) -  
: my_base(ctx), -  
NamedState(context< RecoveryMachine >().pg->cct, "Start") -  
{ -  
context< RecoveryMachine >().log_enter(state_name); -  
PG *pg = context< RecoveryMachine >().pg; -  
if (pg->is_primary()) { -  
dout(1) << "transitioning to Primary" << dendl; -  
post_event(MakePrimary());//go--- -  
} else { //is_stray -  
dout(1) << "transitioning to Stray" << dendl; -  
post_event(MakeStray());//go--- -  
} -  
} 
在用户的自定义函数里,调用函数discard_event()可以直接丢弃事件,不做任何处理
-  
boost::statechart::result PG::RecoveryState::Primary::react(const ActMap&) -  
{ -  
dout(7) << "handle ActMap primary" << dendl; -  
PG *pg = context< RecoveryMachine >().pg; -  
pg->publish_stats_to_osd(); -  
pg->take_waiters(); -  
return discard_event();//go--- -  
} 
在用户的自定义函数里,调用函数forward_event()可以把当前事件继续投递给状态机
-  
boost::statechart::result PG::RecoveryState::WaitUpThru::react(const ActMap& am) -  
{ -  
PG *pg = context< RecoveryMachine >().pg; -  
if (!pg->need_up_thru) { -  
post_event(Activate(pg->get_osdmap()->get_epoch())); -  
} -  
return forward_event(); -  
} 
结合 1.3 状态机的响应 的3种事件响应,大概有7种事件响应处理的方法。
1.7 PG状态机
 在类PG的内部定义了类RecoveryState,该类RecoveryState的内部定义了PG的状态机RecoveryMachine和它的各种状态。
-  
class PG{ -  
class RecoveryState{ -  
class RecoveryMachine{ -  
}; -  
}; -  
}; 
在每个PG创建时,在构造函数里创建一个新的RecoveryState类的对象,并创建相应的RecoveryMachine类的对象,也就是创建了一个新的状态机。每个PG类对应一个独立的状态机来控制该PG的状态转换。
-  
PG::PG(OSDService *o, OSDMapRef curmap, -  
const PGPool &_pool, spg_t p) : -  
recovery_state(this){ -  
} -  
class RecoveryState{ -  
public: -  
explicit RecoveryState(PG *pg) -  
: machine(this, pg), pg(pg), orig_ctx(0) { -  
machine.initiate(); -  
} -  
}; 
上面machine.initiate()调用的是boost::statechart::state_machine中的initiate()方法。
1.8 PG状态机的总体状态转换图
下图为PG状态机的总体状态转换图简化版

1.9 OSD启动加载PG状态机转换
 当OSD重启时,调用函数OSD::init(),该函数调用load_pgs()加载已经存在的PG,其处理过程和以下创建PG的过程相似。
-  
int OSD::init() -  
{ -  
// load up pgs (as they previously existed) -  
load_pgs(); -  
} -  
void OSD::load_pgs() -  
{ -  
... -  
PG::RecoveryCtx rctx(0, 0, 0, 0, 0, 0); -  
pg->handle_loaded(&rctx);//go-- -  
... -  
} -  
void PG::handle_loaded(RecoveryCtx *rctx) -  
{ -  
dout(10) << "handle_loaded" << dendl; -  
Load evt; -  
recovery_state.handle_event(evt, rctx); -  
} -  
struct Initial : boost::statechart::state< Initial, RecoveryMachine >, NamedState { -  
typedef boost::mpl::list < -  
boost::statechart::transition< Initialize, Reset >, -  
boost::statechart::custom_reaction< Load >, -  
boost::statechart::custom_reaction< NullEvt >, -  
boost::statechart::transition< boost::statechart::event_base, Crashed > -  
> reactions; -  
 -  
boost::statechart::result react(const Load&); -  
} -  
boost::statechart::result PG::RecoveryState::Initial::react(const Load& l) -  
{ -  
PG *pg = context< RecoveryMachine >().pg; -  
// do we tell someone we're here? -  
pg->send_notify = (!pg->is_primary()); -  
pg->update_store_with_options(); -  
pg->update_store_on_load(); -  
return transit< Reset >();//go--- -  
} 
1.10 PG创建后状态机的状态转换
  
-  
void PG::handle_create(RecoveryCtx *rctx) -  
{ -  
dout(10) << "handle_create" << dendl; -  
rctx->created_pgs.insert(this); -  
Initialize evt; -  
recovery_state.handle_event(evt, rctx); -  
ActMap evt2; -  
recovery_state.handle_event(evt2, rctx); -  
rctx->on_applied->add(make_lambda_context([this]() { -  
update_store_with_options(); -  
})); -  
} 
当PG创建后,同时在该类内部创建了一个属于该PG的RecoveryMachine类型的状态机,该状态机的初始化状态为默认初始化状态Initial。
在PG创建后,调用函数pg->handle_create(&rctx)来给状态机投递事件
该函数首先向RecoveryMachine投递了Initialize类型的事件。接收到Initialize类型的事件后直接转移到Reset状态。其次,向RecoveryMachine投递了ActMap事件。
-  
boost::statechart::result PG::RecoveryState::Reset::react(const ActMap&) -  
{ -  
PG *pg = context< RecoveryMachine >().pg; -  
if (pg->should_send_notify() && pg->get_primary().osd >= 0) { -  
context< RecoveryMachine >().send_notify( -  
pg->get_primary(), -  
pg_notify_t( -  
pg->get_primary().shard, pg->pg_whoami.shard, -  
pg->get_osdmap()->get_epoch(), -  
pg->get_osdmap()->get_epoch(), -  
pg->info), -  
pg->past_intervals); -  
} -  
pg->update_heartbeat_peers(); -  
pg->take_waiters(); -  
return transit< Started >();//a--- -  
} 
a. 在自定义的react函数里直接调用了transit函数跳转到Started状态。
-  
struct Start; -  
struct Started : boost::statechart::state< Started, RecoveryMachine, Start >, NamedState {//这里直接进入默认子状态Start -  
... -  
} -  
/*-------Start---------*/ -  
PG::RecoveryState::Start::Start(my_context ctx) -  
: my_base(ctx), -  
NamedState(context< RecoveryMachine >().pg, "Start") -  
{ -  
context< RecoveryMachine >().log_enter(state_name); -  
 -  
PG *pg = context< RecoveryMachine >().pg; -  
if (pg->is_primary()) { -  
ldout(pg->cct, 1) << "transitioning to Primary" << dendl; -  
post_event(MakePrimary());//go--- -  
} else { //is_stray -  
ldout(pg->cct, 1) << "transitioning to Stray" << dendl; -  
post_event(MakeStray());//go--- -  
} -  
} -  
 -  
struct Start : boost::statechart::state< Start, Started >, NamedState { -  
explicit Start(my_context ctx); -  
void exit(); -  
typedef boost::mpl::list < -  
boost::statechart::transition< MakePrimary, Primary >, -  
boost::statechart::transition< MakeStray, Stray > -  
> reactions; -  
}; -  
 -  
struct Primary : boost::statechart::state< Primary, Started, Peering >, NamedState {//这里直接进入Primary的默认子状态Peering。 -  
... -  
} -  
 -  
struct Stray : boost::statechart::state< Stray, Started >, NamedState { -  
... -  
} 
1.进入状态RecoveryMachine/Started后,就进入RecoveryMachine/Started的默认的子状态RecoveryMachine/Started/Start中。
 由以上代码可知,在Start状态的构造函数中,根据本OSD在该PG中担任的角色不同分别进行如下处理:
(1)如果是主OSD,就调用函数post_event(),抛出事件MakePrimary,进入主OSD的默认子状态Primary/Peering中;
(2)如果是从OSD,就调用函数post_event(),抛出事件MakeStray,进入Started/Stray状态;
对于一个OSD的PG处于Stray状态,是指该OSD上的PG副本目前状态不确定,但是可以响应主OSD的各种查询操作。它有两种可能:一种是最终转移到状态ReplicaActive,处于活跃状态,成为PG的一个副本;另一种可能的情况是:如果是数据迁移的源端,可能一直保持Stray状态,该OSD上的副本可能在数据迁移完成后,PG以及数据就都被删除了。
1.11 PG在触发Peering过程时机:
 1.当系统初始化时,OSD重新启动导致PG重新加载。
 2.PG新创建时,PG会发起一次Peering的过程
 3. 当有OSD失效,OSD的增加或者删除等导致PG的acting set发生了变化,该PG就会重新发起一次Peering过程。
 参考link:
  https://ivanzz1001.github.io/records/post/ceph/2019/02/01/ceph-src-code-part10_1
