在paxos算法中,主要包含了三个角色,proposer、accepter和learner。learner在paxos算法的论文中提及不是很详细,但是在phxpaxos实现是最为复杂的,之后独立一篇文章分析。
在微信的文档中,通过推导,一步一步得出simple-paxos正式算法的流程,推导的过程可以反复看看原先的文档(
Paxos理论介绍(1): 朴素Paxos算法理论推导与证明),理解透了收益非浅。
这里主要分析phxpaxos的proposer和Acceptor代码实现算法的过程。
先看看算法的主要过程,给出的一页PPT如下:
在phxpaxos的代码中,整理出主要流程如下图,
在图中,左边是propoer的流程,右边是accepter的流程。其实就是最简单的方式实现了文档中paxos算法的流程。这里按照代码的主要流程来进行一步步分析。
首先,Propose的处理:
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;
if (m_bCanSkipPrepare && !m_bWasRejectBySomeone)
{
//本节点之前已经执行过Prepare阶段,并且Prepare阶段或者Accept阶段没有被人拒绝过。。
BP->GetProposerBP()->NewProposalSkipPrepare();
PLGHead("skip prepare, directly start accept");
Accept();
}
else
{
//这里被人拒绝过就增加proposalID,否则,沿用之前的proposalID
//if not reject by someone, no need to increase ballot
Prepare(m_bWasRejectBySomeone);
}
return 0;
}
Propose的代码就是一些初始化并调用Prepare,这里有一个Multi-paxos的处理,就是有选择性的跳过Prepare,当前的proposer已经进行过了提交,并且在Prepare阶段或者Accept阶段没有被拒绝过,则跳过prepare阶段,具体的推导过程可以见(
Paxos理论介绍(2): Multi-Paxos与Leader)。
接着进入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();
//重置proposer的状态,退出Accept状态,进入Prepare状态
ExitAccept();
m_bIsPreparing = true;
m_bCanSkipPrepare = false;
m_bWasRejectBySomeone = false;
//是否需要重新分配ballot,被人拒绝过就需要重新分配
m_oProposerState.ResetHighestOtherPreAcceptBallot();
if (bNeedNewBallot)
{
m_oProposerState.NewPrepare();
}
PaxosMsg oPaxosMsg;
oPaxosMsg.set_msgtype(MsgType_PaxosPrepare);
oPaxosMsg.set_instanceid(GetInstanceID());
oPaxosMsg.set_nodeid(m_poConfig->GetMyNodeID());
oPaxosMsg.set_proposalid(m_oProposerState.GetProposalID());
m_oMsgCounter.StartNewRound();
//设置Prepare超时定时器
AddPrepareTimer();
PLGHead("END OK");
//发送Prepare消息。BroadcastMessage默认采用UDP方式发送,自己先执行,再发送给其他的节点
BroadcastMessage(oPaxosMsg);
}
Prepare主要判断是否需要重新分配ballot,并且增加一个Prepare定时器,超时重新进行Prepare。这里有一行代码: