目錄
1. PhxPaxos源碼分析之關于PhxPaxos
2. PhxPaxos分析之網絡基礎部件
3. PhxPaxos源碼分析之Proposer、Acceptor
4. PhxPaxos源碼分析之Learner
5. PhxPaxos源碼分析之狀態機
6. PhxPaxos源碼分析之歸檔機制
7. PhxPaxos源碼分析之整體架構
5.1 基本概念
-
Paxos log
通過Paxos算法選中(chosen)的、有序的、一組提案值。例如,PhxSQL中使用PhxPaxos確定的有序值為binlog。 -
狀態機
業務自定義的,如何使用Paxos log的數據消費邏輯。狀態及的一個特點是:只要初始狀態一致,輸入一致,那么引出的最終狀態也是一致的。在PhxSQL中,這個狀態機就是binlog的replay機制,即在其他節點上執行和主節點一致的binlog操作,保證各個節點數據的一致性。
5.2 代碼設計
狀態機相關類的類圖如下:
-
SMFac
狀態機管理類,內部維護一個狀態機(StateMachine)列表,對外提供統一的狀態機訪問接口。 -
StateMachine
狀態機抽象類。允許PhxPaxos使用者繼承該類,定制自己的業務狀態機。并將該狀態機添加到管理類中。 -
InsideSM
內部狀態機抽象類。 -
SystemVSM
系統狀態機。用于處理集群節點變更。 -
MasterStateMachine
主節點狀態機。用于集群選主。
5.3 狀態機管理(SMFac)
PhxPaxos的每個Group(暫時理解為PhxPaxos)允許同時存在多個狀態機,但一個paxos log只能被一個狀態機消費,不同的狀態機的之間數據互相隔離。但他們共享同一份Group資源:Proposer、Acceptor、Learner、InstanceId等等。其中,SystemVSM,MasterStateMachine這兩個內置的狀態機默認添加到所有的Group之中。
SMFac做為管理類,除支持添加各種狀態機,還對外提供了統一的狀態機執行接口。
class SMFac
{
public:
//執行狀態機
bool Execute(const int iGroupIdx, const uint64_t llInstanceID,
const std::string& sPaxosValue, SMCtx* poSMCtx);
//執行狀態機的Checkpoint操作
bool ExecuteForCheckpoint(const int iGroupIdx, const uint64_t llInstanceID, const std::string& sPaxosValue);
//打包
void PackPaxosValue(std::string& sPaxosValue, const int iSMID = 0);
//添加狀態機
void AddSM(StateMachine* poSM);
public:
void BeforePropose(const int iGroupIdx, std::string& sValue);
void BeforeBatchPropose(const int iGroupIdx, std::string& sValue);
void BeforeProposeCall(const int iGroupIdx, const int iSMID, std::string& sValue, bool& change);
public:
const uint64_t GetCheckpointInstanceID(const int iGroupIdx) const;
//返回狀態機列表
std::vector<StateMachine*> GetSMList();
......
};
狀態機用于消費paxos log,即一旦paxos的提案值確定立即交由狀態機消費。入口邏輯如下:
int Instance :: ReceiveMsgForLearner(const PaxosMsg& oPaxosMsg)
{
//Learner消息處理邏輯
......
//當前Instance Id的提案值已習得
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;
}
......
}
}
SMExecute只是一個跳板函數,直接調用SMFact的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;
}
//必須為有效的SM ID
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;
}
//提前paxos log數據
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;
}
return BatchExecute(iGroupIdx, llInstanceID, sBodyValue, poBatchSMCtx);
}
else
{
//指定狀態機處理
return DoExecute(iGroupIdx, llInstanceID, sBodyValue, iSMID, poSMCtx);
}
}
前面提到一個paxos log只能被一個狀態機消費,但上面卻提供了一個狀態機批量處理的邏輯。是否互相矛盾呢?來看代碼實現:
bool SMFac :: BatchExecute(const int iGroupIdx, const uint64_t llInstanceID, const std::string& sBodyValue, BatchSMCtx* poBatchSMCtx)
{
BatchPaxosValues oBatchValues;
bool bSucc = oBatchValues.ParseFromArray(sBodyValue.data(), sBodyValue.size());
if (!bSucc)
{
PLG1Err("ParseFromArray fail, valuesize %zu", sBodyValue.size());
return false;
}
if (poBatchSMCtx != nullptr)
{
if ((int)poBatchSMCtx->m_vecSMCtxList.size() != oBatchValues.values_size())
{
PLG1Err("values size %d not equal to smctx size %zu",
oBatchValues.values_size(), poBatchSMCtx->m_vecSMCtxList.size());
return false;
}
}
//依次處理每條記錄
for (int i = 0; i < oBatchValues.values_size(); i++)
{
const PaxosValue& oValue = oBatchValues.values(i);
SMCtx* poSMCtx = poBatchSMCtx != nullptr ? poBatchSMCtx->m_vecSMCtxList[i] : nullptr;
bool bExecuteSucc = DoExecute(iGroupIdx, llInstanceID, oValue.value(), oValue.smid(), poSMCtx);
if (!bExecuteSucc)
{
return false;
}
}
return true;
}
實際上,批量的含義指的是數據中包含多個paxos log值,而非批量的對多個狀態機執行操作。依次處理每條paxos log時,調用的DoExecute攜帶了有效的sm id信息。DoExecute查找sm id配套的狀態機,交由狀態機消費數據。
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)
{
//查找正確的狀態機,消費paxos log
if (poSM->SMID() == iSMID)
{
return poSM->Execute(iGroupIdx, llInstanceID, sBodyValue, poSMCtx);
}
}
PLG1Err("Unknown smid %d instanceid %lu", iSMID, llInstanceID);
return false;
}
SMFac中的其他邏輯,此處不再贅述,有興趣可以參考源碼。
5.4 狀態機(StateMachine)
狀態機抽象類的接口定義如下:
class StateMachine
{
public:
virtual ~StateMachine() {}
//狀態機標識,需要保證唯一性。
virtual const int SMID() const = 0;
//狀態機執行函數,返回true意味著execute執行完成,不需要重新執行。
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機制相關---------------------------------------------------
//Checkpoint機制執行函數
virtual bool ExecuteForCheckpoint(const int iGroupIdx, const uint64_t llInstanceID,
const std::string& sPaxosValue);
//返回checkpoint已執行的、最大的instance id;PhxPaxos將頻繁調用此接口。
virtual const uint64_t GetCheckpointInstanceID(const int iGroupIdx) const;
//調用此接口鎖定Checkpoint文件,后續調用GetCheckpointState獲取的文件不允許有任何變更
virtual int LockCheckpointState();
//返回Checkpoint的文件路徑及文件列表
virtual int GetCheckpointState(const int iGroupIdx, std::string& sDirPath,
std::vector<std::string>& vecFileList);
//Checkpoint文件使用完畢,解鎖Checkpoint文件
virtual void UnLockCheckpointState();
//使用指定路徑的Checkpoint文件,PhxPaxos在執行完該函數后將重啟當前進程。
virtual int LoadCheckpointState(const int iGroupIdx, const std::string& sCheckpointTmpFileDirPath,
const std::vector<std::string>& vecFileList, const uint64_t llCheckpointInstanceID);
};
按功能劃分為如下兩類:
- 狀態機職責函數。包括狀態機標識(SMID)、狀態遷移(Execute)、BeforePropose、NeedCallBeforePropose。
- Checkpoint相關函數。上述代碼中由“Checkpoint機制相關”分割部分邏輯,這部分“Checkpoint機制”一章中還會提到。
5.5 集群變更狀態機(SystemVSM)
SystemVSM(System Variable State Machine)用于PhxPaxos集群成員變更,這是對外提供的一組功能接口,PhxPaxos本身不會主動觸發集群變更。對外接口定義在Node接口類中,如下:
class Node
{
public:
virtual int ShowMembership(const int iGroupIdx, NodeInfoList& vecNodeInfoList) = 0;
virtual int AddMember(const int iGroupIdx, const NodeInfo& oNode) = 0;
virtual int RemoveMember(const int iGroupIdx, const NodeInfo& oNode) = 0;
virtual int ChangeMember(const int iGroupIdx, const NodeInfo& oFromNode, const NodeInfo& oToNode) = 0;
}
注意:如需要啟用集群成員變更功能,需要設置Options中bUseMembership配置項為true。
集群的初始節點信息通過Options::vecNodeInfoList配置項傳遞給PhxPaxos。如果啟用了節點變更功能,將在首次發送消息前同步集群狀態。
void Instance :: CheckNewValue()
{
......
//啟用系統變更 and (首個提案實例 or 當前集群狀態需要更新)
if (m_poConfig->GetIsUseMembership()
&& (m_oProposer.GetInstanceID() == 0 || m_poConfig->GetGid() == 0))
{
//Init system variables.
PLGHead("Need to init system variables, Now.InstanceID %lu Now.Gid %lu",
m_oProposer.GetInstanceID(), m_poConfig->GetGid());
uint64_t llGid = OtherUtils::GenGid(m_poConfig->GetMyNodeID());
string sInitSVOpValue;
int ret = m_poConfig->GetSystemVSM()->CreateGid_OPValue(llGid, sInitSVOpValue);
assert(ret == 0);
//發起定向由SystemVSM處理的paxos消息
m_oSMFac.PackPaxosValue(sInitSVOpValue, m_poConfig->GetSystemVSM()->SMID());
m_oProposer.NewValue(sInitSVOpValue);
}
}
因為集群節點可能發生變更,因此集群信息不能再以配置文件中的Options::vecNodeInfoList為準,需要單獨做持久化。這部分邏輯在狀態機的Execute中觸發,調用UpdateSystemVariables完成。
int SystemVSM :: UpdateSystemVariables(const SystemVariables& oVariables)
{
WriteOptions oWriteOptions;
oWriteOptions.bSync = true;
//以選中的提案值為準,持久化到數據庫
int ret = m_oSystemVStore.Write(oWriteOptions, m_iMyGroupIdx, oVariables);
if (ret != 0)
{
PLG1Err("SystemVStore::Write fail, ret %d", ret);
return -1;
}
//以選中的提案值為準
m_oSystemVariables = oVariables;
RefleshNodeID();
return 0;
}
集群節點變更包括新增、修改、刪除,處理邏輯如下(引自《如何進行成員變更》):
在已有集群新增,替換機器
- 準備好新的機器,新的機器以空成員啟動PhxPaxos(Options::vecNodeInfoList設置為空)。
- 在任意一個已有的集群成員節點中調用Node::AddMember或Node::ChangeMember
在已有集群刪除機器
- 在任意一個已有的成員節點中調用Node::RemoveMember
如果所有節點都希望立即獲得這個成員變更操作的通知,可通過options.h設置Options::pMembershipChangeCallback回調函數。
代碼實現上,增、刪、改處理邏輯類似,以AddMember為例。
int PNode :: AddMember(const int iGroupIdx, const NodeInfo& oNode)
{
if (!CheckGroupID(iGroupIdx))
{
return Paxos_GroupIdxWrong;
}
SystemVSM* poSystemVSM = m_vecGroupList[iGroupIdx]->GetConfig()->GetSystemVSM();
if (poSystemVSM->GetGid() == 0)
{
return Paxos_MembershipOp_NoGid;
}
//獲取當前集群已有的節點列表
uint64_t llVersion = 0;
NodeInfoList vecNodeInfoList;
poSystemVSM->GetMembership(vecNodeInfoList, llVersion);
for (auto & oNodeInfo : vecNodeInfoList)
{
if (oNodeInfo.GetNodeID() == oNode.GetNodeID())
{
return Paxos_MembershipOp_Add_NodeExist;
}
}
//補充需要新增的節點信息
vecNodeInfoList.push_back(oNode);
//以完整的集群信息發起提案,即將該信息同步到各個節點
return ProposalMembership(poSystemVSM, iGroupIdx, vecNodeInfoList, llVersion);
}
5.6 選主服務狀態機(MasterStateMachine)
MasterStateMachine稱為主節點狀態機,用于和選主及租約相關操作的狀態維護、更新。除MasterStateMachine之外,另外一個和選主服務相關的類為MasterMgr,PhxPaxos為它啟動了一個單獨的線程。這次我們從MasterMgr開始說起。
MasterMgr內部組合了MasterStateMachine,也是MasterStateMachine的唯一實例點。
MasterStateMachine* MasterMgr :: GetMasterSM()
{
return &m_oDefaultMasterSM;
}
在PhxPaxos節點啟動時,為每個Group創一個選主服務對象,啟動選主服務(MasterMgr)。選主服務線程定期檢測主節點狀態、執行續約等操作。
void MasterMgr :: run()
{
m_bIsStarted = true;
while (true)
{
if (m_bIsEnd)
{
return;
}
//租約時長,單位毫秒
int iLeaseTime = m_iLeaseTime;
uint64_t llBeginTime = Time::GetSteadyClockMS();
//嘗試選主
TryBeMaster(iLeaseTime);
//計算下次選主操作的時間間隔
int iContinueLeaseTimeout = (iLeaseTime - 100) / 4;
iContinueLeaseTimeout = iContinueLeaseTimeout / 2 + OtherUtils::FastRand() % iContinueLeaseTimeout;
//如果需要主動觸發重新選主,將下次選主周期設置為:租約*2
if (m_bNeedDropMaster)
{
BP->GetMasterBP()->DropMaster();
m_bNeedDropMaster = false;
iContinueLeaseTimeout = iLeaseTime * 2;
PLG1Imp("Need drop master, this round wait time %dms", iContinueLeaseTimeout);
}
//實際間隔 = 計算間隔 - 本次操作運行時間
uint64_t llEndTime = Time::GetSteadyClockMS();
int iRunTime = llEndTime > llBeginTime ? llEndTime - llBeginTime : 0;
int iNeedSleepTime = iContinueLeaseTimeout > iRunTime ? iContinueLeaseTimeout - iRunTime : 0;
PLG1Imp("TryBeMaster, sleep time %dms", iNeedSleepTime);
Time::MsSleep(iNeedSleepTime);
}
}
假設租約時長為5秒,每次選主操作耗時500ms,那么選主間隔為:
step1: (5000 - 100) / 4 = 1225
step2: 1225 / 2 + rand() % 1225 = 612 + 600(0-1225的任意值) = 1212
step3: 1212 - 500 = 712 ms
也就是說,選主和續約周期大約為租約的1/4時間。
來看TryBeMaster:
void MasterMgr :: TryBeMaster(const int iLeaseTime)
{
nodeid_t iMasterNodeID = nullnode;
uint64_t llMasterVersion = 0;
//獲取當前的主節點信息
//step 1 check exist master and get version
m_oDefaultMasterSM.SafeGetMaster(iMasterNodeID, llMasterVersion);
//1. 如果當前已經存在主節點,那么需要執行續約動作,該動作只能由主節點發起
//2. 如果當前無主節點,需要執行選主動作,任意節點發起
if (iMasterNodeID != nullnode && (iMasterNodeID != m_poPaxosNode->GetMyNodeID()))
{
PLG1Imp("Ohter as master, can't try be master, masterid %lu myid %lu",
iMasterNodeID, m_poPaxosNode->GetMyNodeID());
return;
}
BP->GetMasterBP()->TryBeMaster();
//step 2 try be master
std::string sPaxosValue;
//構建選主、續約數據(node id、租約時長等)
if (!MasterStateMachine::MakeOpValue(
m_poPaxosNode->GetMyNodeID(),
llMasterVersion,
iLeaseTime,
MasterOperatorType_Complete,
sPaxosValue))
{
PLG1Err("Make paxos value fail");
return;
}
const int iMasterLeaseTimeout = iLeaseTime - 100;
uint64_t llAbsMasterTimeout = Time::GetSteadyClockMS() + iMasterLeaseTimeout;
uint64_t llCommitInstanceID = 0;
SMCtx oCtx;
oCtx.m_iSMID = MASTER_V_SMID;
oCtx.m_pCtx = (void*)&llAbsMasterTimeout;
//各節點間同步選主、續約數據
int ret = m_poPaxosNode->Propose(m_iMyGroupIdx, sPaxosValue, llCommitInstanceID, &oCtx);
if (ret != 0)
{
BP->GetMasterBP()->TryBeMasterProposeFail();
}
}
一旦節點發起了選主、續約操作到各個節點,各節點習得該數據后執行MasterStateMachine的Execute操作。Execute做了基本檢查后調用LearnMaster,直接看該函數。
int MasterStateMachine :: LearnMaster(
const uint64_t llInstanceID,
const MasterOperator& oMasterOper,
const uint64_t llAbsMasterTimeout)
{
std::lock_guard<std::mutex> oLockGuard(m_oMutex);
PLG1Debug("my last version %lu other last version %lu this version %lu instanceid %lu",
m_llMasterVersion, oMasterOper.lastversion(), oMasterOper.version(), llInstanceID);
//master version不一致,且由其他節點發送來的master version更新,嘗試使用新的master
if (oMasterOper.lastversion() != 0
&& llInstanceID > m_llMasterVersion
&& oMasterOper.lastversion() != m_llMasterVersion)
{
BP->GetMasterBP()->MasterSMInconsistent();
PLG1Err("other last version %lu not same to my last version %lu, instanceid %lu",
oMasterOper.lastversion(), m_llMasterVersion, llInstanceID);
//隨機修復?
if (OtherUtils::FastRand() % 100 < 50)
{
//try to fix online
PLG1Err("try to fix, set my master version %lu as other last version %lu, instanceid %lu",
m_llMasterVersion, oMasterOper.lastversion(), llInstanceID);
m_llMasterVersion = oMasterOper.lastversion();
}
}
//即便經過上面的修復嘗試,版本依舊不一致
if (oMasterOper.version() != m_llMasterVersion)
{
PLG1Debug("version conflit, op version %lu now master version %lu",
oMasterOper.version(), m_llMasterVersion);
return 0;
}
//將新主、續約時間等更新到數據庫中
int ret = UpdateMasterToStore(oMasterOper.nodeid(), llInstanceID, oMasterOper.timeout());
if (ret != 0)
{
PLG1Err("UpdateMasterToStore fail, ret %d", ret);
return -1;
}
//內存數據更新
m_iMasterNodeID = oMasterOper.nodeid();
if (m_iMasterNodeID == m_iMyNodeID)
{
//self be master
//use local abstimeout
m_llAbsExpireTime = llAbsMasterTimeout;
BP->GetMasterBP()->SuccessBeMaster();
PLG1Head("Be master success, absexpiretime %lu", m_llAbsExpireTime);
}
else
{
//other be master
//use new start timeout
m_llAbsExpireTime = Time::GetSteadyClockMS() + oMasterOper.timeout();
BP->GetMasterBP()->OtherBeMaster();
PLG1Head("Ohter be master, absexpiretime %lu", m_llAbsExpireTime);
}
m_iLeaseTime = oMasterOper.timeout();
m_llMasterVersion = llInstanceID;
PLG1Imp("OK, masternodeid %lu version %lu abstimeout %lu",
m_iMasterNodeID, m_llMasterVersion, m_llAbsExpireTime);
return 0;
}
總結如下:
- 每個Group啟動一個選主服務線程,定期觸發選主、續約操作。
- 通過Propose發起選主,選主成功后主節點信息持久化到數據庫。
- 主節點定時發起續約保活,周期約為租約的1/4。
- 節點重啟,優先沿用原有的主節點信息。
都已經到這了,但有一件事一直沒提。選了Master又能怎么樣呢?PhxPaxos在哪些場景下使用了Master這個身份?
答:PhxPaxos對Master角色沒有任何依賴,即便沒有Master,PhxPaxos依舊可以正常工作。Master是提供給外部業務使用的。
5.7 業務狀態機
業務狀態機即由業務實現的狀態機,負責業務自身邏輯。業務狀態機繼承自StateMachine并通過Node的AddStateMachine添加到PhxPaxos。
class Node
{
public:
//為所有Group添加狀態機
virtual void AddStateMachine(StateMachine* poSM) = 0;
//為指定Group添加狀態機
virtual void AddStateMachine(const int iGroupIdx, StateMachine* poSM) = 0;
......
}
那么如何使用狀態機呢?也非常簡單,在Node的Propose方法中指定SMCtx(State Machine Context),指明本次Propose配套的狀態機。
class SMCtx
{
public:
SMCtx();
SMCtx(const int iSMID, void* pCtx);
//狀態機ID
int m_iSMID;
//自定義數據
void* m_pCtx;
};
class Node
{
public:
//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; 0;
......
}
5.8 總結
本章講解了PhxPaxos中的狀態機、狀態機管理機制以及兩個內置的狀態機。每個節點的paxos log只會被一個狀態機消費,因此保存的paxos log中記錄了本條數據所屬的狀態機。
- 內置的SystemVSM用于處理集群節點變更,變更操作包括成員的新增、修改、刪除。它通過Propose方式完成集群間節點狀態變更。
- 內置的MasterStateMachine用于選舉服務,它配套的MasterMgr定時觸發選舉或者續約行為,保證整個集群有一個主節點。
- 業務狀態機通過Node提供的AddStateMachine接口添加到PhxPaxos,并通過Propose時指定處理狀態機。
上一節我們提到:“PhxPaxos并未對paxos做任何變種,甚至還做了一點簡化。”。看過狀態機的相關邏輯后,不知道各位是否能回答這個問題?所指的是《Paxos Made Simple》下面這部分邏輯并未實現:
In general, suppose a leader can get α commands ahead—that is, it can propose commands i + 1 through i +α after commands 1 through i are chosen. A gap of up to α?1 commands could then arise.
在我看來,PhxPaxos這么做應該是出于以下幾點考慮:
- 預取并發確定多個提案值,將導致程序復雜度指數上升。
- 通過支持BatchPropose同樣可以同時確定多個值。
至此,《Paxos Made Simple》中介紹的主要概念都已經講解完畢。但有一個問題:如果paxos持續運行,將產生大量的paxos log(選中的提案值)。存儲介質不可能支持無限量的數據,但同時新節點加入需要習得全部paxos log。paxos協議并沒有給出解決方案,那PhxPaxos又給出了什么方案呢?來看下一節。
【轉載請注明】隨安居士. 5. PhxPaxos源碼分析之狀態機. 2017.11.16