ZAB(Zookeeper Atomic Broadcast)协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议。在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,是Zookeeper保证数据一致性的核心算法。
ZAB协议借鉴了Paxos算法,但又不像Paxos那样,是一种通用的分布式一致性算法。基于该协议,ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。
ZAB协议包含两种基本模式:分别是:1.崩溃恢复 2.原子广播
崩溃恢复,从字面意思理解,就是集群崩溃后数据恢复的意思。name什么时候zookeeper集群会进入崩溃恢复模式呢?
在如下两种情况下,集群会进入崩溃恢复模式:
1.因为网络等原因,当leader服务器,失去了过半的与follower节点的联系;
2.当leader服务挂了的时候
什么情况下,集群会退出崩溃恢复模式:
当 leader 服务器选举出来后,并且集群中有过半的机器和该 leader 节点完成数据同步后(同步指的是数据同步,用来保证集群中过半的机器能够和 leader 服务器的数据状态保持一致),ZAB 协议就会退出恢复模式。
针对数据恢复情况,分为如下两种情况:
1.已经被处理的消息不能丢失(如何做到,参见本文如下:zxid最大)
leader向其他follower发送事务请求,当leader收到集群过半数follower的ACK响应后,会向各个follower广播commit命令,同时自己也会在本地执行commit提交,并向连接的客户端返回「成功」。但是如果在各个follower在收到commit前,leader就挂掉了,会导致部分的节点收到commit命令,部分的节点没有收到commit命令,从而导致部分服务器并没有执行这条commit操作。那么ZAB协议需要保证已经被处理的消息不能被丢失。
2.被丢弃的消息不能再次出现(如何做到,参见本文如下:epoch原理)
当leader收到事务请求,并且在还未发起事务投票之前,leader就已经挂掉了。这个时候就需要满足,从follower节点中重新选举出来一台leader。当新的leader从follower中选举出来之后,能够让这条未处理的消息被丢弃掉,并被其他节点也都丢弃掉(未处理的这条消息会被删除,或者跳过)
ZAB 协议需要满足上面两种情况,就必须要设计一个 leader 选举算法:能够确保已经被 leader 提交的事务能够提交、同时丢弃已经被跳过的事务。ZAB协议遵循如下两种设计思想:
1.zxid最大
如果 leader 选举算法能够保证新选举出来的 leader 服务器拥有集群中所有机器最高编号(ZXID 最大)的事务 Proposal,那么就可以保证这个新选举出来的leader一定具有已经提交的提案。因为所有提案被commit之前必须有超过半数的follower返回ACK,即必须有超过半数节点的服务器的事务日志上有该提案的 proposal,因此,只要有合法数量的节点正常工作,就必然有一个节点保存了所有被 commit 消息的 proposal 状态。
简单来说:leader挂掉后,需要重新从 follower1 和 follower2 中选举 leader,每个follower都会有zxid(这个zxid是挂掉时,正在执行事务的zxid),zxid最大保证数据是最新的,则选择zxid最大的那个follower作为leader(说明数据是最新的)
2.epoch的概念
zxid 是 64 位,高 32 位是 epoch 编号,每经过一次 leader 选举产生一个新的 leader,新的 leader 会将 epoch 号+1,低 32 位是消息计数器,每接收到一条消息这个值+1,新 leader 选举后这个值重置为 0。
为什么要有个epoch?
这样设计的好处在于:新的leader选举以后,消息会重新从0开始自增,epoch会在之前epoch+1。所以,新选举出来的epoch会比旧的leader的epoch高,老的leader服务器重新启动,加入集群之后,因为epoch大小问题,使得它不会再次成为leader,因为老leader的epoch号会比新leader的epoch号小。
老的leader重新启动,加入集群后。他的zxid一定小于当前新的leader的zxid。当老的leader以follower的角色加入集群,新的leader会把老的leader中的所有epoch号、未提交的事务Proposal请求全部清除
epoch测试
通过zoo.cfg配置,我们配置过dataDir=/tmp/zookeeper,所以在/tmp/zookeeper目录下有个version-2文件夹,文件夹下会有一个currentEpoch,通过cat命令我们便可以查看当前集群的epoch,如下图:
①当前集群正常,我们发现currentEpoch均为12,我们发现192.168.204.202这台服务器为leader角色;
②当我们将202这台服务器停掉,集群满足一半以上服务可用原则(3台机器,停掉一台,可用2台,2>1.5),会重新选举leader;
③我们发现203服务器被选举为leader,我们这时再查看epoch,会发现201和203服务器epoch已经变为13,停掉的202服务器还是12;
④当我们将202服务器重新启动,会发现他的epoch会被全部清除,同步变成最新的13,此时它的角色会是follower,在本次服务过程中不可能再次成为leader。
在zookeeper集群中,数据副本的传递策略就是采用消息广播模式。zookeeper中数据副本的同步方式与2PC方式提交(二阶段提交)相似,但是却又不同。原子广播相当于是改进版的2PC方式提交,实际上是一个简化版本的2PC提交过程
2PC提交,要求协调者必须等到所有的参与者全部反馈ACK确认消息后,再发送commit消息。要求所有的参与者要么全部成功,要么全部失败。二段提交会产生严重的阻塞问题。
当集群中已经有过半的 Follower 节点完成了和 Leader 状态同步以后,那么整个集群就进入了消息广播模式。这个时候,在 Leader 节点正常工作时,启动一台新的服务器加入到集群,那这个服务器会直接进入数据恢复模式,和 leader 节点进行数据同步。同步完成后即可正常对外提供非事务请求的处理。
消息广播的实现原理
1.leader 接收到消息请求后,将消息赋予一个全局唯一的64 位自增 id,叫:zxid,通过 zxid 的大小比较既可以实现因果有序这个特征 (zxid是啥?参考:zNode节点解析)(为什么要赋值到zxid,参考本文:4.ZAB设计思想)
2.leader 为每个 follower 准备了一个 FIFO 队列(通过 TCP 协议来实现,以实现了全局有序这一个特点)将带有 zxid 的消息作为一个提案(proposal)分发给所有的 follower
3.当 follower 接收到 proposal,先把 proposal 写到磁盘,写入成功以后再向 leader 回复一个 ack
4.当 leader 接收到合法数量(超过半数节点)的 ACK 后, leader 就会向这些 follower 发送 commit 命令,同时会在本地执行该消息
5.当 follower 收到消息的 commit 命令以后,会提交该消息
博主写作不易,来个关注呗
求关注、求点赞,加个关注不迷路 ヾ(◍°∇°◍)ノ゙
博主不能保证写的所有知识点都正确,但是能保证纯手敲,错误也请指出,望轻喷 Thanks♪(・ω・)ノ