ceph peering机制-状态机

本章介绍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里,一个状态的定义方式有两种:

复制代码
  1. struct Reset : boost::statechart::state< Reset, RecoveryMachine >, NamedState {

  2. ...

  3. };

这里定义了状态Reset,它需要继承boost::statechart::state类。该类的模板参数中,第一个参数为状态自己的名字Reset,第二个参数为该状态所属状态机的名字,表明Reset是状态机RecoveryMachine的一个状态。

有子状态情况下的状态定义

复制代码
  1. struct Start;

  2. struct Started : boost::statechart::state< Started, RecoveryMachine, Start >, NamedState {

  3. ...

  4. }

  5. struct Start : boost::statechart::state< Start, Started >, NamedState {

  6. };

状态Started也是状态机RecoveryMachine的一个状态,模板参数中多了一个参数Start,它是状态Started的默认初始子状态。

这里定义的Start是状态Started的子状态。第一个模板参数是自己的名字,第二个模板参数是该子状态所属父状态的名字。

综上所述,一个状态,要么属于一个状态机,要么属于一个状态,成为该状态的子状态。其定义的模板参数是自己,第二个参数是拥有者,第三个参数是它的起始子状态。

1.2 事件

状态能够接收并处理事件。事件可以改变状态,促使状态发生转移。在boost库的statechart状态机中定义事件的方式如下所示:

复制代码
  1. struct QueryState : boost::statechart::event< QueryState > {

  2. Formatter *f;

  3. explicit QueryState(Formatter *f) : f(f) {}

  4. void print(std::ostream *out) const {

  5. *out << "Query";

  6. }

  7. };

  8. };

QueryState为一个事件,需要继承boost::statechart::event类,模板参数为自己的名字。

1.3 状态机的响应

在一个状态内部,需要定义状态机处于当前状态时,可以接受的事件以及如何处理这些事件的方法:

复制代码
  1. #define TrivialEvent(T) struct T : boost::statechart::event< T > { \

  2. T() : boost::statechart::event< T >() {} \

  3. void print(std::ostream *out) const { \

  4. *out << #T; \

  5. } \

  6. };

  7. TrivialEvent(Initialize)

  8. TrivialEvent(Load)

  9. TrivialEvent(GotInfo)

  10. TrivialEvent(NeedUpThru)

  11. TrivialEvent(NullEvt)

  12. TrivialEvent(FlushedEvt)

  13. TrivialEvent(Backfilled)

  14. TrivialEvent(LocalBackfillReserved)

  15. TrivialEvent(RemoteBackfillReserved)

  16. TrivialEvent(RejectRemoteReservation)

  17. TrivialEvent(RemoteReservationRejected)

  18. TrivialEvent(RemoteReservationCanceled)

  19. TrivialEvent(RequestBackfill)

  20. TrivialEvent(RequestRecovery)

  21. TrivialEvent(RecoveryDone)

  22. TrivialEvent(BackfillTooFull)

  23. TrivialEvent(RecoveryTooFull)

  24. TrivialEvent(MakePrimary)

  25. TrivialEvent(MakeStray)

  26. TrivialEvent(NeedActingChange)

  27. TrivialEvent(IsIncomplete)

  28. TrivialEvent(IsDown)

  29. TrivialEvent(AllReplicasRecovered)

  30. TrivialEvent(DoRecovery)

  31. TrivialEvent(LocalRecoveryReserved)

  32. TrivialEvent(RemoteRecoveryReserved)

  33. TrivialEvent(AllRemotesReserved)

  34. TrivialEvent(AllBackfillsReserved)

  35. TrivialEvent(GoClean)

  36. TrivialEvent(AllReplicasActivated)

  37. TrivialEvent(IntervalFlush)

复制代码
  1. struct Initial : boost::statechart::state< Initial, RecoveryMachine >, NamedState {

  2. explicit Initial(my_context ctx);

  3. void exit();

  4. typedef boost::mpl::list <

  5. boost::statechart::transition< Initialize, Reset >,

  6. boost::statechart::custom_reaction< Load >,

  7. boost::statechart::custom_reaction< NullEvt >,

  8. boost::statechart::transition< boost::statechart::event_base, Crashed >

  9. > reactions;

  10. boost::statechart::result react(const Load&);

  11. boost::statechart::result react(const MNotifyRec&);

  12. boost::statechart::result react(const MInfoRec&);

  13. boost::statechart::result react(const MLogRec&);

  14. boost::statechart::result react(const boost::statechart::event_base&) {

  15. return discard_event();

  16. }

  17. };

状态机的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把事件丢弃掉。

复制代码
  1. boost::statechart::custom_reaction< NullEvt >

  2. boost::statechart::result react(const boost::statechart::event_base&) {

  3. return discard_event();

  4. }

1.4 状态机的定义

RecoveryMachine为定义的状态机,需要继承boost::statechart::state_machine类:

复制代码
  1. struct Initial;

  2. class RecoveryMachine : public boost::statechart::state_machine< RecoveryMachine, Initial > {

  3. RecoveryState *state;

  4. public:

  5. PG *pg;

  6. }

模板参数第一个参数为自己的名字,第二个参数为状态机默认的初始状态Initial。

状态机的基本操作有两个:

复制代码
  1. RecoveryMachine machine;

  2. PG *pg;

  3. explicit RecoveryState(PG *pg)

  4. : machine(this, pg), pg(pg), orig_ctx(0) {

  5. machine.initiate();//a---

  6. }

  7. void handle_event(const boost::statechart::event_base &evt,

  8. RecoveryCtx *rctx) {

  9. start_handle(rctx);

  10. machine.process_event(evt);//b---

  11. end_handle();

  12. }

  13. void handle_event(CephPeeringEvtRef evt,

  14. RecoveryCtx *rctx) {

  15. start_handle(rctx);

  16. machine.process_event(evt->get_event());/b---

  17. end_handle();

  18. }

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中有实现:

复制代码
  1. //boost_1_73_0/boost/statechart/simple_state.hpp

  2. 234 template< class OtherContext >

  3. 235 OtherContext & context()

  4. 236 {

  5. 237 typedef typename mpl::if_<

  6. 238 is_base_of< OtherContext, MostDerived >,

  7. 239 context_impl_this_context,

  8. 240 context_impl_other_context

  9. 241 >::type impl;

  10. 242 return impl::template context_impl< OtherContext >( *this );

  11. 243 }

  12. 244

  13. 245 template< class OtherContext >

  14. 246 const OtherContext & context() const

  15. 247 {

  16. 248 typedef typename mpl::if_<

  17. 249 is_base_of< OtherContext, MostDerived >,

  18. 250 context_impl_this_context,

  19. 251 context_impl_other_context

  20. 252 >::type impl;

  21. 253 return impl::template context_impl< OtherContext >( *this );

  22. 254 }

从simple_state的实现来看,context()可以获取当前状态的祖先状态指针,也可以获取当前状态所属状态机的指针。

例如状态Started是RecoveryMachine的一个状态,状态Start是Started状态的一个子状态,那么如果当前状态是Start,就可以通过该函数获取它的父状态Started的指针:

复制代码
Started * parent = context< Started >();

同时也可以获取其祖先状态RecoveryMachine的指针:

复制代码
RecoveryMachine *machine = context< RecoveryMachine >();

在状态机实现中,大量了使用该函数来获取相应的指针。Eg:

复制代码
  1. PG *pg = context< RecoveryMachine >().pg;

  2. context< RecoveryMachine >().get_cur_transaction(),

  3. context< RecoveryMachine >().get_on_applied_context_list(),

  4. context< RecoveryMachine >().get_on_safe_context_list());

综上所述,context()函数为获取当前状态的祖先状态上下文提供了一种方法。

<span id = "1.6事件的特殊处理"></span>

1.6 事件的特殊处理

事件除了在状态转移列表中触发状态转移,或者进入用户自定义的状态处理函数,还可以有下列特殊的处理方式:

在用户自定义的函数里,可以直接调用函数transit来直接跳转到目标状态。例如:

复制代码
  1. boost::statechart::result PG::RecoveryState::Initial::react(const MLogRec& i)

  2. {

  3. PG *pg = context< RecoveryMachine >().pg;

  4. assert(!pg->is_primary());

  5. post_event(i);

  6. return transit< Stray >();//go---

  7. }

可以直接跳转到状态Stray。在用户自定义的函数里,可以调用函数post_event()直接产生相应的事件,并投递给状态机

复制代码
  1. PG::RecoveryState::Start::Start(my_context ctx)

  2. : my_base(ctx),

  3. NamedState(context< RecoveryMachine >().pg->cct, "Start")

  4. {

  5. context< RecoveryMachine >().log_enter(state_name);

  6. PG *pg = context< RecoveryMachine >().pg;

  7. if (pg->is_primary()) {

  8. dout(1) << "transitioning to Primary" << dendl;

  9. post_event(MakePrimary());//go---

  10. } else { //is_stray

  11. dout(1) << "transitioning to Stray" << dendl;

  12. post_event(MakeStray());//go---

  13. }

  14. }

在用户的自定义函数里,调用函数discard_event()可以直接丢弃事件,不做任何处理

复制代码
  1. boost::statechart::result PG::RecoveryState::Primary::react(const ActMap&)

  2. {

  3. dout(7) << "handle ActMap primary" << dendl;

  4. PG *pg = context< RecoveryMachine >().pg;

  5. pg->publish_stats_to_osd();

  6. pg->take_waiters();

  7. return discard_event();//go---

  8. }

在用户的自定义函数里,调用函数forward_event()可以把当前事件继续投递给状态机

复制代码
  1. boost::statechart::result PG::RecoveryState::WaitUpThru::react(const ActMap& am)

  2. {

  3. PG *pg = context< RecoveryMachine >().pg;

  4. if (!pg->need_up_thru) {

  5. post_event(Activate(pg->get_osdmap()->get_epoch()));

  6. }

  7. return forward_event();

  8. }

结合1.3 状态机的响应 的3种事件响应,大概有7种事件响应处理的方法。

1.7 PG状态机

在类PG的内部定义了类RecoveryState,该类RecoveryState的内部定义了PG的状态机RecoveryMachine和它的各种状态。

复制代码
  1. class PG{

  2. class RecoveryState{

  3. class RecoveryMachine{

  4. };

  5. };

  6. };

在每个PG创建时,在构造函数里创建一个新的RecoveryState类的对象,并创建相应的RecoveryMachine类的对象,也就是创建了一个新的状态机。每个PG类对应一个独立的状态机来控制该PG的状态转换。

复制代码
  1. PG::PG(OSDService *o, OSDMapRef curmap,

  2. const PGPool &_pool, spg_t p) :

  3. recovery_state(this){

  4. }

  5. class RecoveryState{

  6. public:

  7. explicit RecoveryState(PG *pg)

  8. : machine(this, pg), pg(pg), orig_ctx(0) {

  9. machine.initiate();

  10. }

  11. };

上面machine.initiate()调用的是boost::statechart::state_machine中的initiate()方法。
1.8 PG状态机的总体状态转换图

下图为PG状态机的总体状态转换图简化版

1.9 OSD启动加载PG状态机转换

当OSD重启时,调用函数OSD::init(),该函数调用load_pgs()加载已经存在的PG,其处理过程和以下创建PG的过程相似。

复制代码
  1. int OSD::init()

  2. {

  3. // load up pgs (as they previously existed)

  4. load_pgs();

  5. }

  6. void OSD::load_pgs()

  7. {

  8. ...

  9. PG::RecoveryCtx rctx(0, 0, 0, 0, 0, 0);

  10. pg->handle_loaded(&rctx);//go--

  11. ...

  12. }

  13. void PG::handle_loaded(RecoveryCtx *rctx)

  14. {

  15. dout(10) << "handle_loaded" << dendl;

  16. Load evt;

  17. recovery_state.handle_event(evt, rctx);

  18. }

  19. struct Initial : boost::statechart::state< Initial, RecoveryMachine >, NamedState {

  20. typedef boost::mpl::list <

  21. boost::statechart::transition< Initialize, Reset >,

  22. boost::statechart::custom_reaction< Load >,

  23. boost::statechart::custom_reaction< NullEvt >,

  24. boost::statechart::transition< boost::statechart::event_base, Crashed >

  25. > reactions;

  26. boost::statechart::result react(const Load&);

  27. }

  28. boost::statechart::result PG::RecoveryState::Initial::react(const Load& l)

  29. {

  30. PG *pg = context< RecoveryMachine >().pg;

  31. // do we tell someone we're here?

  32. pg->send_notify = (!pg->is_primary());

  33. pg->update_store_with_options();

  34. pg->update_store_on_load();

  35. return transit< Reset >();//go---

  36. }

1.10 PG创建后状态机的状态转换

复制代码
  1. void PG::handle_create(RecoveryCtx *rctx)

  2. {

  3. dout(10) << "handle_create" << dendl;

  4. rctx->created_pgs.insert(this);

  5. Initialize evt;

  6. recovery_state.handle_event(evt, rctx);

  7. ActMap evt2;

  8. recovery_state.handle_event(evt2, rctx);

  9. rctx->on_applied->add(make_lambda_context([this]() {

  10. update_store_with_options();

  11. }));

  12. }

当PG创建后,同时在该类内部创建了一个属于该PG的RecoveryMachine类型的状态机,该状态机的初始化状态为默认初始化状态Initial。

在PG创建后,调用函数pg->handle_create(&rctx)来给状态机投递事件

该函数首先向RecoveryMachine投递了Initialize类型的事件。接收到Initialize类型的事件后直接转移到Reset状态。其次,向RecoveryMachine投递了ActMap事件。

复制代码
  1. boost::statechart::result PG::RecoveryState::Reset::react(const ActMap&)

  2. {

  3. PG *pg = context< RecoveryMachine >().pg;

  4. if (pg->should_send_notify() && pg->get_primary().osd >= 0) {

  5. context< RecoveryMachine >().send_notify(

  6. pg->get_primary(),

  7. pg_notify_t(

  8. pg->get_primary().shard, pg->pg_whoami.shard,

  9. pg->get_osdmap()->get_epoch(),

  10. pg->get_osdmap()->get_epoch(),

  11. pg->info),

  12. pg->past_intervals);

  13. }

  14. pg->update_heartbeat_peers();

  15. pg->take_waiters();

  16. return transit< Started >();//a---

  17. }

a. 在自定义的react函数里直接调用了transit函数跳转到Started状态。

复制代码
  1. struct Start;

  2. struct Started : boost::statechart::state< Started, RecoveryMachine, Start >, NamedState {//这里直接进入默认子状态Start

  3. ...

  4. }

  5. /*-------Start---------*/

  6. PG::RecoveryState::Start::Start(my_context ctx)

  7. : my_base(ctx),

  8. NamedState(context< RecoveryMachine >().pg, "Start")

  9. {

  10. context< RecoveryMachine >().log_enter(state_name);

  11. PG *pg = context< RecoveryMachine >().pg;

  12. if (pg->is_primary()) {

  13. ldout(pg->cct, 1) << "transitioning to Primary" << dendl;

  14. post_event(MakePrimary());//go---

  15. } else { //is_stray

  16. ldout(pg->cct, 1) << "transitioning to Stray" << dendl;

  17. post_event(MakeStray());//go---

  18. }

  19. }

  20. struct Start : boost::statechart::state< Start, Started >, NamedState {

  21. explicit Start(my_context ctx);

  22. void exit();

  23. typedef boost::mpl::list <

  24. boost::statechart::transition< MakePrimary, Primary >,

  25. boost::statechart::transition< MakeStray, Stray >

  26. > reactions;

  27. };

  28. struct Primary : boost::statechart::state< Primary, Started, Peering >, NamedState {//这里直接进入Primary的默认子状态Peering。

  29. ...

  30. }

  31. struct Stray : boost::statechart::state< Stray, Started >, NamedState {

  32. ...

  33. }

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的过程

  1. 当有OSD失效,OSD的增加或者删除等导致PG的acting set发生了变化,该PG就会重新发起一次Peering过程。

参考link:

https://ivanzz1001.github.io/records/post/ceph/2019/02/01/ceph-src-code-part10_1

相关推荐
三十..2 天前
Ceph分布式存储核心技术精要与运维实践指南
运维·分布式·ceph
一个行走的民2 天前
Ceph OSD NUMA 亲和性、Page Cache 跨 NUMA 访问与绑核实践
ceph
潮起鲸落入海2 天前
ceph集群组件管理 ceph orch 和ceph config命令
ceph
bukeyiwanshui2 天前
20260529 Ceph 分布式存储 认证和授权管理
ceph
bukeyiwanshui2 天前
20260528 Ceph 分布式存储 池管理
ceph
一个行走的民2 天前
CephX 认证机制深度解析
ceph
马立杰2 天前
Ceph 集群手动部署
ceph·分布式存储
bukeyiwanshui2 天前
20260528 Ceph 分布式存储 集群配置
分布式·ceph
qq_356408662 天前
Kubernetes Rook-Ceph 高可用存储部署文档
ceph·容器·kubernetes
潮起鲸落入海2 天前
ceph集群mon 以及池管理
ceph