PhxPaxos原始碼分析——Paxos演算法實現

lzslbd發表於2019-03-04

可以進入我的部落格檢視原文。

這篇主要來分析Paxos演算法實現的部分,我想這應該也是讀者最感興趣的。在看這篇文章之前,如果之前對Paxos演算法沒有了解的童鞋可以看下這篇文章:Paxos演算法原理與推導,相信瞭解Paxos演算法後再來通過原始碼看演算法實現應該會很酸爽。

Paxos演算法中最重要的兩個角色是ProposerAcceptor。當然Leaner也很重要,特別是在PhxPaxos的實現中,Leaner具有重要的功能。但是因為《Paxos Made Simple》論文中主要還是Proposer和Acceptor,因此這篇文章還是以這兩個角色為主,通過原始碼來回顧論文中Paxos演算法的過程,同時也看看工程實現和論文的描述有什麼區別。

這裡先貼出Paxos演算法的過程,方便大家對照接下來的工程實現。

  • Prepare階段:

    (a) Proposer選擇一個提案編號N,然後向半數以上的Acceptor傳送編號為N的Prepare請求。

    (b) 如果一個Acceptor收到一個編號為N的Prepare請求,且N大於該Acceptor已經響應過的所有Prepare請求的編號,那麼它就會將它已經接受過的編號最大的提案(如果有的話)作為響應反饋給Proposer,同時該Acceptor承諾不再接受任何編號小於N的提案。

  • Accept階段:

    (a) 如果Proposer收到半數以上Acceptor對其發出的編號為N的Prepare請求的響應,那麼它就會傳送一個針對[N,V]提案的Accept請求給半數以上的Acceptor。注意:V就是收到的響應中編號最大的提案的value,如果響應中不包含任何提案,那麼V就由Proposer自己決定。

    (b) 如果Acceptor收到一個針對編號為N的提案的Accept請求,只要該Acceptor沒有對編號大於N的Prepare請求做出過響應,它就接受該提案。

Proposer

因為Proposer需要維護或者說記錄一些狀態資訊,包括自己的提案編號ProposalID、提出的Value、其他Proposer提出的最大的提案編號HighestOtherProposalID、Acceptor已經接受過的編號最大的提案的值等,因此這裡專門有一個ProposerState類來管理這些資訊。同樣Acceptor也有一個AcceptorState類來管理Acceptor相關的資訊。

先來看下ProposerState的定義:

class ProposerState
{
public:
    ProposerState(const Config * poConfig);
    ~ProposerState();

    void Init();

    void SetStartProposalID(const uint64_t llProposalID);

    void NewPrepare();

    void AddPreAcceptValue(const BallotNumber & oOtherPreAcceptBallot, const std::string & sOtherPreAcceptValue);

    /////////////////////////

    const uint64_t GetProposalID();

    const std::string & GetValue();

    void SetValue(const std::string & sValue);

    void SetOtherProposalID(const uint64_t llOtherProposalID);

    void ResetHighestOtherPreAcceptBallot();

public:
    uint64_t m_llProposalID;
    uint64_t m_llHighestOtherProposalID;
    std::string m_sValue;

    BallotNumber m_oHighestOtherPreAcceptBallot;

    Config * m_poConfig;
};複製程式碼

基本都是對這些資訊的set跟get,很容易理解。直接來看Proposer類的定義:

class Proposer : public Base
{
public:
    Proposer(
            const Config * poConfig, 
            const MsgTransport * poMsgTransport,
            const Instance * poInstance,
            const Learner * poLearner,
            const IOLoop * poIOLoop);
    ~Proposer();

    //設定起始的ProposalID
    void SetStartProposalID(const uint64_t llProposalID);

    //初始化新的一輪Paxos過程,每一輪叫做一個Paxos Instance,每一輪確定一個值
    virtual void InitForNewPaxosInstance();

    //Proposer發起提案的入口函式。引數sValue即Proposer自己想提出的value,當然最終提出的value不一定是這個,需要根據Acceptor再Prepare階段的回覆來確定
    int NewValue(const std::string & sValue);

    //判斷Proposer是否處於Prepare階段或Accept階段
    bool IsWorking();

    /////////////////////////////

    //對應Paxos演算法中的Prepare階段
    void Prepare(const bool bNeedNewBallot = true);

    //Prepare階段等待Acceptor的回覆,統計投票並確定是否進入Accept階段
    void OnPrepareReply(const PaxosMsg & oPaxosMsg);

    //Prepare階段被拒絕
    void OnExpiredPrepareReply(const PaxosMsg & oPaxosMsg);

    //對應Paxos演算法中的Accept階段
    void Accept();

    //Accept階段等待Acceptor的回覆,統計投票並確定值(Value)是否被選定(Chosen)
    void OnAcceptReply(const PaxosMsg & oPaxosMsg);

    //Accept階段被拒絕
    void OnExpiredAcceptReply(const PaxosMsg & oPaxosMsg);

    //Prepare階段超時
    void OnPrepareTimeout();

    //Accept階段超時
    void OnAcceptTimeout();

    //退出Prepare階段
    void ExitPrepare();

    //退出Accept階段
    void ExitAccept();

    //取消跳過Prepare階段,也就是必須先Prepare階段再Accept階段
    void CancelSkipPrepare();

    /////////////////////////////

    void AddPrepareTimer(const int iTimeoutMs = 0);

    void AddAcceptTimer(const int iTimeoutMs = 0);

public:
    ProposerState m_oProposerState;
    MsgCounter m_oMsgCounter;
    Learner * m_poLearner;

    bool m_bIsPreparing;
    bool m_bIsAccepting;

    IOLoop * m_poIOLoop;

    uint32_t m_iPrepareTimerID;
    int m_iLastPrepareTimeoutMs;
    uint32_t m_iAcceptTimerID;
    int m_iLastAcceptTimeoutMs;
    uint64_t m_llTimeoutInstanceID;

    bool m_bCanSkipPrepare;

    bool m_bWasRejectBySomeone;

    TimeStat m_oTimeStat;
};複製程式碼

NewValue

下面就從NewValue方法入手:

int Proposer :: NewValue(const std::string & sValue)
{
    BP->GetProposerBP()->NewProposal(sValue);

    if (m_oProposerState.GetValue().size() == 0)
    {
        m_oProposerState.SetValue(sValue);
    }

    m_iLastPrepareTimeoutMs = START_PREPARE_TIMEOUTMS;
    m_iLastAcceptTimeoutMs = START_ACCEPT_TIMEOUTMS;

    //如果可以跳過Prepare階段並且沒有被Acceptor拒絕過,則直接進入Accept階段
    if (m_bCanSkipPrepare && !m_bWasRejectBySomeone)
    {
        BP->GetProposerBP()->NewProposalSkipPrepare();

        PLGHead("skip prepare, directly start accept");
        Accept();
    }

    //否則先進入Prepare階段
    else
    {
        //if not reject by someone, no need to increase ballot
        Prepare(m_bWasRejectBySomeone);
    }

    return 0;
}複製程式碼

這裡可以直接進入Accept階段的前提是該Proposer已經發起過Prepare請求且得到半數以上的同意(即通過了Prepare階段),並且沒有被任何Acceptor拒絕(說明沒有Acceptor響應過比該Proposer的提案編號更高的提案)。那麼,什麼情況下可以跳過Prepare請求呢,這裡應該對應的是選出一個master的情況?相當於raft裡的leader?

Prepare

接下來直接看Prepare階段:

void Proposer :: Prepare(const bool bNeedNewBallot)
{
    PLGHead("START Now.InstanceID %lu MyNodeID %lu State.ProposalID %lu State.ValueLen %zu",
            GetInstanceID(), m_poConfig->GetMyNodeID(), m_oProposerState.GetProposalID(),
            m_oProposerState.GetValue().size());

    BP->GetProposerBP()->Prepare();
    m_oTimeStat.Point();

    ExitAccept();

    //表明Proposer正處於Prepare階段
    m_bIsPreparing = true;

    //不能跳過Prepare階段
    m_bCanSkipPrepare = false;

    //目前還未被任意一個Acceptor拒絕
    m_bWasRejectBySomeone = false;

    m_oProposerState.ResetHighestOtherPreAcceptBallot();

    //如果需要產生新的投票,就呼叫NewPrepare產生新的ProposalID,新的ProposalID為當前已知的最大ProposalID+1
    if (bNeedNewBallot)
    {
        m_oProposerState.NewPrepare();
    }

    PaxosMsg oPaxosMsg;

    //設定Prepare訊息的各個欄位
    oPaxosMsg.set_msgtype(MsgType_PaxosPrepare);
    oPaxosMsg.set_instanceid(GetInstanceID());
    oPaxosMsg.set_nodeid(m_poConfig->GetMyNodeID());
    oPaxosMsg.set_proposalid(m_oProposerState.GetProposalID());

    //MsgCount是專門用來統計票數的,根據計算的結果確定是否通過Prepare階段或者Accept階段
    m_oMsgCounter.StartNewRound();

    //Prepare超時定時器
    AddPrepareTimer();

    PLGHead("END OK");

    //將Prepare訊息傳送到各個節點
    BroadcastMessage(oPaxosMsg);
}複製程式碼

Proposer在Prepare階段主要做了這麼幾件事:

  1. 重置各個狀態位,表明當前正處於Prepare階段。
  2. 獲取提案編號ProposalID。當bNeedNewBallot為true時需要將ProposalID+1。否則沿用之前的ProposalID。bNeedNewBallot是在NewValue中呼叫Prepare方法時傳入的m_bWasRejectBySomeone引數。也就是如果之前沒有被任何Acceptor拒絕(說明還沒有明確出現更大的ProposalID),則不需要獲取新的ProposalID。對應的場景是Prepare階段超時了,在超時時間內沒有收到過半Acceptor同意的訊息,因此需要重新執行Prepare階段,此時只需要沿用原來的ProposalID即可。
  3. 傳送Prepare請求。該請求PaxosMsg是Protocol Buffer定義的一個message,包含MsgType、InstanceID、NodeID、ProposalID等欄位。在BroadcastMessage(oPaxosMsg)中還會將oPaxosMsg序列化後才傳送出去。

PaxosMsg的定義如下,Prepare和Accept階段Proposer和Acceptor的所有訊息都用PaxosMsg來表示:

message PaxosMsg
{
    required int32 MsgType = 1;
    optional uint64 InstanceID = 2;
    optional uint64 NodeID = 3;
    optional uint64 ProposalID = 4;
    optional uint64 ProposalNodeID = 5;
    optional bytes Value = 6;
    optional uint64 PreAcceptID = 7;
    optional uint64 PreAcceptNodeID = 8;
    optional uint64 RejectByPromiseID = 9;
    optional uint64 NowInstanceID = 10;
    optional uint64 MinChosenInstanceID = 11;
    optional uint32 LastChecksum = 12;
    optional uint32 Flag = 13;
    optional bytes SystemVariables = 14;
    optional bytes MasterVariables = 15;
};複製程式碼

OnPrepareReply

Proposer發出Prepare請求後就開始等待Acceptor的回覆。當Proposer所在節點收到PaxosPrepareReply訊息後,就會呼叫Proposer的OnPrepareReply(oPaxosMsg),其中oPaxosMsg是Acceptor回覆的訊息。

void Proposer :: OnPrepareReply(const PaxosMsg & oPaxosMsg)
{
    PLGHead("START Msg.ProposalID %lu State.ProposalID %lu Msg.from_nodeid %lu RejectByPromiseID %lu",
            oPaxosMsg.proposalid(), m_oProposerState.GetProposalID(), 
            oPaxosMsg.nodeid(), oPaxosMsg.rejectbypromiseid());

    BP->GetProposerBP()->OnPrepareReply();

    //如果Proposer不是在Prepare階段,則忽略該訊息
    if (!m_bIsPreparing)
    {
        BP->GetProposerBP()->OnPrepareReplyButNotPreparing();
        //PLGErr("Not preparing, skip this msg");
        return;
    }

    //如果ProposalID不同,也忽略
    if (oPaxosMsg.proposalid() != m_oProposerState.GetProposalID())
    {
        BP->GetProposerBP()->OnPrepareReplyNotSameProposalIDMsg();
        //PLGErr("ProposalID not same, skip this msg");
        return;
    }

    //加入一個收到的訊息,用於MsgCounter統計
    m_oMsgCounter.AddReceive(oPaxosMsg.nodeid());

    //如果該訊息不是拒絕,即Acceptor同意本次Prepare請求
    if (oPaxosMsg.rejectbypromiseid() == 0)
    {
        BallotNumber oBallot(oPaxosMsg.preacceptid(), oPaxosMsg.preacceptnodeid());
        PLGDebug("[Promise] PreAcceptedID %lu PreAcceptedNodeID %lu ValueSize %zu", 
                oPaxosMsg.preacceptid(), oPaxosMsg.preacceptnodeid(), oPaxosMsg.value().size());
        //加入MsgCounter用於統計投票
        m_oMsgCounter.AddPromiseOrAccept(oPaxosMsg.nodeid());
        //將Acceptor返回的它接受過的編號最大的提案記錄下來(如果有的話),用於確定Accept階段的Value
        m_oProposerState.AddPreAcceptValue(oBallot, oPaxosMsg.value());
    }

    //Acceptor拒絕了Prepare請求
    else
    {
        PLGDebug("[Reject] RejectByPromiseID %lu", oPaxosMsg.rejectbypromiseid());

        //同樣也要記錄到MsgCounter用於統計投票
        m_oMsgCounter.AddReject(oPaxosMsg.nodeid());

        //記錄被Acceptor拒絕過,待會兒如果重新進入Prepare階段的話就需要獲取更大的ProposalID
        m_bWasRejectBySomeone = true;

        //記錄下別的Proposer提出的更大的ProposalID。這樣重新發起Prepare請求時才知道需要用多大的ProposalID
        m_oProposerState.SetOtherProposalID(oPaxosMsg.rejectbypromiseid());
    }


    //本次Prepare請求通過了。也就是得到了半數以上Acceptor的同意
    if (m_oMsgCounter.IsPassedOnThisRound())
    {
        int iUseTimeMs = m_oTimeStat.Point();
        BP->GetProposerBP()->PreparePass(iUseTimeMs);
        PLGImp("[Pass] start accept, usetime %dms", iUseTimeMs);
        m_bCanSkipPrepare = true;

        //進入Accept階段
        Accept();
    }

    //本次Prepare請求沒有通過
    else if (m_oMsgCounter.IsRejectedOnThisRound()
            || m_oMsgCounter.IsAllReceiveOnThisRound())
    {
        BP->GetProposerBP()->PrepareNotPass();
        PLGImp("[Not Pass] wait 30ms and restart prepare");

         //隨機等待一段時間後重新發起Prepare請求
        AddPrepareTimer(OtherUtils::FastRand() % 30 + 10);
    }

    PLGHead("END");
}複製程式碼

該階段Proposer主要做了以下事情:

  1. 判斷訊息是否有效。包括ProposalID是否相同,自身是否處於Prepare階段等。因為網路是不可靠的,有些訊息可能延遲很久,等收到的時候已經不需要了,所以需要做這些判斷。

  2. 將收到的訊息加入MsgCounter用於統計。

  3. 根據收到的訊息更新自身狀態。包括Acceptor承諾過的ProposalID,以及Acceptor接受過的編號最大的提案等。

  4. 根據MsgCounter統計的Acceptor投票結果決定是進入Acceptor階段還是重新發起Prepare請求。這裡如果判斷需要重新發起Prepare請求的話,也不是立即進行,而是等待一段隨機的時間,這樣做的好處是減少不同Proposer之間的衝突,採取的策略跟raft中leader選舉衝突時在一段隨機的選舉超時時間後重新發起選舉的做法類似。

注:這裡跟Paxos演算法中提案編號對應的並不是ProposalID,而是BallotNumber。BallotNumber由ProposalID和NodeID組成。還實現了運算子過載。如果ProposalID大,則BallotNumber(即提案編號)大。在ProposalID相同的情況下,NodeID大的BallotNumber大。

Accept

接下來Proposer就進入Accept階段:

void Proposer :: Accept()
{
    PLGHead("START ProposalID %lu ValueSize %zu ValueLen %zu", 
            m_oProposerState.GetProposalID(), m_oProposerState.GetValue().size(), m_oProposerState.GetValue().size());

    BP->GetProposerBP()->Accept();
    m_oTimeStat.Point();

    ExitPrepare();
    m_bIsAccepting = true;

    //設定Accept請求的訊息內容
    PaxosMsg oPaxosMsg;
    oPaxosMsg.set_msgtype(MsgType_PaxosAccept);
    oPaxosMsg.set_instanceid(GetInstanceID());
    oPaxosMsg.set_nodeid(m_poConfig->GetMyNodeID());
    oPaxosMsg.set_proposalid(m_oProposerState.GetProposalID());
    oPaxosMsg.set_value(m_oProposerState.GetValue());
    oPaxosMsg.set_lastchecksum(GetLastChecksum());

    m_oMsgCounter.StartNewRound();

    AddAcceptTimer();

    PLGHead("END");

    //發給各個節點
    BroadcastMessage(oPaxosMsg, BroadcastMessage_Type_RunSelf_Final);
}複製程式碼

Accept請求中PaxosMsg裡的Value是這樣確定的:如果Prepare階段有Acceptor的回覆中帶有提案值,則該Value為所有的Acceptor的回覆中,編號最大的提案的值。否則就是Proposer在最初呼叫NewValue時傳入的值。

OnAcceptReply

void Proposer :: OnAcceptReply(const PaxosMsg & oPaxosMsg)
{
    PLGHead("START Msg.ProposalID %lu State.ProposalID %lu Msg.from_nodeid %lu RejectByPromiseID %lu",
            oPaxosMsg.proposalid(), m_oProposerState.GetProposalID(), 
            oPaxosMsg.nodeid(), oPaxosMsg.rejectbypromiseid());

    BP->GetProposerBP()->OnAcceptReply();

    if (!m_bIsAccepting)
    {
        //PLGErr("Not proposing, skip this msg");
        BP->GetProposerBP()->OnAcceptReplyButNotAccepting();
        return;
    }

    if (oPaxosMsg.proposalid() != m_oProposerState.GetProposalID())
    {
        //PLGErr("ProposalID not same, skip this msg");
        BP->GetProposerBP()->OnAcceptReplyNotSameProposalIDMsg();
        return;
    }

    m_oMsgCounter.AddReceive(oPaxosMsg.nodeid());

    if (oPaxosMsg.rejectbypromiseid() == 0)
    {
        PLGDebug("[Accept]");
        m_oMsgCounter.AddPromiseOrAccept(oPaxosMsg.nodeid());
    }
    else
    {
        PLGDebug("[Reject]");
        m_oMsgCounter.AddReject(oPaxosMsg.nodeid());

        m_bWasRejectBySomeone = true;

        m_oProposerState.SetOtherProposalID(oPaxosMsg.rejectbypromiseid());
    }

    if (m_oMsgCounter.IsPassedOnThisRound())
    {
        int iUseTimeMs = m_oTimeStat.Point();
        BP->GetProposerBP()->AcceptPass(iUseTimeMs);
        PLGImp("[Pass] Start send learn, usetime %dms", iUseTimeMs);
        ExitAccept();

        //讓Leaner學習被選定(Chosen)的值
        m_poLearner->ProposerSendSuccess(GetInstanceID(), m_oProposerState.GetProposalID());
    }
    else if (m_oMsgCounter.IsRejectedOnThisRound()
            || m_oMsgCounter.IsAllReceiveOnThisRound())
    {
        BP->GetProposerBP()->AcceptNotPass();
        PLGImp("[Not pass] wait 30ms and Restart prepare");
        AddAcceptTimer(OtherUtils::FastRand() % 30 + 10);
    }

    PLGHead("END");
}複製程式碼

這裡跟OnPrepareReply的過程基本一致,因此就不加太多註釋了。比較大的區別在於最後如果過半的Acceptor接受了該Accept請求,則說明該Value被選定(Chosen)了,就傳送訊息,讓每個節點上的Learner學習該Value。因為Leaner不是本文的重點,這裡就不詳細介紹了。

Acceptor

Acceptor的邏輯比Proposer更簡單。同樣先看它的定義:

class Acceptor : public Base
{
public:
    Acceptor(
            const Config * poConfig, 
            const MsgTransport * poMsgTransport, 
            const Instance * poInstance,
            const LogStorage * poLogStorage);
    ~Acceptor();

    virtual void InitForNewPaxosInstance();

    int Init();

    AcceptorState * GetAcceptorState();

    //Prepare階段回覆Prepare請求
    int OnPrepare(const PaxosMsg & oPaxosMsg);

    //Accept階段回覆Accept請求
    void OnAccept(const PaxosMsg & oPaxosMsg);

//private:
    AcceptorState m_oAcceptorState;
};複製程式碼

OnPrepare

OnPrepare用於處理收到的Prepare請求,邏輯如下:

int Acceptor :: OnPrepare(const PaxosMsg & oPaxosMsg)
{
    PLGHead("START Msg.InstanceID %lu Msg.from_nodeid %lu Msg.ProposalID %lu",
            oPaxosMsg.instanceid(), oPaxosMsg.nodeid(), oPaxosMsg.proposalid());

    BP->GetAcceptorBP()->OnPrepare();

    PaxosMsg oReplyPaxosMsg;
    oReplyPaxosMsg.set_instanceid(GetInstanceID());
    oReplyPaxosMsg.set_nodeid(m_poConfig->GetMyNodeID());
    oReplyPaxosMsg.set_proposalid(oPaxosMsg.proposalid());
    oReplyPaxosMsg.set_msgtype(MsgType_PaxosPrepareReply);

    //構造接收到的Prepare請求裡的提案編號
    BallotNumber oBallot(oPaxosMsg.proposalid(), oPaxosMsg.nodeid());

    //提案編號大於承諾過的提案編號
    if (oBallot >= m_oAcceptorState.GetPromiseBallot())
    {
        PLGDebug("[Promise] State.PromiseID %lu State.PromiseNodeID %lu "
                "State.PreAcceptedID %lu State.PreAcceptedNodeID %lu",
                m_oAcceptorState.GetPromiseBallot().m_llProposalID, 
                m_oAcceptorState.GetPromiseBallot().m_llNodeID,
                m_oAcceptorState.GetAcceptedBallot().m_llProposalID,
                m_oAcceptorState.GetAcceptedBallot().m_llNodeID);

        //返回之前接受過的提案的編號
        oReplyPaxosMsg.set_preacceptid(m_oAcceptorState.GetAcceptedBallot().m_llProposalID);
        oReplyPaxosMsg.set_preacceptnodeid(m_oAcceptorState.GetAcceptedBallot().m_llNodeID);
        //如果接受過的提案編號大於0(<=0說明沒有接受過提案),則設定接受過的提案的Value
        if (m_oAcceptorState.GetAcceptedBallot().m_llProposalID > 0)
        {
            oReplyPaxosMsg.set_value(m_oAcceptorState.GetAcceptedValue());
        }

        //更新承諾的提案編號為新的提案編號(因為新的提案編號更大)
        m_oAcceptorState.SetPromiseBallot(oBallot);

        //資訊持久化
        int ret = m_oAcceptorState.Persist(GetInstanceID(), GetLastChecksum());
        if (ret != 0)
        {
            BP->GetAcceptorBP()->OnPreparePersistFail();
            PLGErr("Persist fail, Now.InstanceID %lu ret %d",
                    GetInstanceID(), ret);

            return -1;
        }

        BP->GetAcceptorBP()->OnPreparePass();
    }

    //提案編號小於承諾過的提案編號,需要拒絕
    else
    {
        BP->GetAcceptorBP()->OnPrepareReject();

        PLGDebug("[Reject] State.PromiseID %lu State.PromiseNodeID %lu", 
                m_oAcceptorState.GetPromiseBallot().m_llProposalID, 
                m_oAcceptorState.GetPromiseBallot().m_llNodeID);

        //拒絕該Prepare請求,並返回承諾過的ProposalID      
        oReplyPaxosMsg.set_rejectbypromiseid(m_oAcceptorState.GetPromiseBallot().m_llProposalID);
    }

    nodeid_t iReplyNodeID = oPaxosMsg.nodeid();

    PLGHead("END Now.InstanceID %lu ReplyNodeID %lu",
            GetInstanceID(), oPaxosMsg.nodeid());;

    //向發出Prepare請求的Proposer回覆訊息
    SendMessage(iReplyNodeID, oReplyPaxosMsg);

    return 0;
}複製程式碼

OnAccept

再來看看OnAccept:

void Acceptor :: OnAccept(const PaxosMsg & oPaxosMsg)
{
    PLGHead("START Msg.InstanceID %lu Msg.from_nodeid %lu Msg.ProposalID %lu Msg.ValueLen %zu",
            oPaxosMsg.instanceid(), oPaxosMsg.nodeid(), oPaxosMsg.proposalid(), oPaxosMsg.value().size());

    BP->GetAcceptorBP()->OnAccept();

    PaxosMsg oReplyPaxosMsg;
    oReplyPaxosMsg.set_instanceid(GetInstanceID());
    oReplyPaxosMsg.set_nodeid(m_poConfig->GetMyNodeID());
    oReplyPaxosMsg.set_proposalid(oPaxosMsg.proposalid());
    oReplyPaxosMsg.set_msgtype(MsgType_PaxosAcceptReply);

    BallotNumber oBallot(oPaxosMsg.proposalid(), oPaxosMsg.nodeid());

    //提案編號不小於承諾過的提案編號(注意:這裡是“>=”,而再OnPrepare中是“>”,可以先思考下為什麼),需要接受該提案
    if (oBallot >= m_oAcceptorState.GetPromiseBallot())
    {
        PLGDebug("[Promise] State.PromiseID %lu State.PromiseNodeID %lu "
                "State.PreAcceptedID %lu State.PreAcceptedNodeID %lu",
                m_oAcceptorState.GetPromiseBallot().m_llProposalID, 
                m_oAcceptorState.GetPromiseBallot().m_llNodeID,
                m_oAcceptorState.GetAcceptedBallot().m_llProposalID,
                m_oAcceptorState.GetAcceptedBallot().m_llNodeID);

        //更新承諾的提案編號;接受的提案編號、提案值
        m_oAcceptorState.SetPromiseBallot(oBallot);
        m_oAcceptorState.SetAcceptedBallot(oBallot);
        m_oAcceptorState.SetAcceptedValue(oPaxosMsg.value());

        //資訊持久化
        int ret = m_oAcceptorState.Persist(GetInstanceID(), GetLastChecksum());
        if (ret != 0)
        {
            BP->GetAcceptorBP()->OnAcceptPersistFail();

            PLGErr("Persist fail, Now.InstanceID %lu ret %d",
                    GetInstanceID(), ret);

            return;
        }

        BP->GetAcceptorBP()->OnAcceptPass();
    }

    //需要拒絕該提案
    else
    {
        BP->GetAcceptorBP()->OnAcceptReject();

        PLGDebug("[Reject] State.PromiseID %lu State.PromiseNodeID %lu", 
                m_oAcceptorState.GetPromiseBallot().m_llProposalID, 
                m_oAcceptorState.GetPromiseBallot().m_llNodeID);

        //拒絕的訊息中附上承諾過的ProposalID
        oReplyPaxosMsg.set_rejectbypromiseid(m_oAcceptorState.GetPromiseBallot().m_llProposalID);
    }

    nodeid_t iReplyNodeID = oPaxosMsg.nodeid();

    PLGHead("END Now.InstanceID %lu ReplyNodeID %lu",
            GetInstanceID(), oPaxosMsg.nodeid());

    //將響應傳送給Proposer
    SendMessage(iReplyNodeID, oReplyPaxosMsg);
}複製程式碼

結語

通過閱讀原始碼可以發現,整個PhxPaxos完全基於Lamport的《Paxos Made Simple》進行工程化,沒有進行任何演算法變種。這對於學習Paxos演算法的人來說真的是一筆寶貴的財富,所以如果對Paxos演算法感興趣,應該深入地去閱讀PhxPaxos的原始碼,相信看完後大家對Paxos會有更深的理解。同時我們也發現,在工程實現上還是有很多細節需要注意,這比單純理解演算法要難得多。

可以進入我的部落格檢視原文。

歡迎關注公眾號: FullStackPlan 獲取更多幹貨
歡迎關注公眾號: FullStackPlan 獲取更多幹貨

相關文章