一直对Paxos协议比较感兴趣,之前对这个算法 有耳闻 但只是了解皮毛,最近在学Zookeeper,趁着这股新鲜劲,就花时间研究了下Zookeeper的选举算法实现,再重新学习了Paxos算法,这篇文章算是我的学习总结吧。
Paxos 协议是一个解决分布式系统中 多个节点之间就某个值(提案value)达成一致(决议)的通信协议,也就是说,每个节点 提出的提案 会 对 同一个 提案内容 达成一致(知悉 且 要commit成功
)。下面简称提案ID
为 ProposalID
,提案内容
为value
。
第一阶段 Prepare P1a:Proposer 发送 Prepare请求 Proposer 生成全局唯一且递增的ProposalID,向 Paxos 集群的所有机器发送 Prepare请求,这里不携带value,只携带 ProposalID 。
P1b:Acceptor 应答 Prepare Acceptor 收到 Prepare请求 后,判断:收到的ProposalID 是否 比 之前已响应的所有提案的ProposalID 大:
Max_ProposalID
。
(2) 回复请求,并带上 已Accept的提案中 ProposalID 最大的 value
(若此时还没有已Accept的提案,则返回value为空)。
(3) 做出承诺:不会Accept 任何 小于 Max_ProposalID
的提案。第二阶段 Accept
P2a:Proposer 发送 Accept
经过一段时间后,Proposer 收集到一些 Prepare 回复,有下列几种情况:
(1) 回复数量 > 一半的Acceptor数量,且 所有的回复的 value 都为空,则 Porposer发出accept请求,并带上 自己指定的value
。
(2) 回复数量 > 一半的Acceptor数量,且 有的回复 value 不为空,则 Porposer发出accept请求,并带上 回复中 ProposalID最大 的value
(作为自己的提案内容)。
(3) 回复数量 <= 一半的Acceptor数量,则 尝试 更新生成更大的 ProposalID,再转 P1a 执行。
P2b:Acceptor 应答 Accept
Accpetor 收到 Accpet请求 后,判断:
(1) 收到的ProposalID >= Max_ProposalID (一般情况下是 等于
),则 回复 提交成功,并 持久化 ProposalID 和value。
(2) 收到的ProposalID < Max_ProposalID,则 不回复 或者 回复 提交失败
。
P2c: Proposer 统计投票
经过一段时间后,Proposer 收集到一些 Accept 回复提交成功
,有几种情况:
(1) 回复数量 > 一半的Acceptor数量,则表示 提交value成功。此时,可以发一个广播给所有Proposer、Learner,通知它们 已commit的value。
(2) 回复数量 <= 一半的Acceptor数量,则 尝试 更新生成更大的 ProposalID,再转 P1a 执行。
(3) 收到 一条提交失败
的回复,则 尝试 更新生成更大的 ProposalID,再转 P1a 执行。
最后,经过多轮投票后,达到的结果是: (1) 所有Proposer都 提交提案成功了,且提交的value是同一个value。 (2) 过半数的 Acceptor都提交成功了,且提交的是 同一个value。
后者认同前者
的思路运行。
容错要求:
(1) 半数以内的Acceptor失效、任意数量的Proposer 失效,都能运行。
(2) 一旦value值被确定,即使 半数以内的Acceptor失效,此值也可以被获取,并不再修改。ProposalID
怎么定?
在《Paxos made simple》中提到,推荐Proposer从不相交的数据集合中进行选择,例如系统有5个Proposer,则可为每一个Proposer分配一个标识j(0~4),则每一个proposer每次提出决议的编号可以为5*i + j(i可以用来表示提出议案的次数)。
在实践过程中,可以用 时间戳 + 提出提案的次数 + 机器 IP/机器ID
来保证唯一性和递增性。所有回复中ProposalID最大 的value
作为自己的提案内容。
其中,prepare阶段的目的有两个: 1) 检查是否有被批准的值,如果有,就改用批准的值。2) 如果之前的提案还没有被批准,则阻塞掉他们以便不让他们和我们发生竞争,当然最终由ProposalID 的大小决定。随机改变 ProposalID的增长幅度
或者 增加Proposer发送新一轮提案的间隔
来解决。最大的ProposalID的value
,另一个会回复 执行失败
。当proposer收到执行失败
的回复时,就知道:当前 具有更大的ProposalID的提案提交成功了。大ProposalID
可以 抢占 小ProposalID
的提交权限,如果 此时 Acceptor还没有一个确定性取值,有一个具有最大ProposalID的proposer
进行到P2a阶段了,但这时 这个proposer挂了,会造成一种死锁状态(小ProposalID的会提交失败,但是 具有最大ProposalID的proposer
却不能提交accept请求),如何解决这种死锁状态?
不会产生这种死锁状态,acceptor回复提交失败
后,proposer再生成 更大的ProposalID,下一轮可以用自己value提交成功。解决 leader节点由于负载过重 导致 假死
,优先选择 原来的leader节点 可以减少 同步的数据量。Zxid + 机器ID
来确定,其中 Zxid表示 本节点 最近Commit的事务ID。
Zookeeper会优化 选择 拥有最新commit记录(Zxid最大)的机器
作为leader,如果Zxid相等,则选择机器ID更大的那个。自己的提案ProposalID和value
更新为 收到的Vote的值
,且广播向其它节点发送新的提案。
经过多轮投票后,会使 每个节点的提案 趋于最终的那个值。可以看出,一个Basic Paxos Instance运行完,要经过 每个Proposer的prepare阶段、accept阶段,还需要在过半数的节点上commit成功,流程太多,导致性能不高。一般在实践过程中用Multi Paxos协议。
Multi Paxos协议省去了Basic Paxos的prepare阶段,直接由一个leader节点提交提案,并通知其它Follower节点提交提案。在Leader租约内,只有Leader可以提出提案。如果leader节点崩溃宕机、或者租约到期后没有续约,则由 其它Follower 使用 Basix Paxos协议 竞争leadship。
在使用Multi Paxos 协议做多节点的复制时,有一些细节需要注意。详细可以参考这篇文章 和 这篇文章。
最近在项目中遇到一个需求,要解决一个中心节点(下称DC节点)的单点问题,我思考了下,解决方式有三种:
获取Dc leader节点的ip
。出处:https://baozh.github.io/2016-03/paxos-learning/
版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢。