前言
上一篇:《分布式数据一致性模型有哪些?》 提到了Base理论提到了一个重要的点就是「最终一致性」 有什么方式能实现这种一致性呢?
本文主要对以下两个问题进行介绍:
-
一致性算法Paxos透析?
-
Paxos算法的应用场景?
Paxos算法透析
Google Chubby的作者Mike Burrows说过,世上只有一种一致性算法,那就是Paxos,所有其他一致性算法都是Paxos算法的不完整版。Paxos算法是公认的晦涩,很难可能能将清楚,但是工程上也很难实现,所以有很多Paxos算法的工程实现,如Raft,ZAB,微信的PhxPaxos等。
Paxos算法是莱斯利·兰伯特(Leslie Lamport)1990年提出的一种基于消息传递的一致性算法,它曾就此发表了《The Part-Time Parliament》,《Paxos Made Simple》,由于采用故事的方式来解释此算法,感觉还是很难理解。
为什么要需要Paxos
因为「数据一致性」
在常见的分布式系统中,总会发生诸如机器宕机或网络异常(包括消息的延迟、丢失、重复、乱序,还有网络分区)(也就是会发生异常的分布式系统)等情况。Paxos算法需要解决的问题就是如何在一个可能发生上述异常的分布式系统中,快速且正确地在集群内部对某个数据的值达成一致。也可以理解成分布式系统中达成状态的一致性。
Base理论的核心思想是最终一致性,即使无法做到强一致性(StrongConsistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual Consistency)。
Quorum 机制
在了解Paxos之前我们先了解一下「Quorum选举算法」 大家看到选举算法,是不是就联想到了zookeeper中的Leader选举?你的直觉是没有问题的。
在各种一致性算法中都可以看到Quorum机制的身影,主要数学思想来源于抽屉原理,用一句话解释那就是,在N个副本中,一次更新成功的如果有W个,那么我在读取数据时是要从大于N-W个副本中读取,这样就能至少读到一个更新的数据了。
和Quorum机制对应的是WARO,也就是WriteAllReadone,是一种简单的副本控制协议,当 Client 请求向某副本写数据时(更新数据),只有当所有的副本都更新成功之后,这次写操作才算成功,否则视为失败。
WARO优先保证读服务,因为所有的副本更新成功,才能视为更新成功,从而保证了所有的副本一致,这样的话,只需要读任何一个副本上的数据即可。写服务的可用性较低,因为只要有一个副本更新失败,此次写操作就视为失败了。假设有 N 个副本,N-1 个都宕机了,剩下的那个副本仍能提供读服务;但是只要有一个副本宕机了,写服务就不会成功。
WARO 牺牲了更新服务的可用性,最大程度地增强了读服务的可用性,而 Quorum 就是在更新服务和读服务之间进行的一个折衷。
Quorum 定义
Quorum的定义如下:假设有N个副本,更新操作wi在W个副本中更新成功之后,才认为此次更新操作wi成功,把这次成功提交的更新操作对应的数据叫做:“成功提交的数据”。对于读操作而言,至少需要读 R 个副本才能读到此次更新的数据,其中,W+R>N ,即 W 和 R 有重叠,一般,W+R=N+1。
N = 存储数据副本的数量
W = 更新成功所需的副本
R = 一次数据对象读取要访问的副本的数量
Quorum就是限定了一次需要读取至少N+1-w的副本数据,听起来有些抽象。
举个例子,我们维护了10个副本,一次成功更新了三个,那么至少需要读取八个副本的数据,可以保证我们读到了最新的数据。
Quorum 的应用
Quorum机制无法保证强一致性,也就是无法实现任何时刻任何用户或节点都可以读到最近一次成功提交的副本数据。
Quorum 机制的使用需要配合一个获取最新成功提交的版本号的 metadata 服务,这样可以确定最新已经成功提交的版本号,然后从已经读到的数据中就可以确认最新写入的数据。
Quorum 是分布式系统中常用的一种机制,用来保证数据冗余和最终一致性的投票算法,在 Paxos、Raft 和 ZooKeeper 的 Zab 等算法中,都可以看到 Quorum 机制的应用。
Paxos算法的相关概念
在 Paxos 协议中,有三类节点角色,分别是 Proposer、Acceptor 和 Learner,另外还有一个 Client,作为产生议题者。
上述三类角色只是逻辑上的划分,在工作实践中,一个节点可以同时充当这三类角色。
Proposer 提案者
Proposer可以有多个,在流程开始时,Proposer提出议案,也就是value,所谓value,在工程中可以是任何操作,比如 “修改某个变量的值为某个新值”,Paxos协议中统一将这些操作抽象为value。
不同的Proposer可以提出不同的甚至矛盾的value,比如某个Proposer提议“将变量X设置为1”,另一个 Proposer 提议 “将变量 X 设置为 2”,但对同一轮 Paxos 过程,最多只有一个 value 被批准。
Acceptor 批准者
在集群中,Acceptor 有 N 个,Acceptor 之间完全对等独立,Proposer 提出的 value 必须获得超过半数(N/2+1)的 Acceptor 批准后才能通过。
Learner 学习者
Learner 不参与选举,而是学习被批准的 value,在Paxos中,Learner主要参与相关的状态机同步流程。
这里Leaner的流程就参考了Quorum议会机制,某个value需要获得W=N/2+1的Acceptor批准,Learner需要至少读取N/2+1个Accpetor,最多读取 N 个 Acceptor 的结果后,才能学习到一个通过的 value。举个例子:
我有10个Accpetor,至少读取6个相同的value,最多读取10个相同的value。也就是选取票数最多的结果。
Client 产生议题者
Client 角色,作为产生议题者,实际不参与选举过程,比如发起修改请求的来源等。 Proposer 与 Acceptor 之间的交互
Proposer与Acceptor之间的交互
Paxos 中,proposer提交给Acceptor,由Acceptor达成一致选取最终的value,然后告诉Learner最终的value。
Proposer 与 Acceptor 之间的交互主要有 4 类消息通信,如下图:
这 4 类消息对应于 Paxos 算法的两个阶段 4 个过程,下面在分析选举过程时会讲到。
Paxos 选举过程
选举过程可分为两个阶段:
-
准备阶段
-
选举阶段
准备阶段
Proposer 生成全局唯一且递增的 ProposalID,向 Paxos 集群的所有机器发送 Prepare 请求,这里不携带 value,只携带 N 即 ProposalID。
Acceptor 收到 Prepare 请求后,判断收到的 ProposalID 是否比之前已响应的所有提案的 N 大,如果是,则:
-
在本地持久化N,可记为Max_N;
-
回复请求,并带上已经Accept的提案中N最大的value,如果此时还没有已经Accept的提案,则返回value为空;
-
做出承诺,不会 Accept 任何小于 Max_N 的提案。
如果否,则不回复或者回复 Error。
选举阶段
「Proposer 发送 Accept」
经过一段时间后,Proposer收集到一些Prepare回复,有下列几种情况:
-
若回复数量>一半的Acceptor数量,且所有回复的value都为空时,则 Porposer 发出 accept 请求,并带上自己指定的 value。
-
若回复数量>一半的Acceptor数量,且有的回复value不为空时,则Porposer发出accept请求,并带上回复中ProposalID最大的value,作为自己的提案内容。
-
若回复数量 <= 一半的 Acceptor 数量时,则尝试更新生成更大的 ProposalID,再转到准备阶段执行。
「Acceptor 应答 Accept」
Accpetor收到Accpet请求后,判断:
-
若收到的N>=Max_N(一般情况下是等于),则回复提交成功,并持久化N和value;
-
若收到的 N < Max_N,则不回复或者回复提交失败。
「Proposer 统计投票」
经过一段时间后,Proposer 会收集到一些 Accept 回复提交成功的情况,比如:
-
当回复数量 > 一半的 Acceptor 数量时,则表示提交 value 成功,此时可以发一个广播给所有的 Proposer、Learner,通知它们已 commit 的 value;
-
当回复数量 <= 一半的 Acceptor 数量时,则尝试更新生成更大的 ProposalID,转到准备阶段执行。
-
当收到一条提交失败的回复时,则尝试更新生成更大的 ProposalID,也会转到准备阶段执行。
Paxos 常见的问题
1.如果半数以内的 Acceptor 失效,如何正常运行?
在Paxos流程中,如果出现半数以内的Acceptor失效,可以分为两种情况:
-
第一种,如果半数以内的Acceptor失效时还没确定最终的value,此时所有的Proposer会重新竞争提案,最终有一个提案会成功提交。
-
第二种,如果半数以内的Acceptor失效时已确定最终的value,此时所有的 Proposer 提交前必须以最终的 value 提交,也就是Value实际已经生效,此值可以被获取,并不再修改。
2. Acceptor需要接受更大的N,也就是ProposalID有什么意义?
这种机制可以防止其中一个Proposer崩溃宕机产生阻塞问题,允许其他Proposer用更大ProposalID来抢占临时的访问权。
3. 如何产生唯一的编号,也就是 ProposalID?
在《Paxosmadesimple》论文中提到,唯一编号是让所有的Proposer都从不相交的数据集合中进行选择,需要保证在不同Proposer之间不重复,比如系统有5个Proposer,则可为每一个Proposer分配一个标识j(0~4),那么每一个Proposer每次提出决议的编号可以为5*i+j,i可以用来表示提出议案的次数。
Paxos算法的应用场景?
-
database replication, log replication等, 如bdb的数据复制就是使用paxos兼容的算法。Paxos最大的用途就是保持多个节点数据的一致性。
-
naming service, 如大型系统内部通常存在多个接口服务相互调用。
-
config配置管理
-
membership用户角色/access control list, 比如在权限设置中,用户一旦设置某项权限比如由管理员变成普通身份,这时应在所有的服务器上所有远程CDN立即生效,否则就会导致不能接受的后果
-
号码分配。通常简单的解决方法是用数据库自增ID, 这导致数据库切分困难,或程序生成GUID, 这通常导致ID过长。更优雅的做法是利用paxos算法在多台replicas之间选择一个作为master, 通过master来分配号码。当master发生故障时,再用paxos选择另外一个master。
-
ZooKeeper用到了Paxos算法,但是并不是遵循Paxos协议,而是基于自身设计并优化的一个2 phase commit的协议
总结
-
只有被提出的value才能被选中
-
只有一个value被选中
-
如果某个进程认为某个value被选定了,那么这个value必须是真的被选定的那个。
-
因为Paxos算法引入了过半的概念,所以具有高度容错性的分布式一致性算法。
-
Paxos算法支持分布式节点角色之间的轮换,这极大避免了分布式单点的出现,因此Paxos算法既解决了无限等待问题,也解决了脑裂问题,是目前来说最优秀的分布式一致性算法。
-
Zookeeper的ZAB算法和Raft一致性算法都是基于Paxos的。
来源:oschina
链接:https://my.oschina.net/960823/blog/3235902