java集群选举_深入理解 ZK集群的Leader选举

前言

ZooKeeper对Zab协议的实现有自己的主备模型,即Leader和learner(Observer + Follower),有如下几种情况需要进行领导者的选举工作

情形1: 集群在启动的过程中,需要选举Leader

情形2: 集群正常启动后,leader因故障挂掉了,需要选举Leader

情形3: 集群中的Follower数量不足以通过半数检验,Leader会挂掉自己,选举新leader

情景4: 集群正常运行,新增加1个Follower

本篇博文,从这四个方面进行源码的追踪阅读

程序入口

QuorumPeer.java相当于集群中的每一个节点server,在它的start()方法中,完成当前节点的启动工作,源码如下:

// todo 进入了 QuorumPeer(意为仲裁人数)类中,可以把这个类理解成集群中的某一个点

@Override

public synchronized void start() {

// todo 从磁盘中加载数据到内存中

loadDataBase();

// todo 启动上下文的这个工厂,他是个线程类, 接受客户端的请求

cnxnFactory.start();

// todo 开启leader的选举工作

startLeaderElection();

// todo 确定服务器的角色, 启动的就是当前类的run方法在900行

super.start();

}

第一个loadDataBase();目的是将数据从集群中恢复到内存中

第二个cnxnFactory.start();是当前的节点可以接受来自客户端(java代码,或者控制台)发送过来的连接请求

第三个startLeaderElection();开启leader的选举工作, 但是其实他是初始化了一系列的辅助类,用来辅助leader的选举,并非真正在选举

当前类,quorumPeer继承了ZKThread,它本身就是一个线程类,super.start();就是启动它的run方法,在他的Run方法中有一个while循环,一开始在程序启动的阶段,所有的节点的默认值都是Looking,于是会进入这个分支中,在这个分之中会进行真正的leader选举工作

小结

从程序的入口介绍中,可以看出本篇文章在会着重看下startLeaderElection();做了哪些工作? 以及在looking分支中如何选举leader

情形1:集群在启动的过程中,选举新Leader

进入startLeaderElection();方法,源码如下, 他主要做了两件事

对本类QuorumPeer.java维护的变量(volatile private Vote currentVote;)初始化

createElectionAlgorithm()创建一个leader选举的方法

其实到现在,就剩下一个算法没过期了,就是fastLeaderElection

// TODO 开启投票选举Leader的工作

synchronized public void startLeaderElection() {

try {

// todo 创建了一个封装了投票结果对象 包含myid 最大的zxid 第几轮Leader

// todo 先投票给自己

// todo 跟进它的构造函数

currentVote = new Vote(myid, getLastLoggedZxid(), getCurrentEpoch());

} catch(IOException e) {

RuntimeException re = new RuntimeException(e.getMessage());

re.setStackTrace(e.getStackTrace());

throw re;

}

for (QuorumServer p : getView().values()) {

if (p.id == myid) {

myQuorumAddr = p.addr;

break;

}

}

if (myQuorumAddr == null) {

throw new RuntimeException("My id " + myid + " not in the peer list");

}

if (electionType == 0) {

try {

udpSocket = new DatagramSocket(myQuorumAddr.getPort());

responder = new ResponderThread();

responder.start();

} catch (SocketException e) {

throw new RuntimeException(e);

}

}

// todo 创建一个领导者选举的算法,这个算法还剩下一个唯一的实现 快速选举

this.electionAlg = createElectionAlgorithm(electionType);

}

继续跟进createElectionAlgorithm(electionType), 在这个方法中做了如下三件大事

创建了QuorumCnxnManager

创建Listenner

创建FastLeaderElection

protected Election createElectionAlgorithm(int electionAlgorithm){

Election le=null;

//TODO: use a factory rather than a switch

switch (electionAlgorithm) {

case 0:

le = new LeaderElection(this);

break;

case 1:

le = new AuthFastLeaderElection(this);

break;

case 2:

le = new AuthFastLeaderElection(this, true);

break;

case 3:

// todo 创建CnxnManager 上下文的管理器

qcm = createCnxnManager();

QuorumCnxManager.Listener listener = qcm.listener;

if(listener != null){

// todo 在这里将listener 开启

listener.start();

// todo 实例化领导者选举的算法

le = new FastLeaderElection(this, qcm);

} else {

LOG.error("Null listener when initializing cnx manager");

}

break;

准备选举环境

QuorumManager

1a3937ba1e9a0e8baa60405d95baf632.png

上图是QuorumCnxManager的类图,看一下,它有6个内部类, 其中的除了Message外其他都是可以单独运行的线程类

这个类有着举足轻重的作用,它是集群中全体节点共享辅助类, 那到底有什么作用呢? 我不卖关子直接说,因为leader的选举是通过投票决议出来的,既然要相互投票,那集群中的各个点就得两两之间建立连接,这个QuorumCnxManager就负责维护集群中的各个点的通信

它维护了两种队列,源码在下面,第一个队列被存入了ConcurrentHashMap中 key就是节点的myid(或者说是serverId),值可以理解成存储它往其他服务器发送投票的队列

第二个队列是收到的其他服务器发送过来的msg

// todo key=serverId(myid) value = 保存着当前服务器向其他服务器发送消息的队列

final ConcurrentHashMap> queueSendMap;

// todo 接收到的所有数据都在这个队列中

public final ArrayBlockingQueue recvQueue;

2f1083f3be36efa5231a819914c77cc8.png

如上图是手绘的QuorumCnxManager.java的体系图,最直观的可以看到它内部的三条线程类,那三条线程类的run()方法又分别做了什么呢?

SendWorker的run(), 可以看到它根据sid取出了当前节点对应的队列,然后将队列中的数据往外发送

public void run() {

threadCnt.incrementAndGet();

try {

ArrayBlockingQueue bq = queueSendMap.get(sid);

if (bq == null || isSendQueueEmpty(bq)) {

ByteBuffer b = lastMessageSent.get(sid);

if (b != null) {

LOG.debug("Attempting to send lastMessage to sid=" + sid);

send(b);

}

}

} catch (IOException e) {

LOG.error("Failed to send last message. Shutting down thread.", e);

this.finish();

}

try {

while (running && !shutdown && sock != null) {

ByteBuffer b = null;

try {

// todo 取出任务所在的队列

ArrayBlockingQueue bq = queueSendMap.get(sid);

if (bq != null) {

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值