可以進入我的部落格檢視原文。
這篇主要來分析Paxos演算法實現的部分,我想這應該也是讀者最感興趣的。在看這篇文章之前,如果之前對Paxos演算法沒有了解的童鞋可以看下這篇文章:Paxos演算法原理與推導,相信瞭解Paxos演算法後再來通過原始碼看演算法實現應該會很酸爽。
Paxos演算法中最重要的兩個角色是Proposer和Acceptor。當然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階段主要做了這麼幾件事:
- 重置各個狀態位,表明當前正處於Prepare階段。
- 獲取提案編號ProposalID。當bNeedNewBallot為true時需要將ProposalID+1。否則沿用之前的ProposalID。bNeedNewBallot是在NewValue中呼叫Prepare方法時傳入的m_bWasRejectBySomeone引數。也就是如果之前沒有被任何Acceptor拒絕(說明還沒有明確出現更大的ProposalID),則不需要獲取新的ProposalID。對應的場景是Prepare階段超時了,在超時時間內沒有收到過半Acceptor同意的訊息,因此需要重新執行Prepare階段,此時只需要沿用原來的ProposalID即可。
- 傳送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主要做了以下事情:
-
判斷訊息是否有效。包括ProposalID是否相同,自身是否處於Prepare階段等。因為網路是不可靠的,有些訊息可能延遲很久,等收到的時候已經不需要了,所以需要做這些判斷。
-
將收到的訊息加入MsgCounter用於統計。
-
根據收到的訊息更新自身狀態。包括Acceptor承諾過的ProposalID,以及Acceptor接受過的編號最大的提案等。
-
根據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會有更深的理解。同時我們也發現,在工程實現上還是有很多細節需要注意,這比單純理解演算法要難得多。
可以進入我的部落格檢視原文。