ceph peering机制-状态机

article/2025/9/16 16:59:47

本章介绍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_straydout(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_strayldout(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


http://chatgpt.dhexx.cn/article/2W5JiTAj.shtml

相关文章

Ceph源码解析:PG peering

集群中的设备异常(异常OSD的添加删除操作)&#xff0c;会导致PG的各个副本间出现数据的不一致现象&#xff0c;这时就需要进行数据的恢复&#xff0c;让所有的副本都达到一致的状态。 一、OSD的故障和处理办法&#xff1a; 1. OSD的故障种类&#xff1a; 故障A&#xff1a;一个…

京东云VPC对等连接(VPC Peering)

VPC对等连接&#xff08;VPC Peering&#xff09;用于打通两个同区域的VPC&#xff08;是否为同一账号无所谓&#xff09;的内网连接。 这里的区域是指四个大区&#xff1a;华北-北京、华东-上海、华东-宿迁、华南-广州。 1.基本概念&#xff1a; VPC 对等连接是一种跨VPC网络互…

如何通过AWS VPC Peering云服务,解决公共云的局限性?

曾经&#xff0c;AWS全球范围内的VPC实例资源可以说是相互独立&#xff0c;各地的VPC受区域限制没有相互连接的通道&#xff0c;VPC中的实例也就无法通过内网相互连接。必须通过公用进行访问&#xff0c;给各个企业带来了很多安全方面的挑战。但是现在通过AWS提供的云服务&…

Windows Azure Virtual Network (12) 虚拟网络之间点对点连接VNet Peering

《Windows Azure Platform 系列文章目录》 在有些时候&#xff0c;我们需要通过VNet Peering&#xff0c;把两个虚拟网络通过内网互通互联。比如&#xff1a; 1.在订阅A里的Virtual Network&#xff0c;部署了CRM系统 2.在订阅B里的Virtual Network&#xff0c;部署了Order系统…

VPC Peering 具有特定路由的配置

https://docs.aws.amazon.com/zh_cn/vpc/latest/peering/peering-configurations-partial-access.html 您可以配置 VPC 对等连接以便访问对等 VPC 内的部分 CIDR 块、特定 CIDR 块&#xff08;如果 VPC 有多个 CIDR 块&#xff09;或特定实例。在这些示例中&#xff0c;一个中心…

k8s当中calico节点日志报错 confd/health.go 180: Number of node(s) with BGP peering established = 0

k8s当中两个calico节点未准备就绪 查看日志显示 confd/health.go 180: Number of node(s) with BGP peering established 0 calico/node is not ready: BIRD is not ready: BGP not established with 172.16.0.4 两个原因&#xff1a; 1.网卡信息错误&#xff0c;网卡过多未…

Ceph _backfill and recovery 之间的不同以及 peering理解

在 Ceph 中,有两种方法可以在集群内的 OSD 之间同步数据,recovery和backfill。虽然这两种方法都实现了相同的最终目标,但在这两个过程中存在细微差别,如下所述。 什么时候使用 Recovery 而不是 Backfill? Ceph OSD 进程为每个归置组 (PG) 维护一个名为 的日志pglog,其中…

Ceph Peering以及数据均衡的改进思路

前言 从15年3月接触Ceph分布式存储系统&#xff0c;至今已经5年了&#xff1b;因为工作的需要&#xff0c;对Ceph的主要模块进行了较深入的学习&#xff0c;也在Ceph代码层面做了些许改进&#xff0c;以满足业务需要&#xff08;我们主要使用M版本&#xff09;。最近得闲&…

AWS VPC Peering Azure VNET Peering

前提条件&#xff1a; IP 地址不能有冲突 Global 不能和 中国区VNET Peering Azure VNET Peering 首先创建两个VNET: vnet01 vnet02 创建Peering Connection b. c. 选择VNET Peering 配置项 add 即可 AWS VPC Peering 创建两个VPC 创建Peering Connection a. b. c. 创建即可…

VPC对等连接(VPC Peering)

VPC对等连接&#xff08;VPC Peering&#xff09;是两个VPC之间的连接&#xff0c;通过VPC Peering&#xff0c;你可以使用私有地址让两个VPC之间相互通信&#xff0c;就像它们在同一个VPC内一样。 你可以在自己的两个VPC之间建立对等连接&#xff0c;也可以在自己的VPC与其他…

ceph peering流程分析

数据结构 pg_interval_t{vector<int32_t> up, acting;//当前pg_interval的up和acting的osd列表 epoch_t first, last;//该interval的起始和结束epoch bool maybe_went_rw;//在这个阶段是否可能有数据读写 int32_t primary;//主osd int32_t up_primary;//up状态的…

ceph存储 PG的状态机和peering过程

&#xfeff;&#xfeff; PG 的状态机和peering过程 首先来解释下什么是pg peering过程&#xff1f; 当最初建立PG之后&#xff0c;那么需要同步这个pg上所有osd中的pg状态。在这个同步状态的过程叫做peering过程。同样当启动osd的时候&#xff0c;这个osd上所有的pg都要进行…

你所不知道的BGP知识,Peering 和IP-Transit.

了解网络的同行都知道BGP又称“边界网关协议”&#xff0c;他的英文全称是“border gateway protocol”&#xff0c;业内简称“BGP”&#xff0c;他是应用在TCP上的一种路由协议&#xff0c;它的主要功能是为了实现自治系统间的路由选择功能&#xff0c;通俗来讲就是通过控制路…

AWS攻略——Peering连接VPC

文章目录 创建IP/CIDR不覆盖的VPC创建VPC创建子网创建密钥对创建EC2 创建Peering接受Peering邀请修改各个VPC的路由表修改美东us-east-1 pulic subnet的路由修改悉尼ap-southeast-2路由 测试知识点 我们回顾下《AWS攻略——VPC初识》中的知识&#xff1a; 一个VPC只能设置在一…

蒙特卡洛法(Monte Carlo)电动汽车负荷预测matlab程序设计

电动汽车充电负荷的时间分布预测 规模化电动汽车充电负荷在未来某一天随时间特性的分布规律是研究电动汽车发展对配 电网影响以及充电站选址定容问题的前提与基础。电动汽车充电负荷的分布情况与车主的行 为特征有关&#xff0c;不同类型的电动汽车车主出行规律以及充电习惯不…

蒙特卡洛法(三)马尔科夫链蒙特卡洛法

马尔科夫链蒙特卡洛法适合于随机变量是多元的、密度函数是非标准形式的随机变量各分量不独立的情况。如何构建具体的马尔科夫链是这个方法的关键&#xff0c;离散变量的时候&#xff0c;需要定义转移矩阵&#xff0c;构建可逆马尔科夫链&#xff0c;保证遍历定理成立。常用的马…

蒙特卡洛法简述

蒙特卡洛法简述 一.简介&#xff1a; 1.蒙特卡洛方法又称随机模拟法&#xff0c;随机抽样技术&#xff0c;是一种随机模拟方法。 蒙特卡洛法使用随机数&#xff08;伪随机数&#xff09;以概率和统计理论方法为基础&#xff0c;将所要求解的问题同一定的概率模型相互联系&am…

蒙特卡洛法模拟计算圆周率π

一、蒙特卡洛法介绍 蒙特卡罗方法&#xff08;Monte Carlo method&#xff09;&#xff0c;也称统计模拟方法&#xff0c;是一种以概率统计理论为基础的数值计算方法&#xff0c;常用于特定条件下的概率计算问题。蒙特卡罗是摩纳哥的著名赌城&#xff0c;该法为表明其随机抽样的…

蒙特卡洛法之MATLAB实现

by WC 1.7.2016蒙特卡洛法&#xff08;随机取样法&#xff09;也称为计算机随机模拟方法&#xff0c;它源于世界著名的赌城——Monte Carlo。它是基于对大量事件的统计结果来实现一些确定性问题的计算。使用蒙特卡洛法必须使用计算机生成相关分布的随机数。 eg&#xff1a; y…

C语言文件打开关闭和读写

文件在进行读写操作之前要先打开&#xff0c;使用完毕要关闭。在C语言中&#xff0c;文件操作都是由库函数来完成的。在本节内将介绍主要的文件操作函数。 文件的打开(fopen函数) fopen函数用来打开一个文件&#xff0c;其调用的一般形式为&#xff1a; 文件指针名 fopen( 文…