zab协议是为分布式协调服务zookpeer专门设计的一种支持崩溃恢复的原子广播协议。
zookpeer依赖ZAB协议来实现数据一致性,基于该协议,zk实现了一种主备模式的系统架构来保证集群中各个副本之间数据的一致性。
Zookeeper服务器角色
Leader:一个zk集群中同一时间只会有一个Leader。它会发起并维护与各Follwer及Observer间的心跳。所有的写操作必须要通过Leader完成再由Leader将写操作广播给其它服务器。
Follower:一个zk集群中会有多个Follower,它会响应Leader的心跳。Follower可直接处理并返回客户端的读请求,同时会将写请求转发给Leader处理,并且负责在Leader处理写请求时对请求进行投票。
Observer:角色与Follower类似,但是无投票权。
zab模式
主从分布式系统中,主要解决两大问题:
1、leader服务器是如何把数据更新到所有的Follower的。
2、Leader服务器突然间失效了,怎么办?
ZAB协议为了解决上面两个问题,设计了两种模式:
1、消息广播模式:把数据更新到所有的Follower
2、崩溃恢复模式:Leader发生崩溃时,如何恢复
消息广播模式
消息广播实际是一个简化的二阶段提交,它不需要等待所有follower返回ACK,只需要半数及以上的参与者返回成功ACK即可。
如图,具体步骤如下:
- 客户端发起一个写操作请求
- Leader服务器将写请求转化为事物proposal提案,同时为每个proposal分配一个全局唯一的ID,即ZXID(事务ID)。
- leader服务器与每个follower之间都有一个队列,leader将消息发送到该队列。
- follower机器从队列中取出消息处理完毕后(写入本地事物日志中),向leader服务器发送ACK确认。
- leader服务器收到半数以上的follower的ACK后,即认为可以发送commit。
- leader向所有的follower服务器发送commit消息。
通俗点讲:我是领导,我要向各位传达指令,不过传达之前我先问一下大家支不支持我,若有一半以上的人支持我,那我就向各位传达指令了。
崩溃恢复模式
zookeeper集群中为保证任何所有进程能够有序的顺序执行,只能是、leader服务器接受写请求,即使是follower服务器接受到客户端的请求,也会转发到leader服务器进行处理。
如果leader服务器发生崩溃,则zab协议要求zookeeper集群进行崩溃恢复和leader服务器选举。
ZAB协议崩溃恢复要求满足如下2个要求:
1、确保已经被leader提交的proposal必须最终被所有的follower服务器提交。
2、确保丢弃已经被leader提出的但是没有被提交的proposal。
所以新选举出来的leader不能包含未提交的proposal,即新选举的leader必须都是已经提交了的proposal的follower服务器节点。同时,新选举的leader节点中含有最高的ZXID。这样做的好处就是可以避免了leader服务器检查proposal的提交和丢弃工作。
让具有最高ZXID的服务器成为Leader,就可以省去Leader服务器检查proposal的提交和丢弃工作的这一步操作了。
选举流程
myid:zk服务器唯一编号
zxid:最新事务ID
Epoch:选举的轮数,即逻辑时钟。随着选举的轮数 (k规定所有有效的投票都必须在同一轮次中。)
Server状态:LOOKING(观望状态),FOLLOWING,OBSERVING,LEADING
1、所有Server加入集群时都会推荐自己为leader,然后将(Myid, Zxid, Epoch)作为广播信息,广播到集群中所有的服务器。然后等待集群中的服务器返回信息。
2、 收到集群中其他服务器返回的信息,此时要分为两类:该服务器处于looking状态,或者其他状态。
如果服务器处于looking状态:
首先判断逻辑时钟:
外部Epoch > 自己的Epoch:说明自己所保存的逻辑时钟落伍了。更新本机逻辑时钟Epoch,立即清空自己的投票箱。然后进行选票PK(规则见选票PK规则),更新自己的投票,最终把结果广播出去。
外部Epoch < 自己的Epoch:说明对方处于一个比较OUT的选举轮数,这时只需要将自己的 (Myid,Zxid,Epoch)发送给他即可。
外部Epoch = 自己的Epoch:进行选票PK,更新自己的投票,最终把结果广播出去。
如果服务器处于Following, Leading状态:
外部Epoch = 自己的Epoch:如果所接收服务器宣称自己是leader,那么将判断是不是有半数以上的服务器选举它,如果是则设置选举状态退出选举过程。
否则这是一条与当前逻辑时钟不符合的消息,那么说明在另一个选举过程中已经有了选举结果,于是将该选举结果加入到outofelection集合中,再根据outofelection来判断是否可以结束选举,如果可以也是保存逻辑时钟,设置选举状态,退出选举过程。
3、投票PK:
优先比较zxid,zxid大的投票胜出。如果zxid相同,再比较myid,myid较大的胜出。然后更新自己的投票结果,再次广播更新后的投票结果。
4、投票统计:
每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息。如果有则终止投票。
5、更新服务器状态:
投票终止后,服务器开始更新自身状态。若过半的票投给了自己,则将自己的服务器状态更新为LEADING,否则将自己的状态更新为FOLLOWING。
数据同步
在zk集群中新的leader选举成功之后,leader会将自身的提交的最大proposal的事物ZXID发送给其他的follower节点。
follower节点会根据leader的消息进行回退或者是数据同步操作。
最终目的要保证集群中所有节点的数据副本保持一致。
数据同步完之后,zookeeper集群如何保证新选举的leader分配的ZXID是全局唯一呢?这个就要从ZXID的设计谈起。
ZXID是一个长度64位的数字,其中低32位是按照数字递增,即每次客户端发起一个proposal,低32位的数字简单加1。高32位是leader周期的epoch编号。
每当选举出一个新的leader时,新的leader就从本地事物日志中取出ZXID,然后解析出高32位的epoch编号,进行加1,再将低32位的全部设置为0。这样就保证了每次新选举的leader后,保证了ZXID的唯一性而且是保证递增的。