phxpaxos狀態機的管理

1、phxpaxos內置的日誌系統

爲了應對系統故障、進程重啓之類的工程問題,phxpaxos須要對系統的狀態進行持久化存儲。從最簡單的狀況來講,系統必需要保證全部proposer提議的實例ID是連續遞增的,而這個信息自己就要求對系統的狀態進行持久化存儲。
可是,實例號的連續只是一個基礎要求。考慮到paxos算法自己就是爲了避免穩定系統的強一致性問題,那麼容許系統中的節點出現故障而後恢復的場景就不可避免。當一個節點從新啓動以後,它若是獲取整個系統的當前狀態,一樣是工程實現的一個重要問題。
爲了知足第二個需求,每一個節點能夠保存本身全部已經執行過的日誌,它的做用感受是至關於一個「我爲人人、人人爲我」的保險互助功能:因爲每一個節點都存儲了全部已經完成的協商信息,那麼當少數節點故障重啓以後,就能夠從系統的其它節點中同步歷史信息,從而進行追趕。
一樣是在工程實現中,若是節點保留全部的歷史信息,那麼這個數據量是不可控制的,因此此時引入checkpoint機制。全部的協商結果都是用於改變狀態機的狀態,若是可以把狀態機的狀態保存起來,那麼就能夠把狀態機以前的狀態刪除,這就至關因而一個同步點功能。舉一個簡單的例子,系統協商的是一個商品的價格,那麼不一樣的提議能夠設置不一樣的值(例如加10,減5等操做),每輪提議均可以修改該值,可是不管如何,最終決定出來的都是一個數值。當系統中有其它節點加入系統以後,它就能夠首先學習這個值,以後就能夠在這個狀態的基礎上操做。再用一個更常見的例子來講明,當數據庫主備遷移的時候,一般把當前的數據庫狀態拷貝到新機器,以後二者同時在接收並處理請求便可。node

2、phxpaxos的日誌實現

算法設計的目的是爲了保證一個值被「接受」以後再也不變化,因此問題的關鍵在於對acceptor狀態的持久化。acceptor的狀態包括它promise的值以及接受的值,因此在acceptor執行這些關鍵操做的時候就須要將這些信息進行持久化,系統中寫入日誌的場景包括下面三個:算法

一、promise時

int Acceptor :: OnPrepare(const PaxosMsg & oPaxosMsg)
{
……
int ret = m_oAcceptorState.Persist(GetInstanceID(), GetLastChecksum());
……
數據庫

二、accept時:

void Acceptor :: OnAccept(const PaxosMsg & oPaxosMsg)
{
……
int ret = m_oAcceptorState.Persist(GetInstanceID(), GetLastChecksum());
……
promise

三、learn時

int LearnerState :: LearnValue(const uint64_t llInstanceID, const BallotNumber & oLearnedBallot,
const std::string & sValue, const uint32_t iLastChecksum)
{
……
int ret = m_oPaxosLog.WriteState(oWriteOptions, m_poConfig->GetMyGroupIdx(), llInstanceID, oState);
……
學習

3、系統初始化時對InstanceID的處理ui

啓動時會從本地存儲中讀取最大的InstanceID數值。因爲leveldb支持這種獲取最大鍵值的形式,因此這個操做沒有問題。spa

int Acceptor :: Init()
{
uint64_t llInstanceID = 0;
int ret = m_oAcceptorState.Load(llInstanceID);
if (ret != 0)
{
NLErr("Load State fail, ret %d", ret);
return ret;
}設計

if (llInstanceID == 0)
{
PLGImp("Empty database");
}日誌

SetInstanceID(llInstanceID);進程

PLGImp("OK");

return 0;
}

4、給其它節點發送系統acceptor狀態

const bool LearnerSender :: Comfirm(const uint64_t llBeginInstanceID, const nodeid_t iSendToNodeID)

{
m_oLock.Lock();

bool bComfirmRet = false;

if (IsIMSending() && (!m_bIsComfirmed))
{
if (m_llBeginInstanceID == llBeginInstanceID && m_iSendToNodeID == iSendToNodeID)
{
bComfirmRet = true;

m_bIsComfirmed = true;
m_oLock.Interupt();
}
}

m_oLock.UnLock();

return bComfirmRet;
}

5、prepare、accept、learn對同一個InstanceID的操做

一個InstanceID的chosen可能要陸續通過這三次持久化,可是因爲這個InstanceID是惟一的,因此以後的更新並不會產生新的實例,只是更新實例的狀態。

6、當其它節點學習的時候,如何避免把prepare和accept狀態下的未完成節點同步過去

發送的上限是learner中保存的已經學習到的實例,prepare和accept狀態下的InstanceID大於m_poLearner->GetInstanceID()void LearnerSender :: SendLearnedValue(const uint64_t llBeginInstanceID, const nodeid_t iSendToNodeID){…… int iSendCount = 0; while (llSendInstanceID < m_poLearner->GetInstanceID()) { ……}

相關文章
相關標籤/搜索