目录
名词概念
协议实现
Phase 0: Leader election(选举阶段,Leader不存在)
Phase 1: Discovery(发现阶段,Leader不存在)
Phase 2: Synchronization(同步阶段,Leader不存在)
Phase 3: Broadcast(广播阶段,Leader存在)
Phase 1 快速选举Leader阶段(FLE)
Phase 2 恢复阶段
Leader视角
Follower视角
Phase 3 广播阶段
Leader视角:
Follower视角:
ZAB与Raft的比较
1.选举对比
2.日志复制
3.日志压缩
4.客户端读取
总结
Zab也是一个强一致性算法,也是(multi-)Paxos的一种,全称是Zookeeper atomic broadcast protocol,是Zookeeper内部用到的一致性协议。相比Paxos,也易于理解。其保证了消息的全局有序和因果有序,拥有强一致性。Zab和Raft也是非常相似的,只是其中有些概念名词不一样。
为了读懂后面的内容,有些术语需要了解:
Peer:节点。代表了系统中的进程,往往系统有多个进程,也就有多个节点提供服务
Quorum:多数。当有N个Peer,多数就代表Q(Q>N/2)个Peer
Leader:主节点,最多存在一个。代表zookeeper系统中主要的工作进程,Leader才是真正处理所有zookeeper写请求的节点,写请求会从Leader广播到Quorum Follower
Follower:从节点,可以有多个。如果client对zookeeper发起一个读请求,Follower可以直接处理。如果client对zookeeper发起一个写请求,Follower需要转发到唯一的Leader,再有Leader处理并发起广播
Election:说明节点处于选举状态。整个集群都处于选举状态中。
Epoch:逻辑时钟,相当于paxos中的proposerID,Raft中的term,相当于一个国家,朝代纪元(每个 leader 就像皇帝,都有自己的年号,所以每次改朝换代,leader 变更之后,都会在前一个年代的基础上加 1。这样就算旧的 leader 崩溃恢复之后,也没有人听他的了,因为 follower 只听从当前年代的 leader 的命令。)
zxid:事务id。zk为了保证消息有序性,提出了事务编号这个概念。zxid是一个二元组
history: a log of transaction proposals accepted; 历史提议日志文件
acceptedEpoch: the epoch number of the last NEWEPOCH message accepted; 集群中的最近最新Epoch
currentEpoch: the epoch number of the last NEWLEADER message accepted; 集群中的最近最新Leader的Epoch
lastZxid: zxid of the last proposal in the history log; 历史提议日志文件的最后一个提议的zxid
vote:选票,代表二元组
state:节点状态,目前只有这三种election、leading、following
lastCommittedZxid:最近提交成功的事务
oldThreshold:日志中记录最早提交成功的事务。之所以设置一个最早的门槛,就是觉得没有必要保留非常久远以前的事务,以便减少对内存的占用以及数据同步带来的网络开销
Zab协议中描述的如下四个阶段
节点在一开始都处于选举阶段,只要有一个节点得到超半数Quorums节点的票数的支持,它就可以当选prospective leader。只有到达 Phase 3 prospective leader 才会成为established leader(EL)。
在这个阶段,PL收集Follower发来的acceptedEpoch,并确定了PL的Epoch和Zxid最大,则会生成一个NEWEPOCH分发给Follower,Follower确认无误后返回ACK给PL。这个一阶段的主要目的是PL生成NEWEPOCH,同时更新Followers的acceptedEpoch,并寻找最新的historylog,赋值给PL的history。
一个 follower 只会连接一个 leader,如果有一个节点 f 认为另一个 follower 是 leader,f 在尝试连接 p 时会被拒绝,f 被拒绝之后,就会进入 Phase 0。
同步阶段主要是利用 leader 前一阶段获得的最新提议历史,同步集群中所有的副本。只有当 quorum 都同步完成,PL才会成为EL。follower 只会接收 zxid 比自己的 lastZxid 大的提议。
这个一阶段的主要目的是同步PL的historylog副本。
到了这个阶段,Zookeeper 集群才能正式对外提供事务服务,并且 leader 可以进行消息广播。同时如果有新的节点加入,还需要对新节点进行同步。这个一阶段的主要目的是接受请求,进行消息广播值得注意的是,ZAB 提交事务并不像 2PC 一样需要全部 follower 都 ACK,只需要得到 quorum (超过半数的节点)的 ACK 就可以了。
但是实际上zookeeper(3.3.3的版本的实现为准)中会有一些改进简化,简化为三个阶段
改进简化原因
首先初始化一些数据
如果当前节点处于选举状态,则它也会接到选票通知。它会从队列中不断轮询节点,以便获取选票信息(如果超时,则不断放松超时时间,直至上限)。根据轮询出来的发送节点的状态,来做相应的处理。
经过FLE,已经选出了日志中具有最新已提交的事务的节点作为预备Leader。下面就分Leader和Follower两个视角来介绍具体实现。
首先,更新lastZxid,将纪元+1,计数清零,宣布改朝换代啦。然后在每次接收到Follower的数据同步请求时,都会将自己lastZxid反馈回去,表示所有Follower以自己的lastZxid为准。接下来,根据具体情况来判断该如何将数据同步给Follower
当Follower完成同步时,会发送同步ack,当Leader收到Quorum ack时,表示数据同步阶段大功告成,进入最后的广播阶段。
通知Leader,表示自己希望能同步Leader中的数据。
当每个Follower完成上述的同步过程时,会发送ack给Leader,并进入广播阶段。
进入到这个阶段,说明所有数据完成同步,Leader已经转正。开始zookeper最常见的工作流程:广播。
广播阶段是真正接受事务请求(写请求)的阶段,也代表了zookeeper正常工作阶段。所有节点都能接受客户端的写请求,但是Follower会转发给Leader,只有Leader才能将这些请求转化成事务,广播出去。这个节点一样有两个角色,下面还是按照这两个角色来讲解。
Raft 和 Zk 状态机的实现机制不同使得两者在 snapshot 的时候有很大差别。Raft是典型的传统复制窗台机,对于更新请求,严格复制对应的 command。Zk 则不是严格的复制状态机,对于更新请求,复制的不是响应的 command 而是更新后的值。如图所示。
Zk 之所以可以这样做,很大程度上依赖于其状态机的实现,比如 y++ 这条 log 在 Raft的复制状态机机制下如果被重复执行就会导致错误的结果,但是 y <- 2 被重复执行不会影响数据的正确性。
个人认为 Zab 协议设计的优秀之处有两点,一是简化二阶段提交,提升了在正常工作情况下的性能;二是巧妙地利用率自增序列,简化了异常恢复的逻辑,也很好地保证了顺序处理这一特性。
参考资料和衍生阅读:
https://cloud.tencent.com/developer/news/261787
https://blog.csdn.net/A_zhenzhen/article/details/78839843
https://www.jianshu.com/p/fb527a64deee
https://my.oschina.net/u/1378920/blog/914215
https://www.jianshu.com/p/24307e7ca9da
https://niceaz.com/2018/11/03/raft-and-zab/#log-replication-and-commitment