强一致性算法Paxos、Raft、ZAB
在B站上一个讲这三个算法的视频网址https://www.bilibili.com/video/av21667358
Paxos协议
Basic paxos算法中,分为4种角色:
- Client: 系统外部角色,产生议题者,像民众
- Proposer :接收议题请求,像集群提出议题(propose),并在冲突发生时,起到冲突调节的作用,像议员
- Acceptor:提议的投票者和决策者,只有在形成法定人数(一般是majority多数派)时,提议才会被最终接受,像国会
- Learner:最终提议的接收者,backup,对集群的已执行没有什么影响,像记录员
Paxos有三种实现版本
- Basic Paxos版本
- Multi Paxos版本
- Fast Paxos版本
两个阶段
Prepare阶段
- Proposer 生成提案编号 Mn, 向Quorum 广播该编号。
- Acceptor 收到Prepare Mn 之后
- 如果比已接受提案编号大,则接受Mn, 并承诺不再接受小于Mn 的提案,将已接收的编号最大提案通过返回ACK 给Proposer。
- 否则忽略Prepare Mn
- Proposer 收到Quorum 数量的ACK 之后,则进入提交阶段。否则重新开始Prepare 阶段。
Accept阶段
- Proposer 选取ACK 中编号最大的提案的Value 作为Vn, 向Quorum 广播 Accept [Mn, Vn]。
- Acceptor 收到Accept [Mn, Vn] 之后
- 若提案不违背承诺,则接受该提案,并返回ACK
- 否则忽略
Basic Paxos版本
该版本是最基本的版本,由多个proposer和多个acceptor组成
该版本的问题是,
- 一个完整的事务,需要两次RPC远程调用,分别是Prepare阶段和Accpet阶段。效率不高
- 因为是多个proposer可以都提交提案,因此会出现活锁问题,Proposer1提出了M1的提案,并完成的Prepare阶段。与此同时,Proposer2提出了一个新的提案M2(M2>M1),同样也完成了Prepare阶段,于是Acceptor承诺不再批准编号小于M2的提案。当P1进入Accept阶段时,其发出的Accept请求将被Acceptor忽略。于是Proposer1再次进入Prepare阶段并提出一个编号为M3(M3>M2)的提案,而这又会让Proposer2的M2在第二阶段的Accept请求被忽略,如此陷入死循环。
Multi Paxos版本
该版本只有一个Leader Proposer,只有Leader Proposer可以提出提案,Leader Proposer通过选举产生。
选举一个Leader Proposer的时候,需要进行投标,当一个Leader选举出来之后,该Leader发出的提案就可以直接进入Accpet阶段。
Raft协议
raft 简化版的Multi Paxos,划分了三个子问题
- Leader Electtion
- Log Replication
- Safety
重定义了三个角色
- eader
- Fllower
- Candidate
这个解释的非常明白
原理动画解释:https://http://thesecretlivesofdata.com/raft/
这个是怎样选举的leader,某个节点宕机了怎么解决的详细动画演示
场景测试:http://raft.github.io
ZAB协议
基本与Raft相同,在一些名词叫起来是有区别的
ZAB将Leader的一个生命周期叫做epoch,而Raft称之为term
实现上也有些许不同,如raft保证日志的连续性,心跳是Leader向Follower发送,而ZAB方向与之相反
术语说明:
ZXID:高32位是epoch,表示Leader周期,单调递增;低32位,Leader产生一个新的事务proposer时,该计数器会加1(在一个Leader周期里是单调递增,epoch变更后从0开始)
SID:服务器ID,用来唯一标识一台ZooKeeper集群中的服务器,每台机器不能重复,和myid的值一致。
vote_sid:接收到的投票信息中所推举的Leader服务器的SID
vote_zxid:接收到的投票信息中所推举的Leader服务器的ZXID
self_sid:当前服务器自己的SID
self_zxid:当前服务器自己的ZXID
electionEpoch:当前服务器的选举轮次。
Leader选举
1.变更状态:如果是运行过程中Leader挂了,则会有这一步,非Observer服务器会将自己的服务器状态变更为LOOKING(寻找Leader状态),然后进入Leader选举流程
2.每个Server会发出一个投票 vote[self_id,self_zxid]
3.接收来自各个Server的投票 vote[vote_sid,vote_zxid]
4.处理投票
-
先比较ZXID,ZXID比较大的服务器优先作为Leader
如果vote_zxid > self_zxid,就认可收到的投票,并再次将该投票发送出去
如果vote_zxid < self_zxid,就坚持自己的投票,不做任何变更
-
如果ZXID相同的话,那么比较myid,myid比较大的服务器作为Leader服务器。
如果vote_sid > self_sid,就认可收到的投票,并再次将该投票发送出去
如果vote_sid < self_sid,那么就坚持自己的投票,不做任何变更
5.统计投票:判断是否已经有过半的机器Quorum接收到相同的投票信息,如果存在一个服务器得到过半的票数,那么得到过半票数的机器作为Leader,终止投票。否则进入步骤3,进入到新一轮的投票选举中。
6.改变服务器状态:确定Leader后,每个服务器会更新自己的状态,如果是Follower,那么就变更为FOLLOWING(跟随者状态),如果是Leader,那么就变更为LEADING(领导者状态)
注意:选举出一个leader后,服务器不会马上更新状态,而是会等待一段时间(默认是200毫秒)来确定是否有新的更优的投票。
Leader同步
由于上述选举过程保证了 Leader 必然拥有最大zxid, Leader 只需要向Follower 同步自己的历史提案即可。
- 若Follower 拥有 Leader 没有的提案,则 truncat掉。
- 若Follower的epoch = 当前leader的epoch,落后则根据log, reply 历史transcation
- 若落后太多,则直接同步 snapshot,再replay transaction log.