Paxos是Lamport大神在1990年提出的,用来解决分布式系统一致性问题的算法。
分布式系统的一致性关注两个问题:
-
如何就某个决议达成一致
-
如何在决议过程结束后保证能最终达成一致
例如,在对某个变量达成一致的过程中,会有多个关于这个变量的值的提议。一致性协议需要保证仅有一个被提议值被最终选择,且未选择这个值的节点能够学习到这个值,使集群最终关于这个值取得一致。
协议过程并不复杂,包括三类角色:Proposer(协议发起者)、Acceptor(协议接收者)、Learner(学习者)。具体流程如下:
-
Proposer广播prepare消息给Acceptor,包含提议序号n。如果Acceptor收到的prepare请求的n值比之前接收到的prepare请求的n值都大的话,回复promise消息表示不会再接受任何小于n的请求且带上之前accept的提议中编号小于n的最大的提议,否则不予理会。
-
-
Acceptor接受到提议后,如果该提议编号不违反自己做过的承诺的话,则接受该提议。Learner根据Acceptor的状态学习最终被确定的值
注意:如果Proposer发出prepare(n)请求后,得到多数派的应答,然后可以再随便选择一个多数派广播accept请求,不一定要将accept请求发给有应答的Acceptor。
协议保证多数派达成一致后,无论采用学习的方法还是重复前两步流程,最终都能就某个值达成一致。
Paxos流程并不复杂,但是证明过程比较复杂。Lamport为了让算法更容易被理解,在2001年写了Paxos Made Simple,这篇论文没有一个数学公式,采用逻辑推演的方法重新描述了算法的流程,但还是没能显著降低算法理解的困难度。加之Paxos算法是纯理论的推导,工业实践过程中往往会经历较大的改动,谷歌Chubby实现了基于Paxos的一致性算法,虽然作者肯定了Paxos在一致性算法领域的地位,但是也承认工业落地后的Paxos算法和原始的Paxos算法差别很大。
国内开源的Paxos工业级别实现是微信的PhxPaxos类库。
具体实现设计:
Paxos是一个在异步通信环境中,集群中多节点存活的条件下能够保证写入一致的协议
Paxos只需要和集群中的多数accepter交互,即可完成一个值的确定,一旦这个值被选定,无论proposer再发起任何值的写入,数据都不会再发生改变。
现有的大部分存储系统都通过AppendLog的形式确定一个操作序列,当需要恢复一个存储时可以通过这个操作序列来恢复,而这个操作序列在确定之后就永远不会被修改。
他们将Proposer、各个Acceptor、所服务的Data共同构成一个大集合,这个集合所运行的paxos算法最终目标是确定一个值。我们称这个集合为一个paxos实例。
实例只能确定一个单独的值,如何完成多个实例的有序添加?
给每个实例生成一个全局只增不减的编号ID,每个机器的多个实例都是一个连续递增编号的有序系列,而基于Paxos协议的保证,同一个编号的实例在多一个机器上的值都是一致的,那么三台都获得了一个有序的多值存储。
然而由于可能出现宕机、消息丢失的情况,如何解决实例编号增长不同步的问题?
Learner询问其他机器相同编号的实例,如果这个实例已经被销毁,则说明这个实例的值已经被确定,直接将这个值写入当前实例中然后自增编号跳到下一个实例,如此反复,直到当前实例编号增长到和其它机器一致。
那么Paxos如何应用?
可重放的编辑日志和状态机结合
Paxos的每个实例,就是状态转移的输入,由于每台机器的实例编号都是有序增长的,而每个实例确定的值是一样的,那么可以保证各台机器的状态输入是完全一致的。根据状态机的理论,输入一致,那么引出的最终状态也是一致的。
一个Paxos工程实践的大致流程
客户端向Proposer发起请求,Proposer与实例编号相同的Acceptor协同工作确定一个值,之后将这个值作为状态机的输入,产生状态转移,最终将状态转移结果返回给客户端。
实现中的性能优化:
严格落盘
Paxos协议的运作过程需要做出很多保证,即保证了在相同的条件下一定会做出相同的处理。磁盘是它记录下这些保证条目的介质。一般使用fsync来解决问题,也就是在每次进行写盘时都要附加一个fsync进行保证。
为保证Paxos的写盘性能,必须要尽可能的减少Paxos算法所需要的写盘次数。理论上Paxos一次交互需要两次RTT,3次硬盘操作。PhxPaxos库将其减至一次RTT和一次硬盘操作,大大提高了Paxos协议的性能。
如果出现拜占庭问题的话,工程上就需要一系列的措施检测出这些拜占庭错误,然后选择性的进行数据回滚或直接丢弃。
一个Leader
多个Proposer写入是被允许的,只不过越来越多的Proposer进行同时写入,冲突的剧烈程度加大,虽然不会妨碍最终确定一个值,但在性能上是比较差的。
一个Leader的引入不是为了解决一致性问题,而是为了解决性能问题。通过简单的心跳以及租约就可以做到。
状态机记录最大的实例编号
在每次启动时,将状态机告诉Paxos最大的实例编号x与Paxos发现自己最大的已确定的实例编号y进行对比,如果有x<y的chosen value,我们将这些value逐一输入到状态机,那么状态机的状态就会更新到y了,这个称为启动重放。
参考文章:
微信PaxosStore:深入浅出Paxos算法协议 作者:郑建军
微信开源:生产级paxos类库PhxPaxos实现原理介绍 作者:lunncui