上两篇介绍了ZK的基础信息以及如何使用ZK实现分布式锁,今儿就来看看ZK是如何保持数据一致性以及ZK中Leader是如何进行选举的。
一、数据一致性
Zookeeper保持数据一致性使用的是ZAB协议。ZAB协议包含两种模式,一是消息广播模式,二是崩溃恢复模式。
(1)消息广播模式(两阶段提交+过半写机制)
具体流程如下:
1、客户端发起写操作,leader接收到写请求
2、leader服务器将客户端请求转化成proposal提案,同时为每个proposal分配一个全局唯一的ID,即ZXID。
3、leader与每个follower节点都维持着一个队列,leader将proposal请求发送到每个队列中(同时也发送给自己)。
4、follower从队列中获取执行成功之后(写入到本地事务中),向leader发送一个ack响应。
5、当leader节点收到半数以上follower的ack响应之后,就会发送commit请求给follower。

(2)崩溃恢复模式
在消息广播时可能会发生如下两种情况:
1、leader执行commit了,但是在给follower发送commit的时候leader宕机了。
2、leader在发送proposal给follower之前,leader宕机了。
针对情况一:
Zookeeper选举出新的leader之后,leader回去检查磁盘上是否存在未commit的消息,如果存在,继续检查follower节点有没有对该请求进行commit,如果有过半节点返回了ACK,但是没有commit,那么新的leader需要完成commit操作。
针对情况二:
此情况下客户端的请求是失败的,假设过了一段时间以后leader节点又恢复了,不过这个时候角色就变为了follower了,它在检查自己磁盘的时候会发现自己有一条消息没有进行commit,此时就会检测消息的编号(也就是ZXID),由高32位和低32位组成,高32位是用来体现是否发生过leader切换的,低32位就是展示消息的顺序的。这个时候当前的节点就会根据高32位知道目前leader已经切换过了,所以就把当前的消息删除,然后从新的leader同步数据,这样保证了数据一致性。
二、leader选举
(1)先看一下在集群初始化时候的leader选举
1、假如现在有两台服务器,分别如下

node-1和node-2,myid是节点在集群中的唯一id,每台机器固定的,ZXID是事务id,因为是初始化,所以默认是0
2、因为一台机器无法进行leader选举,所以启动两台机,两台机启动后,就可以进行leader选举了
3、此时每台机在第一轮投票的时候都会把票投给自己,并把投票信息发送给其他机器,投票信息是以(myid,ZXID)为格式的。所以node-1会将(1,0)发给node-2,node-2会把(2,0)发给node-1。
此时两个节点的所有投票信息如下:
node-1:(1,0),(2,0)
node-2:(1,0),(2,0)
4、每个节点上会进行投票的pk,pk的优先权如下,ZXID大的优先作为leader,如果ZXID一样,则myid大的作为leader,所以此时两个节点的投票pk结果如下:
node-1:(2,0)
node-2:(2,0)
5、统计投票信息
如果某个节点的投票信息超过半数,则为leader,那么此时统计之后发现投给node-2的有两个,已经超过半数,那么node-2就是leader

(2)集群在运行过程中leader宕机,此时的leader选举
大致流程跟初始化时差不多,只是在每个节点接受其他节点投票信息之后会加一个判断,判断选举轮次。此时有三种情况:
1>外部投票选举轮次大于自己的轮次,则会立即更新自己的轮次,然后清空已经接收到的选票。然后使用初始化的选票来PK刚刚的外部投票以确定是否变更自己的投票(PK的规则跟概述里描述一致)。
2>外部投票的选举轮次小于自己的轮次,那么直接忽略该外部选票,不做任何处理。
3>外部投票的选举轮次等于自己的轮次,就进行选票PK。
接下来就是选票PK(PK规则依然是ZXID优先,依次是myid),统计选票,直至选出leader为止。
流程图如下:(侵删!)

在第1步会进行逻辑时针的递增。也就是投票轮次。
在第4步会判断当前Zookeeper的状态:
ZooKeeper的服务有四个状态:LOOKING, FOLLOWING, LEADING, OBSERVING
LOOKING:寻找Leader的状态。
FOLLOWING:跟随者状态。
LEADING:领导者状态。
OBSERVING:观察者状态。
如果当前服务的状态是LOOKING,那么说明还没选举出来Leader,继续下面的步骤,正常情况下一开始都是LOOKING的状态。
其中4-9会经过几轮循环直到Leader产生。








