合肥seo優(yōu)化安徽網站關鍵詞優(yōu)化
本章介紹ceph中比較復雜的模塊:
Peering機制。該過程保障PG內各個副本之間數據的一致性,并實現PG的各種狀態(tài)的維護和轉換。本章首先介紹boost庫的statechart狀態(tài)機基本知識,Ceph使用它來管理PG的狀態(tài)轉換。其次介紹PG的創(chuàng)建過程以及相應的狀態(tài)機創(chuàng)建和初始化。然后詳細介紹peering機制三個具體的實現階段:GetInfo、GetLog、GetMissing。
statechart狀態(tài)機
1.1 狀態(tài)
1.2 事件
1.3 狀態(tài)機的響應
1.4 狀態(tài)機的定義
1.5 context函數
1.6 事件的特殊處理
1.7 PG狀態(tài)機
1.8 PG狀態(tài)機的總體狀態(tài)轉換圖
1.9 OSD啟動加載PG狀態(tài)機轉換
1.10 PG創(chuàng)建后狀態(tài)機的狀態(tài)轉換
1.11 PG在觸發(fā)Peering過程時機
1. statechart狀態(tài)機
Ceph在處理PG的狀態(tài)轉換時,使用了boost庫提供的statechart狀態(tài)機。因此先簡單介紹一下statechart狀態(tài)機的基本概念和涉及的相關知識,以便更好地理解Peering過程PG的狀態(tài)機轉換流程。下面例舉時截取了PG狀態(tài)機的部分代碼。
1.1 狀態(tài)
沒有子狀態(tài)情況下的狀態(tài)定義
在statechart里,一個狀態(tài)的定義方式有兩種:
-
struct Reset : boost::statechart::state< Reset, RecoveryMachine >, NamedState {
-
...
-
};
這里定義了狀態(tài)Reset,它需要繼承boost::statechart::state類。該類的模板參數中,第一個參數為狀態(tài)自己的名字Reset,第二個參數為該狀態(tài)所屬狀態(tài)機的名字,表明Reset是狀態(tài)機RecoveryMachine的一個狀態(tài)。
有子狀態(tài)情況下的狀態(tài)定義
-
struct Start;
-
struct Started : boost::statechart::state< Started, RecoveryMachine, Start >, NamedState {
-
...
-
}
-
struct Start : boost::statechart::state< Start, Started >, NamedState {
-
};
狀態(tài)Started也是狀態(tài)機RecoveryMachine的一個狀態(tài),模板參數中多了一個參數Start,它是狀態(tài)Started的默認初始子狀態(tài)。
這里定義的Start是狀態(tài)Started的子狀態(tài)。第一個模板參數是自己的名字,第二個模板參數是該子狀態(tài)所屬父狀態(tài)的名字。
綜上所述,一個狀態(tài),要么屬于一個狀態(tài)機,要么屬于一個狀態(tài),成為該狀態(tài)的子狀態(tài)。其定義的模板參數是自己,第二個參數是擁有者,第三個參數是它的起始子狀態(tài)。
?1.2 事件
狀態(tài)能夠接收并處理事件。事件可以改變狀態(tài),促使狀態(tài)發(fā)生轉移。在boost庫的statechart狀態(tài)機中定義事件的方式如下所示:
-
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 狀態(tài)機的響應
在一個狀態(tài)內部,需要定義狀態(tài)機處于當前狀態(tài)時,可以接受的事件以及如何處理這些事件的方法:
-
#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();
-
}
-
};
狀態(tài)機的7種事件處理方法?
上述代碼列出了狀態(tài)RecoveryMachine/Initial可以處理的事件列表和處理對應事件的方法:
1) 通過boost::mpl::list定義該狀態(tài)可以處理多個事件類型。本例中可以處理Initialize、Load、NullEvt和event_base事件。
2) 簡單事件處理
boost::statechart::transition< Initialize, Reset >
定義了狀態(tài)Initial接收到事件Initialize后,無條件直接跳轉到Reset狀態(tài);
3) 用戶自定義事件處理: 當接收到事件后,需要根據一些條件來決定狀態(tài)如何轉移,這個邏輯需要用戶自己定義實現
boost::statechart::custom_reaction< Load >
custom_reaction 定義了一個用戶自定義的事件處理方法,必須有一個react()的處理函數處理對應該事件。狀態(tài)轉移的邏輯需要用戶自己在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 狀態(tài)機的定義
RecoveryMachine為定義的狀態(tài)機,需要繼承boost::statechart::state_machine類:
-
? ? struct Initial;
-
? ? class RecoveryMachine : public boost::statechart::state_machine< RecoveryMachine, Initial > {
-
? ? ? RecoveryState *state;
-
? ? public:
-
? ? ? PG *pg;
-
? ? ? }
模板參數第一個參數為自己的名字,第二個參數為狀態(tài)機默認的初始狀態(tài)Initial。
狀態(tài)機的基本操作有兩個:? ?
-
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.狀態(tài)機的初始化
initiate()是繼承自boost::statechart::state_machine的成員函數。
b.函數process_event()用來向狀態(tài)機投遞事件,從而觸發(fā)狀態(tài)機接收并處理該事件
process_event()也是繼承自boost::statechart::state_machine的成員函數。
1.5 context函數
context是狀態(tài)機的一個比較有用的函數,它可以獲取當前狀態(tài)的所有祖先狀態(tài)的指針。通過它可以獲取父狀態(tài)以及祖先狀態(tài)的一些內部參數和狀態(tài)值。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()可以獲取當前狀態(tài)的祖先狀態(tài)指針,也可以獲取當前狀態(tài)所屬狀態(tài)機的指針。
例如狀態(tài)Started是RecoveryMachine的一個狀態(tài),狀態(tài)Start是Started狀態(tài)的一個子狀態(tài),那么如果當前狀態(tài)是Start,就可以通過該函數獲取它的父狀態(tài)Started的指針:
Started * parent = context< Started >();
同時也可以獲取其祖先狀態(tài)RecoveryMachine的指針:
RecoveryMachine *machine = context< RecoveryMachine >();
在狀態(tài)機實現中,大量了使用該函數來獲取相應的指針。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()函數為獲取當前狀態(tài)的祖先狀態(tài)上下文提供了一種方法。
<span id = “1.6事件的特殊處理”></span>
1.6 事件的特殊處理
事件除了在狀態(tài)轉移列表中觸發(fā)狀態(tài)轉移,或者進入用戶自定義的狀態(tài)處理函數,還可以有下列特殊的處理方式:
在用戶自定義的函數里,可以直接調用函數transit來直接跳轉到目標狀態(tài)。例如:
-
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---
-
}
可以直接跳轉到狀態(tài)Stray。在用戶自定義的函數里,可以調用函數post_event()直接產生相應的事件,并投遞給狀態(tài)機
-
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()可以把當前事件繼續(xù)投遞給狀態(tài)機
-
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 狀態(tài)機的響應?的3種事件響應,大概有7種事件響應處理的方法。
1.7 PG狀態(tài)機
在類PG的內部定義了類RecoveryState,該類RecoveryState的內部定義了PG的狀態(tài)機RecoveryMachine和它的各種狀態(tài)。
-
class PG{
-
?? ?class RecoveryState{
-
?? ??? ?class RecoveryMachine{
-
?? ??? ?};
-
?? ?};
-
};
在每個PG創(chuàng)建時,在構造函數里創(chuàng)建一個新的RecoveryState類的對象,并創(chuàng)建相應的RecoveryMachine類的對象,也就是創(chuàng)建了一個新的狀態(tài)機。每個PG類對應一個獨立的狀態(tài)機來控制該PG的狀態(tài)轉換。
-
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狀態(tài)機的總體狀態(tài)轉換圖
下圖為PG狀態(tài)機的總體狀態(tài)轉換圖簡化版
1.9 OSD啟動加載PG狀態(tài)機轉換
當OSD重啟時,調用函數OSD::init(),該函數調用load_pgs()加載已經存在的PG,其處理過程和以下創(chuàng)建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創(chuàng)建后狀態(tài)機的狀態(tài)轉換
?
-
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創(chuàng)建后,同時在該類內部創(chuàng)建了一個屬于該PG的RecoveryMachine類型的狀態(tài)機,該狀態(tài)機的初始化狀態(tài)為默認初始化狀態(tài)Initial。
在PG創(chuàng)建后,調用函數pg->handle_create(&rctx)來給狀態(tài)機投遞事件
該函數首先向RecoveryMachine投遞了Initialize類型的事件。接收到Initialize類型的事件后直接轉移到Reset狀態(tài)。其次,向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狀態(tài)。? ?
-
struct Start;
-
? ? struct Started : boost::statechart::state< Started, RecoveryMachine, Start >, NamedState {//這里直接進入默認子狀態(tài)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的默認子狀態(tài)Peering。
-
? ? ...
-
? ? }
-
? ??
-
? ? struct Stray : boost::statechart::state< Stray, Started >, NamedState {
-
? ? ...
-
? ? } ? ?
1.進入狀態(tài)RecoveryMachine/Started后,就進入RecoveryMachine/Started的默認的子狀態(tài)RecoveryMachine/Started/Start中。
由以上代碼可知,在Start狀態(tài)的構造函數中,根據本OSD在該PG中擔任的角色不同分別進行如下處理:
(1)如果是主OSD,就調用函數post_event(),拋出事件MakePrimary,進入主OSD的默認子狀態(tài)Primary/Peering中;
(2)如果是從OSD,就調用函數post_event(),拋出事件MakeStray,進入Started/Stray狀態(tài);
對于一個OSD的PG處于Stray狀態(tài),是指該OSD上的PG副本目前狀態(tài)不確定,但是可以響應主OSD的各種查詢操作。它有兩種可能:一種是最終轉移到狀態(tài)ReplicaActive,處于活躍狀態(tài),成為PG的一個副本;另一種可能的情況是:如果是數據遷移的源端,可能一直保持Stray狀態(tài),該OSD上的副本可能在數據遷移完成后,PG以及數據就都被刪除了。
1.11 PG在觸發(fā)Peering過程時機:
1.當系統(tǒng)初始化時,OSD重新啟動導致PG重新加載。
2.PG新創(chuàng)建時,PG會發(fā)起一次Peering的過程
3. 當有OSD失效,OSD的增加或者刪除等導致PG的acting set發(fā)生了變化,該PG就會重新發(fā)起一次Peering過程。
參考link:
?https://ivanzz1001.github.io/records/post/ceph/2019/02/01/ceph-src-code-part10_1