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;
    ...
}  



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章