ZAB协议介绍(Zookeeper Atomic Broadcast)
ZAB协议包含两种基本模式,分别是:
- 崩溃恢复之数据恢复
- 消息广播之原子广播
当整个集群正在启动时,或者当leader节点出现网络中断、崩溃等情况时,ZAB协议就会进入恢复模式并选举产生新的leader,当leader服务器选举出来后,并且集群中有过半的机器和该leader节点完成数据同步后(同步指的是数据同步,用来保证集群中过半的机器能够和leader服务器的数据状态保持一致),ZAB协议就会退出恢复模式。
当集群中已经有过半的Follower节点完成了和Leader状态同步以后,那么整个集群就进入了消息广播模式。这个时候,在Leader节点正常工作时,启动一台新的服务器加入到集群,那这个服务器会直接进入数据恢复模式,和leader节点进行数据同步。同步完成后即可正常对外提供非事务请求的处理。
消息广播(原子广播)
消息广播实际上是一个简化版的2PC(二阶段提交协议)提交过程。
过程如下:
- leader接收到消息请求后,将消息赋予一个全局唯一的64位自增id,叫:zxid,通过zxid的大小比较就可以实现因果有序这个特征。
- leader为每个follower准备了一个FIFO队列(通过TCP协议来实现,以实现全局有序这一个特点)将带有zxid的消息作为一个提案(proposal)分发给所有的 follower。
- 当follower接收到proposal,先把proposal写到磁盘,写入成功以后再向leader回复一个ack。
- 当leader接收到合法数量(超过半数节点)的ack后,leader就会向这些follower发送commit命令,同时会在本地执行该消息。
- 当follower收到消息的commit命令以后,会提交该消息。
崩溃恢复(数据恢复)
ZAB协议的这个基于原子广播协议的消息广播过程,在正常情况下是没有任何问题的,但是一旦Leader节点崩溃,或者由于网络问题导致Leader服务器失去了过半的Follower节点的联系(leader失去与过半follower节点联系,可能是leader节点和 follower节点之间产生了网络分区,那么此时的leader不再是合法的leader了),那么就会进入到崩溃恢复模式。在ZAB协议中,为了保证程序的正确运行,整个恢复过程结束后需要选举出一个新的Leader。
为了使leader挂了后系统能正常工作,需要解决以下两个问题:
已经被处理的消息不能丢失
- 当leader收到合法数量follower的ack后,就向各个follower广播commit命令,同时也会在本地执行commit并向连接的客户端返回「成功」。但是如果各个follower在收到commit命令前leader就挂了,导致剩下的服务器并没有执行到这条消息。
- leader对事务消息发起commit操作,该消息在follower1上执行了,但是follower2还没有收到commit,leader就已经挂了,而实际上客户端已经收到该事务消息处理成功的回执了。所以在zab协议下需要保证所有机器都要执行这个事务消息,必须满足已经被处理的消息不能丢失。
被丢弃的消息不能再次出现
- 当leader接收到消息请求生成proposal后就挂了,其他follower并没有收到此proposal,因此经过恢复模式重新选了leader后,这条消息是被跳过的。 此时,之前挂了的leader重新启动并注册成了follower,他保留了被跳过消息的proposal状态,与整个系统的状态是不一致的,需要将其删除。(leader都换代了,所以以前leader的proposal失效了)
针对崩溃恢复的两种情况分析
ZAB协议需要满足上面两种情况,就必须要设计一个leader选举算法,能够确保已经被leader提交的事务Proposal能够提交、同时丢弃已经被跳过的事务Proposal。
针对第一个要求:
- 如果leader选举算法能够保证新选举出来的Leader服务器拥有集群中所有机器最高编号(ZXID 最大)的事务Proposal,那么就可以保证这个新选举出来的leader一定具有已经提交的提案。因为所有提案被commit之前必须有超过半数的follower ack,即必须有超过半数节点的服务器的事务日志上有该提案的proposal,因此只要有合法数量的节点正常工作,就必然有一个节点保存了所有被commit消息的proposal状态。
另外一个:
- zxid是64位,高32位是epoch编号,每经过一次Leader选举产生一个新的leader,新的leader会将epoch号+1,低32位是消息计数器,每接收到一条消息这个值+1,新leader选举后这个值重置为0。这样设计的好处在于老的leader挂了以后重启,它不会被选举为leader,因此此时它的zxid肯定小于当前新的leader。当老的leader作为follower接入新的leader后,新的leader会让它将所有的拥有旧的epoch号的未被commit的proposal清除。
关于ZXID
zxid,也就是事务id,为了保证事务的顺序一致性,zookeeper采用了递增的事务id号(zxid)来标识事务。所有的提议(proposal)都在被提出的时候加上了zxid,实际中zxid是一个64位的数字,它高32位是epoch(ZAB协议通过epoch编号来区分Leader周期变化的策略)用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个新的epoch=(原来的epoch+1),标识当前属于那个leader的统治时期。低32位用于递增计数。
测试epoch的变化
要测试epoch的变化可以做一个简单的实验:
- 启动一个zookeeper集群。
-
在 /tmp/zookeeper/VERSION-2 路径下会看到一个currentEpoch文件,文件中显示的是当前的epoch。
-
把leader节点停机,这个时候再看currentEpoch会发生变化。 随着每次选举新的leader,epoch都会发生变化。
-
先找到leader节点
-
停止leader节点的服务
-
查看当前epoch号的变化(epoch号由原来的6变为7)
-