zk选举流程分析

zk集群运行过程当中,服务器选举的源码剖析

在zk服务器集群启动过程当中,经QuorumPeerMain中,不光会建立ZooKeeperServer对象,同时会生成QuorumPeer对象,表明了ZooKeeper集群中的一台机器。在整个机器运行期间,负责维护该机器的运行状态,同时会根据状况发起Leader选举。下图是 《从PAXOS到ZOOKEEPER分布式一致性原理与实践》的服务器启动流程算法

QuorumPeer是一个独立的线程,维护着zk机器的状态。服务器

@Override
public synchronized void start() {
    loadDataBase();
    cnxnFactory.start();        
    startLeaderElection();
    super.start();
}

本次主要介绍的是选举相关的内容,至于其余操做能够看其余博客。以后的行文都是从startLeaderElection中衍生出来的。网络

基本概念:

SID:服务器ID,用来标示ZooKeeper集群中的机器,每台机器不能重复,和myid的值一直
ZXID:事务ID
Vote: 选票,具体的数据结构后面有
Quorum:过半机器数
选举轮次:logicalclock,zk服务器Leader选举的轮次

服务器类型:数据结构

在zk中,引入了Leader、Follwer和Observer三种角色。zk集群中的全部机器经过一个Leader选举过程来选定一台被称为Leader的机器,Leader服务器为客户端提供读和写服务。Follower和Observer都可以提供读服务,惟一的区别在于,Observer机器不参与Leader选举过程,也不参与写操做的过半写成功策略。所以,Observer存在的意义是:在不影响写性能的状况下提高集群的读性能。架构

服务器状态:分布式

+ LOOKING:Leader选举阶段
+ FOLLOWING:Follower服务器和Leader保持同步状态
+ LEADING:Leader服务器做为主进程领导状态。
+ OBSERVING:观察者状态,代表当前服务器是Observer,不参与投票

选举的目的就是选择出合适的Leader机器,由Leader机器决定事务性的Proposal处理过程,实现类两阶段提交协议(具体是ZAB协议)ide

QuorumPeer维护集群机器状态

QuorumPeer的职责就是不断地检测当前的zk机器的状态,执行对应的逻辑,简单来讲,就是根据服务所处的不一样状态执行不一样的逻辑。删除了一部分逻辑后,代码以下:性能

@Override
public void run() {
    setName("QuorumPeer" + "[myid=" + getId() + "]" +
            cnxnFactory.getLocalAddress());

     try {
        while (running) {
            switch (getPeerState()) {
            case LOOKING:
                LOG.info("LOOKING");
                try {
                    setBCVote(null);
                    setCurrentVote(makeLEStrategy().lookForLeader());
                } catch (Exception e) {
                    LOG.warn("Unexpected exception", e);
                    setPeerState(ServerState.LOOKING);
                }
                
                break;
            case OBSERVING:
                try {
                    LOG.info("OBSERVING");
                    setObserver(makeObserver(logFactory));
                    observer.observeLeader();
                } catch (Exception e) {
                    LOG.warn("Unexpected exception",e );                        
                } finally {
                    observer.shutdown();
                    setObserver(null);
                    setPeerState(ServerState.LOOKING);
                }
                break;
            case FOLLOWING:
                try {
                    LOG.info("FOLLOWING");
                    setFollower(makeFollower(logFactory));
                    follower.followLeader();
                } catch (Exception e) {
                    LOG.warn("Unexpected exception",e);
                } finally {
                    follower.shutdown();
                    setFollower(null);
                    setPeerState(ServerState.LOOKING);
                }
                break;
            case LEADING:
                LOG.info("LEADING");
                try {
                    setLeader(makeLeader(logFactory));
                    leader.lead();
                    setLeader(null);
                } catch (Exception e) {
                    LOG.warn("Unexpected exception",e);
                } finally {
                    if (leader != null) {
                        leader.shutdown("Forcing shutdown");
                        setLeader(null);
                    }
                    setPeerState(ServerState.LOOKING);
                }
                break;
            }
        }
    } finally {
        LOG.warn("QuorumPeer main thread exited");
    }
}

当机器处于LOOKING状态时,QuorumPeer会进行选举,可是具体的逻辑并非由QuorumPeer来负责的,总体的投票过程独立出来了,从逻辑执行的角度看,整个过程设计到两个主要的环节:this

  • 与其余的zk集群通讯的过程
  • 实现具体的选举算法

而QuorumPeer中默认使用的选举算法是FastLeaderElection,以后的分析也是基于FastLeaderElection而言的。线程

选举过程当中的总体架构

在集群启动的过程当中,QuorumPeer会根据配置实现不一样的选举策略 this.electionAlg = createElectionAlgorithm(electionType);

protected Election createElectionAlgorithm(int electionAlgorithm){
    Election le=null;

    switch (electionAlgorithm) {

    case 3:
        QuorumCnxManager qcm = new QuorumCnxManager(this);
        QuorumCnxManager.Listener listener = qcm.listener;
        if(listener != null){
            listener.start();
            le = new FastLeaderElection(this, qcm);
        } else {
            LOG.error("Null listener when initializing cnx manager");
        }
        break;
    default:
        assert false;
    }
    return le;
}

若是ClientCnxn是zk客户端中处理IO请求的管理器,QuorumCnxManager是zk集群间负责选举过程当中网络IO的管理器,在每台服务器启动的时候,都会启动一个QuorumCnxManager,用来维持各台服务器之间的网络通讯。

对于每一台zk机器,都须要创建一个TCP的端口监听,在QuorumCnxManager中交给Listener来处理,使用的是Socket的阻塞式IO(默认监听的端口是3888,是在config文件里面设置的)。在两两相互链接的过程当中,为了不两台机器之间重复地建立TCP链接,zk制定了链接的规则:只容许SID打的服务器主动和其余服务器创建链接。实现的方式也比较简单,在receiveConnection中,服务器会对比与本身创建链接的服务器的SID,判断是否接受请求,若是本身的SID更大,那么会断开链接,而后本身主动去和远程服务器创建链接。这段逻辑是由Listener来作的,且Listener独立线程,receivedConnection,创建链接后的示意图:

QuorumCnxManager是链接的管家,具体的TCP链接交给了Listener,可是对于选票的管理,内部还维护了一系列的队列:

  • recvQueue:消息接收队列,用来存放那些从其余服务器接收到的消息,单独的队列
  • 分组队列(quorumCnxManager中将zk集群中的每台机器按照SID单独分组造成队列集合):
    • queueSendMap:消息发送队列,用于保存待发送的消息。new ConcurrentHashMap<Long, ArrayBlockingQueue >(); 按照SID分组,分别为每台机器分配一个单独队列,保证各台机器之间的消息发放互不影响
    • senderWorderMap:发送器集合。每一个SendWorder消息发送器,都对应一台远程zk服务器,负责消息的发放。
    • lastMessageSent:最近发送过的消息,按照SID分组

基本的通讯流程以下:

以上内容主要是创建各台zk服务器之间的链接通讯过程,具体的选举策略zk抽象成了 Election ,主要分析的是FastLeaderElection方式(选举算法的核心部分):

public interface Election {
    public Vote lookForLeader() throws InterruptedException;
    public void shutdown();
}

FastLeaderElection选举算法

上面说过QuorumPeer检测到当前服务器的状态是LOOKING的时候,就会进行新一轮的选举,经过 setCurrentVote(makeLEStrategy().lookForLeader());也就是FastLeaderElection的lookForLeader来进行初始选择,实现的方式也很简单,主要的逻辑在FastLeaderElection.lookForLeader中实现:

基本流程先说明一下:

  • QuorumPeer会轮询检查当前服务器状态,若是发现State是LOOKING,调用Election的lookForLeader来开始新一轮的选举
  • FastLeaderElection会首先将logicallock++,表示新的一轮选举开始了
  • 构造初始的选票,Vote的内容就是选本身,而后通知zk集群中的其余机器
  • FastLeaderElection会一直轮询查状态,只要是LOOKING态,就会从recvqueue中获取其余服务器同步的选票信息,为了方便说明,记录为n
  • 根据n的票选信息状态,作相关的操做
    • LOOKING: 都处于无Leader态,比较一下选票的优劣,看是否更新本身的选票,若是更新了就同时通知给其余服务器
    • FOLLOWING、LEADING:说明集群中已经有Leader存在,更新一下本身的状态,结束本轮投票
    • OBSERVING:这票没什么卵用,直接舍弃(OBSERVER是不参与投票的)

根据上面的流程,能够大概说明一下FasterLeaderElection肯定选票更优的策略:

  • 若是外部投票中被推举的Leader服务器选举轮次大于自身的轮次,那么就更新选票
  • 若是选举轮次一致,就对比二者的ZXID,ZAB协议中ZXID越大的留存的信息也越多,所以若是ZXID大于本身的,那么就更新选票
  • 若是ZXID也一致,对比二者的SID,SID大,则优先级高

总结:

以上就是zk的默认选票流程,按照ZAB协议的两种状态分析:

  • 初始化的时候,处于同一轮次进行投票直到投票选择出一个Leader
  • 崩溃恢复阶段:
    • Leader服务器挂了,那么经历的和初始化流程相似的过程,选择Leader
    • Follower服务器挂了,那么本身在执行选举的过程当中,会收到其余服务器给的Leader选票信息,也能够肯定Leader所属
相关文章
相关标签/搜索