phxpaxos状态机的管理

一、phxpaxos内置的日志系统

为了应对系统故障、进程重启之类的工程问题,phxpaxos需要对系统的状态进行持久化存储。从最简单的情况来说,系统必须要保证所有proposer提议的实例ID是连续递增的,而这个信息本身就要求对系统的状态进行持久化存储。
但是,实例号的连续只是一个基础要求。考虑到paxos算法本身就是为了不稳定系统的强一致性问题,那么允许系统中的节点出现故障然后恢复的场景就不可避免。当一个节点重新启动之后,它如果获取整个系统的当前状态,同样是工程实现的一个重要问题。
为了满足第二个需求,每个节点可以保存自己所有已经执行过的日志,它的作用感觉是相当于一个“我为人人、人人为我”的保险互助功能:由于每个节点都存储了所有已经完成的协商信息,那么当少数节点故障重启之后,就可以从系统的其它节点中同步历史信息,从而进行追赶。
同样是在工程实现中,如果节点保留所有的历史信息,那么这个数据量是不可控制的,所以此时引入checkpoint机制。所有的协商结果都是用于改变状态机的状态,如果能够把状态机的状态保存起来,那么就可以把状态机之前的状态删除,这就相当于是一个同步点功能。举一个简单的例子,系统协商的是一个商品的价格,那么不同的提议可以设置不同的值(例如加10,减5等操作),每轮提议都可以修改该值,但是无论如何,最终决定出来的都是一个数值。当系统中有其它节点加入系统之后,它就可以首先学习这个值,之后就可以在这个状态的基础上操作。再用一个更常见的例子来说明,当数据库主备迁移的时候,通常把当前的数据库状态拷贝到新机器,之后两者同时在接收并处理请求即可。

二、phxpaxos的日志实现

算法设计的目的是为了保证一个值被“接受”之后不再变化,所以问题的关键在于对acceptor状态的持久化。acceptor的状态包括它promise的值以及接受的值,所以在acceptor执行这些关键操作的时候就需要将这些信息进行持久化,系统中写入日志的场景包括下面三个:

1、promise时

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

2、accept时:

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

3、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);
……

三、系统初始化时对InstanceID的处理

启动时会从本地存储中读取最大的InstanceID数值。由于leveldb支持这种获取最大键值的形式,所以这个操作没有问题。

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

四、给其它节点发送系统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;
}

五、prepare、accept、learn对同一个InstanceID的操作

一个InstanceID的chosen可能要陆续经过这三次持久化,但是由于这个InstanceID是唯一的,所以之后的更新并不会产生新的实例,只是更新实例的状态。

六、当其它节点学习的时候,如何避免把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())
{
……
}

 

posted on 2019-04-01 17:50  tsecer  阅读(284)  评论(0编辑  收藏  举报

导航