Basic-Paxos算法通过多轮的Prepare/Accept过程来确定一个值,我们称这整个过程为一个Instance。
Multi-Paxos是通过Paxos算法来确定很多个值,而且这些值的顺序在各个节点完全一致。概括来讲就是确定一个全局顺序。
多个Instance怎么运作?首先我们先构建最简易的模式,各个Instance独立运作。
每个Instance独立运作一个Basic-Paxos算法,我们保证仅当Instance i的值被确定后,方可进行i+1的Paxos算法,这样我们就保证了Instance的有序性。
但这样效率是比较差的,Multi-Paxos算法希望找到多个Instance的Paxos算法之间的联系,从而尝试在某些情况去掉Prepare步骤。
下面一个Sample的演进情况来阐述这个算法,因为这个算法的要点其实非常简单,而且无需更多证明。
首先我们定义Multi-Paxos的参与要素:
3个参与节点 A/B/C.
Prepare(b) NodeA节点发起Prepare携带的编号。
Promise(b) NodeA节点承诺的编号。
Accept(b) NodeA节点发起Accept携带的编号
1(A)的意思是A节点产生的编号1, 2(B)代表编号2由B节点产生。 绿色表示Accept通过,红色表示拒绝
下图描述了A/B/C三个节点并行提交的演进过程:
这种情况下NodeA节点几乎每个Instance都收到其他节点发来的Prepare,导致Promise编号过大,迫使自己不断提升编号来Prepare。这种情况并未能找到任何的优化突破口。
下图描述了只有A节点提交的演进过程:
只有一个节点提交,每次Prepare的编号都是一样的。于是乎我们想,为何不把Promised(b)变成全局的?来看下图:
假设我们在Instance i进行Prepare(b),我们要求对这个b进行Promise的生效范围是Instance[i, ∞),那么在i之后我们就无需在做任何Prepare了。
可想而知,假设上图Instance 1之后都没有任何除NodeA之外其他节点的提交,我们就可以预期接下来Node A的Accept都是可以通过的。
那么这个去Prepare状态什么时候打破?我们来看有其他节点进行提交的情况:
Instance 4出现了B的提交,连续的Accept(b)被打断,必然是由于Promised(b)被提升了,也就是出现了其他节点的提交(提交会先Prepare从而提升b),使得Promised(b)变成了2(B), 从而导致Node A的Accept被拒绝。
而NodeA如何继续提交?必须得提高自己的Prepare编号从而抢占Promised(b)。
这里出现了很明显的去Prepare的窗口期Instance[1,3],而这种期间很明显的标志就是只有一个节点在提交。
经过一轮的Basic Paxos, 成功得到多数派accept的proposer即成为leader。Leader就这样无形的被产生出来了,我们压根没有刻意去“选举”,它就是来自于Multi-Paxos算法。
(Paxos随机睡眠-重试:有更高编号的提案被提出时,该proposer静默一段时间,而不是马上提出更高的方案。)
之后可以通过lease机制, 保持这个leader的身份, 使得其他proposer不再发起提案, 这样就进入了一个leader任期。
在leader任期中, 由于没有了并发冲突, 这个leader在对后续的操作投票时, 不必每次都向多数派询问提案, 也不必执行prepare阶段, 直接执行accept阶段即可。
为了遵守Basic Paxos协议约束, 在leader Election的prepare阶段, acceptor应答prepare成功的消息之前要先将这次prepare请求所携带的提案编号持久化到本地。允许非连续的日志空洞存在
1、Leader将最新的日志RPC发送给Acceptor, RPC(proposerId=16,value=2,logIndex=7,commitIndex=4)
2、Acceptor 对所有未提交的日志,判断其proposerId是否等于leader的proposerId,
Leader 发送RPC到Acceptor1 ,比对proposerId不同,返回RPC(logIndex=3),
leader再次发出Success(logIndex=3,value=20),Acceptor1将value=20,proposerId设置为∞,标志提交成功。
(正常情况下leader会异步通知Acceptor 进行日志空洞的补全)
leader commitIndex 之前的直接拷贝,无需通过paxos选举;之后的逐个发起完整paxos,补齐日志空洞。
使用Paxos协议处理日志的备份与恢复,可以保证确认形成多数派的日志不丢失,但是无法避免一种被称为“幽灵复现”的现象,如下图所示:
Leader | log-A | log-B | log-C | |
---|---|---|---|---|
第一轮 | A | 1-10 | 1-5 | 1-5 |
第二轮 | B | 宕机 | 1-6,20 | 1-6,20 |
第三轮 | A | 1-20 | 1-20 | 1-20 |
对于将Paxos协议应用在数据库日志同步场景的情况,“幽灵复现”问题是不可接受,一个简单的例子就是转账场景,用户转账时如果返回结果超时,那么往往会查询一下转账是否成功,来决定是否重试一下。如果第一次查询转账结果时,发现未生效而重试,而转账事务日志作为幽灵复现日志重新出现的话,就造成了用户重复转账。
为了处理“幽灵复现”问题,我们在每条日志的内容中保存一个generateID,leader在生成这条日志时以当前的leader ProposalID作为generateID。按logID顺序回放日志时,因为leader在开始服务之前一定会写一条StartWorking日志,所以如果出现generateID相对前一条日志变小的情况,说明这是一条“幽灵复现”日志(它的generateID会小于StartWorking日志),要忽略掉这条日志。
Multi-Paxos通过改变Promised(b)的生效范围至全局的Instance,从而使得一些唯一节点的连续提交获得去Prepare的效果。
很多人认为Multi-Paxos是由leader驱动去掉Prepare的,更有说在有Leader的情况下才能完成Multi-Paxos算法,这都是理解有误。大家看到这里也应该明白这里的因果关系,Basic-Paxos提出了leader概念并未进行优化。Multi-Paxos是适应某种请求特征情况下的优化,而不是要求请求满足这种特征。是Multi-Paxos协议并不假设全局必须只能有唯一的leader来生成日志,所以Multi-Paxos接受并行提交,它允许有多个“自认为是leader的server”来并发生成日志并行提交时退化到Basic-Paxos。
参考:
https://mp.weixin.qq.com/s?__biz=MzI4NDMyNTU2Mw==&mid=2247483798&idx=1&sn=42dd222ae255b13f1f67cd9e6d3f3dc0&scene=21#wechat_redirect