phxpaxos 狀態機
引用的話:
狀態機這個名詞大家都不陌生,一個狀態機必然涉及到一個狀態轉移,而Paxos的每個實例,就是狀態轉移的輸入,由於每臺機器的實例編號都是連續有序增長的,而每個實例確定的值是一樣的,那麼可以保證的是,各臺機器的狀態機輸入是完全一致的。根據狀態機的理論,只要初始狀態一致,輸入一致,那麼引出的最終狀態也是一致的。而這個狀態,是有無限的想象空間,你可以用來實現非常多的東西。
phpaxos狀態機實現的類圖:
其中包含了五個類:
SMFac:狀態機管理類,維護這一個狀態機列表,對外提供添加狀態機,獲取狀態機列表,狀態機執行等訪問接口。
StateMachine:狀態機的抽象基類,業務人員可以定製自己的狀態機,並且加入到狀態機管理類中。
InsideSM:繼承了StateMachine,抽象出一個內部狀態機的基類。
MasterStateMachine:內部狀態機,選舉master操作。
SystemVM:內部狀態機,處理集羣節點變更。
1、SMFac管理類:
由於每個group之間是相對獨立的,所以我們一般以每個group的邏輯來分析。每個group維護這一個SMFac,允許存在多個狀態機,不同的狀態機之間的數據相互隔離,多個狀態機在一個group串行的excute,具體的是一次proposer帶一個狀態機,learn處理完成之後執行狀態機的excute函數(不考慮batch proposer的情況),因爲他們共享相同的資源:Proposer,learner,accepter。 SMFac做爲管理類,除支持添加各種狀態機,還對外提供了統一的狀態機執行接口。
在learn處理消息完成之後,會進行一些後續狀態機的處理等操作:
if (m_oLearner.IsLearned())
{
BP->GetInstanceBP()->OnInstanceLearned();
SMCtx * poSMCtx = nullptr;
bool bIsMyCommit = m_oCommitCtx.IsMyCommit(m_oLearner.GetInstanceID(), m_oLearner.GetLearnValue(), poSMCtx);
if (!bIsMyCommit)
{
BP->GetInstanceBP()->OnInstanceLearnedNotMyCommit();
PLGDebug("this value is not my commit");
}
else
{
int iUseTimeMs = m_oTimeStat.Point();
BP->GetInstanceBP()->OnInstanceLearnedIsMyCommit(iUseTimeMs);
PLGHead("My commit ok, usetime %dms", iUseTimeMs);
}
// 執行狀態機
if (!SMExecute(m_oLearner.GetInstanceID(), m_oLearner.GetLearnValue(), bIsMyCommit, poSMCtx))
{
BP->GetInstanceBP()->OnInstanceLearnedSMExecuteFail();
PLGErr("SMExecute fail, instanceid %lu, not increase instanceid", m_oLearner.GetInstanceID());
m_oCommitCtx.SetResult(PaxosTryCommitRet_ExecuteFail,
m_oLearner.GetInstanceID(), m_oLearner.GetLearnValue());
m_oProposer.CancelSkipPrepare();
return -1;
}
{
//this paxos instance end, tell proposal done
m_oCommitCtx.SetResult(PaxosTryCommitRet_OK
, m_oLearner.GetInstanceID(), m_oLearner.GetLearnValue());
if (m_iCommitTimerID > 0)
{
m_oIOLoop.RemoveTimer(m_iCommitTimerID);
}
}
PLGHead("[Learned] New paxos starting, Now.Proposer.InstanceID %lu "
"Now.Acceptor.InstanceID %lu Now.Learner.InstanceID %lu",
m_oProposer.GetInstanceID(), m_oAcceptor.GetInstanceID(), m_oLearner.GetInstanceID());
PLGHead("[Learned] Checksum change, last checksum %u new checksum %u",
m_iLastChecksum, m_oLearner.GetNewChecksum());
m_iLastChecksum = m_oLearner.GetNewChecksum();
NewInstance();
PLGHead("[Learned] New paxos instance has started, Now.Proposer.InstanceID %lu "
"Now.Acceptor.InstanceID %lu Now.Learner.InstanceID %lu",
m_oProposer.GetInstanceID(), m_oAcceptor.GetInstanceID(), m_oLearner.GetInstanceID());
m_oCheckpointMgr.SetMaxChosenInstanceID(m_oAcceptor.GetInstanceID());
BP->GetInstanceBP()->NewInstance();
}
其他的先不關心,主要執行了SMExecute方法,源碼如下:
bool Instance :: SMExecute(
const uint64_t llInstanceID,
const std::string & sValue,
const bool bIsMyCommit,
SMCtx * poSMCtx)
{
return m_oSMFac.Execute(m_poConfig->GetMyGroupIdx(), llInstanceID, sValue, poSMCtx);
}
主要封裝了m_oSMFac的Execute方法:
bool SMFac :: Execute(const int iGroupIdx, const uint64_t llInstanceID, const std::string & sPaxosValue, SMCtx * poSMCtx)
{
if (sPaxosValue.size() < sizeof(int))
{
PLG1Err("Value wrong, instanceid %lu size %zu", llInstanceID, sPaxosValue.size());
//need do nothing, just skip
return true;
}
int iSMID = 0;
memcpy(&iSMID, sPaxosValue.data(), sizeof(int));
if (iSMID == 0)
{
PLG1Imp("Value no need to do sm, just skip, instanceid %lu", llInstanceID);
return true;
}
std::string sBodyValue = string(sPaxosValue.data() + sizeof(int), sPaxosValue.size() - sizeof(int));
if (iSMID == BATCH_PROPOSE_SMID)
{
BatchSMCtx * poBatchSMCtx = nullptr;
if (poSMCtx != nullptr && poSMCtx->m_pCtx != nullptr)
{
poBatchSMCtx = (BatchSMCtx *)poSMCtx->m_pCtx;
}
//這裏其實是存在一個batch proposer,暫時不討論
return BatchExecute(iGroupIdx, llInstanceID, sBodyValue, poBatchSMCtx);
}
else
{
return DoExecute(iGroupIdx, llInstanceID, sBodyValue, iSMID, poSMCtx);
}
}
關注一下DoExecute算法,其實就是輪詢狀態機列表,找到合適的狀態機,調用狀態機的執行函數:
bool SMFac :: DoExecute(const int iGroupIdx, const uint64_t llInstanceID,
const std::string & sBodyValue, const int iSMID, SMCtx * poSMCtx)
{
if (iSMID == 0)
{
PLG1Imp("Value no need to do sm, just skip, instanceid %lu", llInstanceID);
return true;
}
if (m_vecSMList.size() == 0)
{
PLG1Imp("No any sm, need wait sm, instanceid %lu", llInstanceID);
return false;
}
for (auto & poSM : m_vecSMList)
{
if (poSM->SMID() == iSMID)
{
return poSM->Execute(iGroupIdx, llInstanceID, sBodyValue, poSMCtx);
}
}
PLG1Err("Unknown smid %d instanceid %lu", iSMID, llInstanceID);
return false;
}
class StateMachine
{
public:
virtual ~StateMachine() {}
//獲取狀態機的標識符
virtual const int SMID() const = 0;
//執行狀態機
virtual bool Execute(const int iGroupIdx, const uint64_t llInstanceID,
const std::string & sPaxosValue, SMCtx * poSMCtx) = 0;
//真正發起Propose之前,調用狀態機中該函數,修改請求數據或做其他處理
virtual void BeforePropose(const int iGroupIdx, std::string & sValue);
//是否需要調用BeforePropose,默認爲false
virtual const bool NeedCallBeforePropose();
//Checkpoint機制相關函數
virtual bool ExecuteForCheckpoint(const int iGroupIdx, const uint64_t llInstanceID,
const std::string & sPaxosValue);
virtual const uint64_t GetCheckpointInstanceID(const int iGroupIdx) const;
virtual int LockCheckpointState();
virtual int GetCheckpointState(const int iGroupIdx, std::string & sDirPath,
std::vector<std::string> & vecFileList);
virtual void UnLockCheckpointState();
virtual int LoadCheckpointState(const int iGroupIdx, const std::string & sCheckpointTmpFileDirPath,
const std::vector<std::string> & vecFileList, const uint64_t llCheckpointInstanceID);
};
狀態機的使用方法其實很簡單,定義一個狀態機後,這是一個SMID(每個狀態機都要不一樣),propose的時候指定狀態機ID即可:
//狀態機上下文,包括SMID,和用戶自定義的上下文的數據m_pCtx
class SMCtx
{
public:
SMCtx();
SMCtx(const int iSMID, void * pCtx);
int m_iSMID;
void * m_pCtx;
};
class Node
{
public:
Node() { }
virtual ~Node() { }
//If you want to end paxos, just delete poNode.
//But if you use your own network, poNode can be deleted after your own network stop recieving messages.
static int RunNode(const Options & oOptions, Node *& poNode);
//Base function.
virtual int Propose(const int iGroupIdx, const std::string & sValue, uint64_t & llInstanceID) = 0;
// 最後一個參數制定狀態機即可。
virtual int Propose(const int iGroupIdx, const std::string & sValue, uint64_t & llInstanceID, SMCtx * poSMCtx) = 0;
...
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.