微信开源的PhxPaxos,可用于生产环境的库,有兴趣的可学习下源码
github地址:https://github.com/tencent-wechat/phxpaxos
InfoQ文章介绍:http://www.infoq.com/cn/articles/weinxin-open-source-paxos-phxpaxos/
首先关于Paxos的简单投票流程和相关约束条件,本文不再赘述,网上到处可查,阅读Lamport的原著论文是最原汁原味的,也是最有乐趣的。下面我就把我学习Basic-Paxos算法中的一些疑问记录下,希望对其他学习者能有帮助,也可能我理解的并不完全正确,不足之处大家共同探讨,欢迎批评指正。本文主要是讨论Basic-Paxos朴素算法,对于Multi-Paxos只讨论了leader的选举。
-【问题1】Instance(实例)是什么,能干什么?
在Lamport的论文第3节Implementing a state Machine中有如下语句“To guarantee that all servers execute the same sequence of state machine commands, we implement a sequence of separate instances of the Paxos consensus algorithm, the value chosen by the ith instance being the i th state machine command in the sequence. Each server plays all the roles (proposer,acceptor, and learner) in each instance of the algorithm.”
文中说instance包含了proposer,acceptor, learner三个角色,并且是“sequence of separate”(独立连续的),估计大部分人看到都会有点懵,instance是包含这3个角色的集合容易理解,可以把这3个角色同时实现在一个进程上,但是独立连续又是什么意思呢? 实际上,我们可以认为进程在生成1个instance时,给instance赋予一个id,叫做instanceId,每个角色内部都还有自己的instanceId,假设第一次投票所有角色instanceId=1,达成决议后,1就固化了,因为已经确定了一个值,所以进程把所有角色的环境&变量重置一遍,开启新的instance,同时赋予新的instanceId=2,所以就可以达到连续,instance之间因为所有环境变量等都重置过了,和上一轮没有任何关系,所以彼此之间也是独立的,但不同的instance之间,instanceId可能是不同的。
能干什么? 按照多数派达成决议的原则,最少3个不同进程的instance就可以达成一致性决议,所以每个instance都是一组具有投票和学习能力的完备集合体。
-【问题2】InstanceId和LogId是什么关系,LogId是什么?
在看一些论文时,有论文穿插提到LogId,难免造成一些不必要的疑惑,实际上LogId可以理解是在提交写请求时赋予的请求记录顺序Id,LogId连续,则按LogId顺序加载,依然可以得到一致的状态机,但是LogId是Multi-Paxos中的,选好了leader之后,保持logId连续就比较容易了。在Basic-Paxos中,instanceId才是理解的重点,Lamport提到的也是instanceId,正如问题1里面的说法。
-【问题3】对于Lamport论文中的instanceId“空洞”如何理解?
论文原文如下:”The new leader, being a learner in all instances of the consensus algorithm,
should know most of the commands that have already been chosen.Suppose it knows commands 1–134, 138, and 139—that is, the values chosen in instances 1–134, 138, and 139 of the consensus algorithm. (We will see later how such a gap in the command sequence could arise.) It then executes phase 1 of instances 135–137 and of all instances greater than 139.(I describe below how this is done.) Suppose that the outcome of these executions determine the value to be proposed in instances 135 and 140, but leaves the proposed value unconstrained in all other instances. The leader then executes phase 2 for instances 135 and 140, thereby choosing commands 135 and 140.“
主要是说在Multi-Paxos模式下,新的leader也具有learner的功能(实际上每个instance都有),所以它可以在发起learn的时候,学习到(用感知到更合适)其他人的已达成决议的instanceId,比如例子中的138-140,它就可以知道前面的流程已经走到140号instance了,但自己还在135号instance中,所以它必须迅速追上136,137,对于已经达成决议的instanceId直接学习就好,但是对于136和137,由于没有达成一致,所以需要重新投票,但如果136,137迟迟达不成决议,最后只能放弃,但是顺序不能中断,因此在136,137放置noop占位命令,表示空日志,啥也不做,记住:顺序一定要连续!
-【问题4】状态机是干啥的,什么时候执行状态机?
这个问题,需要从Paxos算法的作用开始说起,Paxos是为了解决多副本之间的数据一致性问题,这个数据是什么呢,随便,反正只能是1个value,你可以是KV系统中某个key对应的value,也可以是1段文件,1个图片,1段音频等等等等,你随便。Paxos就保证就存储的这个value在多个副本之间是一致的即可。这个说起来有点不知所云,在实际的业务系统中,我要1个value确实没什么用,业务需要的是一系列高一致性的value集合,并且具有CRUD接口,PhxPaxos的作者也有讲这个问题,可以细看下。
实际上根据我们上述讲的instance是连续的,那么对于每个进程,实际上大家的instance,除了部署不同机器,没有任何区别,那么连续的instance也保证大家执行的轮数也都一样,这样的话,如果每轮输入数据相同的话,那所有instance的输出结果必然是一致。再回到状态机的话题上,达成决议的value到哪里去了呢,实际上value就是给状态机用的,状态机是什么,就是业务侧定义的具体value存储操作引擎,比如你可以是一个内存型KV系统,value就对应的是对KV的CUD操作,不包含Read请求。每个instance对应的状态机执行相同的请求序列(Append Only Log Sequence),KV系统中的数据必然也是一致的。
状态机的执行是在确定value被chosen时,才能被执行,对于投票的Prepare阶段,value是未被chosen的,Commit只能说Proposer的提议曾经被多数派Prepare阶段达成一致,但并不表示Commit的值就是被chosen,以下图为例,对于5个节点的Paxos集群,假设:
1.A发起了proposalId=1的Prepare提议,得到了CD的响应(满足多数派),和BE未建立连接,但不影响投票;
2.B发起了proposalId=2的Prepare提议,得到了DE的响应(满足多数派),和AC未建立连接,D因为接受了B,所以对A的承诺已经作废,但此时A是不知道D的状态;
3.A以为自己满足多数派,发起了Commit,D显然是不会同意的,但C是不知道A的提议已经没有多数派支持了,所以C选择接受A的提议value,实际上C的这个value已经是错误的了,C如果选择Apply到状态机肯定是错误的;
4.C必须要向其他节点学习,是否proposalId=1的提议已经被chosen,当查询BDE时,BDE告诉C是proposalId=2的提议被chosen了(达到多数派),C才可以放心的将新的value值更新,并Apply到状态机。
-【问题5】Learner在Paxos中发挥的作用有多大?
看了Lamport的论文,对Learner的说明比较少,只是说Learner是用来学习的,但什么时候学习,学习完去干啥都讲的比较少。实际上Learner的作用在整个Paxos的作用非常大,对于2PC的正常情况的投票大家都能理解,但异常情况谁来解决,怎么解决,关于异常【问题4】有举1个例子,但还有更多的异常情况需要去思考解决。Leaner可以被acceptor驱动,但自己也是可以独立驱动的,即不停的去学习,和其他Learner的值做比较,去对照value是否被chosen,真心不是在打酱油的。
-【问题6】PaxosLog和Checkpoint的关系?
PaxosLog就是【问题4】中提到的Append Only Log Sequence,很多系统中称其为mutation log,就是一条条操作日志流水,该流水是只允许追加写的,不可重写,这样PaxosLog就会越来越大,如果不清除,磁盘也会放置不下,但是对于内存中的KeyValue正常情况下肯定比PaxosLog小很多。PaxosLog主要是用来防止内存数据丢失的,所以如果把内存KeyValue Dumo到磁盘,重启的时候,再读入进来,数据也完全不会丢失,PaxosLog就无用了,所以对内存数据的Dump到磁盘的文件就是Checkpoint,该文件也可以被用来学习,重建内存数据,和PaxosLog效果相同。
-【问题7】关于value是何时“chosen”
设有ABC三个节点的集群,当前Proposer&Acceptor&Learner的InstanceId=2,磁盘中CheckpointInstanceId=1,此时A发起了一次投票获得了自己和B的Promise,所以AB中的Acceptor都会同时把承诺PromiseBallot(InstanceId=2,ProposalId,Value)持久化,A获得了多数派的承诺,所以A的Proposer可以调动A中的Learner广播ProposerSendSuccess消息通知其他Learner,但是ProposerSendSuccess消息的发送无非就是两种结果(忽略C,因为C并未承诺,所以不关心):
A1:成功发送到B:B收到此消息,认为value已经被chosen,所以执行了状态机,并且写入了PaxosLog;
A2:未成功发送到B:B未收到此消息,但B中的Acceptor已经持久化了上述的信息;
OK,就在A发出ProposerSendSuccess消息后,A立即挂掉,甚至连A自己的Learner都未收到该消息,所以给Client的回复也是超时Timeout,过了一会A又重新启动了,现在ABC三台机器都在运行,Id状态分别是A(Proposer InstanceId=2,Acceptor InstanceId=2,Learner InstanceId=2,Checkpoint InstanceId=1),C(Proposer InstanceId=2,Acceptor InstanceId=2,Learner InstanceId=2,Checkpoint InstanceId=1),对于B的状态,我们逐个情况来分析:
A1:B(Proposer InstanceId=3,Acceptor InstanceId=3,Learner InstanceId=3,Checkpoint InstanceId=2),AC中的Learner可以学习到此消息,这种情况比较好理解,日志是“活”的。
但是还有一种情况,B成功接收ProposerSendSuccess消息,执行了状态机,B立即挂掉/或失联,AC还未学习到此value,这时只有A的Acceptor有记录此PromiseBallot,AC组成集群且InstanceId都是2,如果有新的Client到来,就会再次使用InstanceId=2进行新Value的投票,但是因为AB已经accept了当时的value,所以对于新到来的请求,A会告知发起提议的Proposer自己曾经接受过的value(老的),发起提议的Proposer于是也需要将新的value替换为老的value apply到状态机,因此,对于已经达成多数派Accept的value就确定是此value被chosen了,满足Paxos论文的B3约束,所以无论B是否挂掉,value均已被决定。那对用新value请求的Client怎么回应呢,Proposer返回其错误码:ERR_CONFLICT冲突,Client可以选择重试。
A2:B未接到消息,和上述讲的也是类似的,value也已被确定。
上述流程是提议已经满足多数派Accept的流程,那对于未到此流程的情况呢?有如下场景:A发起了Prepare,得到了AB的Promise,然后A发起Accept的只有A自己完成了accept,A和BC网络Partition。
问题1:BC还能继续工作吗,B在下一个决议是不是A已经accept的value?
BC满足多数派,可以继续工作。下一个决议不是A已经accept的value,因为BC无人知道accept的value是什么,B只是promise了;
问题2:若此时A恢复,BC能看到这个value吗?
首先A未完成ProposerSendSuccss,所以BC学习不到,那只能是ABC有人发起了新的投票,如果BC在A离开这段时间未达成任何新的决议,A仍可以提交成功(A会不停重试上次的value,进行Prepare);假设A离开这段时间,BC达成了新的决议,那BC达成决议的ProposalId肯定是比A手里的要大,那么A上已经accept的value就无效了(BC也不会再接受A accept的value)
-【问题8】Paxos如何并发投票?
Paxos算法因为要达成多数派决议,而且还有若干2PC+刷盘,整体性能很低,所以在工程上可用性不高,但这个问题还是要去解决的,就是我们要讨论的如何提高其并发量。这里也走过弯路,原来的思路是,能不能每个进程同时开启10个并发的instance,instanceId分别为1-10,3台机器,然后请求按顺序分发到各instance中,但想了下,请求是定序的,乱序执行的结果肯定是不对的,这样后面的请求如果先投票结束了,该instance实际也不能apply到状态机,最糟糕的是,假设请求1是define key;请求2是++key,请求间是有依赖关系的,请求2的PaxosLog要在请求1之后,所以这样是不行的。
换个思路,如果把key水平拆分,各自在不同的paxos group投票,相同key的串行执行,不同key的并行执行,最后落到同一个状态机里,就没有问题了。
还有一种思路,PhxPaxos也提到,就是假的并发,把client的请求每次收集N个,N个作为一次投票,状态机一次执行N条日志,也是可以达到批量的效果,唯一的缺点,可能也不算缺点,就是这些批量请求共用1个instanceId/logId。
-【问题9】数据一致性
Paxos就是解决数据一致性的,为什么还要再次讨论一致性,是的,但是这里要说的是数据一致性的时间窗口问题。对于分布式系统来讲,Basic-Paxos没有leader,这样对于read请求,所有的节点均可接受请求,可以把其状态机的数据返回给client,但是由于存在我们说的上述不一致节点,以及learner是要有一个过程的,可以称之为“不一致时间窗口”,在这个时间窗口内,就会出现节点落后的情况,数据是老数据,某种意义上讲算是“dirty”了。CAP理论中的Partition就会导致有不一致时间窗问题,再快也是会有先后。
-【问题10】Acceptor在已经落后的情况下,是否可以参与新的一轮Paxos投票过程?
不可以,假设可以的话,我们知道对于每轮投票确定的都是当前的value,那么不在同一轮的value过来之后,如果该acceptor接受了,learner也学习到了该instanceId value已被chosen,那此轮状态机的输入就是错误的,和【问题3】中的的“空洞”问题也冲突,所以不能。在PhxPaxos的实现里面,每一轮ProposalId会重置为0,这样不是一轮的ProposalId根本不能做大小比较。
-【问题11】Checkpoint的InstanceId是怎么保存的?
每次达成决议执行状态机Execute时都会把InstanceId落地,表示业务侧知道的最大InstanceId,假设业务是个内存型KV数据库,业务在做Checkpoint时就会将此InstanId的决议写入Checkpoint中,下次加载时,此InstanceId就是checkpoint中最大的InstanceId,再配合PaxosLog RePlay就可以恢复到所有数据。这里也会有一些异常情况,但解决应该都还算比较简单。
-【问题12】Master选举 & 租约Lease
此文有讲Master选举https://zhuanlan.zhihu.com/p/21540239,重点是说选举与Paxos的关系,以及租约机制,并未讨论通过Paxos算法选主的流程,实际上了解了Paxos算法之后,选主实际上很简单Lamport都不屑于讨论主的问题,哈哈~~,不过这里还要提到Lynncui的另一篇文章《Multi-Paxos与Leader》:https://zhuanlan.zhihu.com/p/21466932?refer=lynncui,实际上在Basic-Paxos中如果一个Proposer A提交的协议每次都被其他Acceptor正常接受(即没有其他的人做Proposer),那么实际上Basic-Paxos中此Proposer A在下一轮的投票中也完全可以不需要发起Prepare阶段,直接进入Accept,直到有人也开始做Proposer打破了A的“统治”,A才需要再次发起Prepare提高自己的ProposalId,由此为了优化投票过程,我们只需要保证A的统治尽可能不被打破,很简单那就不让其他的节点随意具有发起投票的权力就可以了,只有一个节点具有,所以Leader的概念就有了,而非Multi-Paxos必须有Leader!
Leader(或叫Master)的选举也是采用Paxos投票,所有的节点都不停会先检查是否有人已经是Leader了,如果无人是Leader,则发起Paxos投票,写入自己的节点信息,如果写入成功(写入SmMaster状态机),自己就成为Leader,其他的节点也会学习到谁是Leader。
是否存在有异常流程,假设一个场景:
A是主,然后A和集群Partition了,若此时Client和A可访问,则Client的请求,A无法达成多数派,所以提交失败;若此时Client和A之间不可访问,则Client的请求也无法提交,所以集群相当于无Leader的状态,只是集群临时不可用;
然后租约到期了,其他节点会TryBeMaster,有人会成为新的Leader,因为自己手里的租约早已到期了,无论A学习与否,A都不会认为自己还是Leader,所以集群在任意时刻内,最多只可能有一个Leader。
注意:重要的问题都列在这里,还有其他的一些问题就不一一列举了,后续有其他重要问题再补充,大家有问题可以随时探讨,欢迎批评指正!。
1.www.lamport.org
2.Paxos Made Simple
3.PhxPaxos