ZAB中,请求在主节点上执行,那么主节点一旦崩溃了,怎么办呢?

必然,选举新的领导者,新的主节点,如何选出呢?

整体的领导者选举,就是快速领导者选举协议 FastLeader Election.更能深刻的理解ZAB协议

首先是ZAB中支持的成员身份

分别是3种成员身份 ;领导者 跟随者 观察者

Leader:主节点,所有写请求的执行者

Follower:备份节点,响应心跳,进行存储,进行投票,可以处理读请求

Observer:可以存储和读请求,不能投票

还定义了4种成员状态

LOOKING:选举状态,这个状态下的节点认为当前集群中没有领导者,会发起领导者选举

FOLLOWING:跟随者状态

LEADING:领导者状态

OBSERVING:观察者状态

具体的选举过程如下

我们首先规定的投的投票信息的格式是

<proposedLeader,proposedEpoch,proposedLastZxid,node>,

proposedLeader,节点提议的,领导者的集群ID,集群配置指定的ID,

proposedEpoch,节点提议的,任期编号

proposedLastZxid,节点提议的,领导者事务标识符最大值

node:投票的节点,比如节点B

一个集群,其中由A B C组成 A是领导者, B C是跟随者,那么epoch是1,lastZxid是101和102,那么节点A如果宕机了,会如何选举呢?

图片

跟随者检测到领导者节点的读操作超时了,跟随者会变更节点状态,将自己的节点状态变更为LOOKING,然后发起领导者选举

我们这里为了模拟,假设B C同时发起选举

图片

每个节点都会创建一张选票,这张选票首先给自己的,就是说,B C都推荐自己为领导者,分别创建了选票,<2,1,101,B > <3,1,102,C>

然后彼此进行广播

首先,节点会自己发给自己选票,然后进行自我投票,这一步必然通过

图片

集群各个节点收到了选票后,为了选举出数据最为完整的节点,对于每一张收到的选票,都需要进行领导者的PK,PK的规则如下

首先检查任期编号,Epoch,任期编号大的节点为领导者

任期编号相同,就比较事务标识符中最大的值,值大的胜

事务标识符相同,那就比较集群ID,集群ID大的节点作为领导者

如果自己的提议失败了,就重新调整选票内容,将选票提议的领导者作为领导者

而且,收到的提议和自己的提议的领导者相同时,就不调整选票信息

图片

接着节点B C 分别接受对方的选票,B接到来自C的选票,C收到来自B的选票

图片

对于C来说,收到了<2,1,101,B>任期编号相同,那么比较事务标识符,因为C的事务标识符更加的大,那么C更加适合做领导者,C就不做任何的调整

对于C来说,收到了<3,1,102,C>任期编号相同,那么比较事务标识符,因为C的事务标识符更加的大,那么C更加适合做领导者,B就将自己的领导者重新设置为C,并且重新发送一次

图片

当节点B C收到来自节点B,新的选票的时候,因为这张选票 <3,1,102,B>提议的领导者,就是自己提议的领导者,所以不进行变化

最后,C赢得了大多数的选票,最后B C根据投票结果,变更节点状态,退出选举,因为C是Leader,B就是FOLLOWING

最后补充一点,逻辑时钟,会影响选举的有效性,具体来说,逻辑时钟大的节点不会接收来自值小的节点的投票,比如,节点A B 的逻辑时钟分别来自第一轮和第二轮,那么会拒绝第一轮的投票

而且领导者的选举目标是,大多数节点之中,数据最为完整的节点,事务标识符最为大的节点

接下来,我们看一下Zookeeper的源码

首先,在zookeeper之中,成员状态是QuorumPeer.java中实现的,是一个枚举

public enum ServerState{}

LOOKING,

FOLLOWING,

LEADING,

OBSERVING

}

没有定义成员身份,直接使用成员状态表示,这样也足够了

Zookeeper的领导者选举流程如下

是在FastLeaderElection.lookForLeader()中实现的

图片

1.首先是在处于跟随者的节点,在Follower.followLeader()函数中,周期的处理数据包

QuorumPacket qp = new QuorumPacket();

while (this.isRunning()) {

// 读取数据包

readPacket(qp);

// 处理数据包

processPacket(qp);

}

然后跟随者检测到连接到领导者的读操作超时了,就会抛出异常,跳出循环体,将自己的状态更为选举状态

public void run() {

case FOLLOWING:

……

finally {

// 关闭跟随者节点

follower.shutdown();

setFollower(null);

// 设置状态为选举状态

updateServerState();

}

break;

……

}

3,当节点处于选举状态时候,会调用makeLEStrategy().lookForLeader()函数,发起领导者选举

setCurrentVote(makeLEStrategy().lookForLeader());

4.在这个函数中,首先对逻辑时钟进行了增加操作,然后开启新一轮的领导者选举,然后创建投票提案,并进行广播

synchronized (this) {

// 对逻辑时钟的值执行加一操作

logicalclock.incrementAndGet();

// 创建投票提案,并默认推荐自己为领导者

updateProposal(getInitId(), getInitLastLoggedZxid(),

getPeerEpoch());

}

// 广播投票信息给所有节点

sendNotifications();

5.节点处于选举状态,周期的从列表中读取接收到的投票信息,直到选举成功

while ((self.getPeerState() == ServerState.LOOKING) && (!stop)) {

// 从队列中读取接收到的投票信息

Notification n = recvqueue.poll(notTimeout, TimeUnit.MILLISECONDS);

……

}

6.接收到的新的投票信息,节点进行领导者PK,来判断谁更加适合当领导者,如果投票信息中节点比起自己更加适合领导者,那就替换

else if (totalOrderPredicate(n.leader, n.zxid, n.peerEpoch, proposedLeader, proposedZxid, proposedEpoch)) {

// 投票信息中提议的节点比自己提议的节点更适合作为领导者,更新投票信息,并推荐投票信息中提议的节点

updateProposal(n.leader, n.zxid, n.peerEpoch);

// 将新的投票信息广播给所有节点

sendNotifications();

}

7.如果自己的提议者赢了这次选举,就进行状态的变更,不然就继续从接受队列中读取新的投票信息

8.当提议的领导者获得大多数的选票的时候,根据投票的结果,跑到当前的节点的状态,是领导者还是跟随者,变更节点状态,退出选举

if (voteSet.hasAllQuorums()) {

……

// 根据投票结果,判断并设置节点状态

setPeerState(proposedLeader, voteSet);

// 退出领导者选举

Vote endVote = new Vote(proposedLeader, proposedZxid, logicalclock.get(), proposedEpoch);

leaveInstance(endVote);

return endVote;

……

}

这样,我们就看了ZAB是如何选举领导者的?以及如何在Zookeeper中实现的

领导者选举的目标,是选举出大多数数据完整的节点,事务标识符最大的节点

以及选举时候,是根据什么样的顺序进行选举的

ZAB协议中,ZAB协议是通过快速领导者选举,来选举出新的领导者的,那么会出现选票会瓜分的情况吗?

必然可能啊,ZAB是一种脱胎于Multi-Paxos的算法,其本质上也是一种投票选举,那么对于这种投票选举,设置不同的选举时间是一种相对较好的选择

看到这个选举突然想到了网络环路中STP的算法解决

发表评论

邮箱地址不会被公开。 必填项已用*标注