zk源码—6.Leader选举的实现原理二

大纲

1.zk是如何实现数据一致性的

(1)数据一致性分析

(2)实现数据一致性的广播模式

(3)实现数据一致性的恢复模式

2.zk是如何进行Leader选举的

(1)服务器启动时的Leader选举

(2)服务器运行时的Leader选举

(3)Leader选举的算法设计

(4)Leader选举的实现细节

2.zk是如何进行Leader选举的

(1)服务器启动时的Leader选举流程概述

(2)服务器运行时的Leader选举流程概述

(3)Leader选举的规则

(4)Leader选举的实现细节

(5)Leader选举算法的实现流程

(1)服务器启动时的Leader选举流程概述

一.向其他服务器发出一个投自己的投票

二.接收来自其他服务器的投票

三.PK投票

四.统计投票

五.改变服务器状态

一个zk服务要想满足集群运行方式,至少需要三台服务器。下面以3台机器组成的服务器集群为例。当只有一台服务器启动时,是无法进行Leader选举的。当有两台服务器启动,两台机器可以相互通信时,每台机器都会试图找到一个Leader,于是便进入了Leader选举流程。

一.向其他服务器发出一个投自己的投票

每个服务器刚启动时,都会将自己作为Leader服务器来生成投票。投票包括的信息是:服务器ID(SID)、事务ID(ZXID),可记为(SID, ZXID)。该投票信息会发给集群中的其他所有机器。

二.接收来自其他服务器的投票

每个服务器接收到投票后,首先会检查该投票的有效性,包括检查是否是本轮投票、是否来自LOOKING状态的服务器等。

三.PK投票

每个服务器接收到投票并检查有效后,会PK自己的投票和收到的投票。

PK规则一:ZXID比较大的优先作为Leader

PK规则二:ZXID相同则SID较大的为Leader

PK出的Leader不是服务器自己,则更新自己的投票并重新把投票发出去。

四.统计投票

每次投票后,都会统计所有投票,判断是否已有过半机器收到相同投票。

五.改变服务器状态

一旦确定了Leader,每个服务器都会更新自己的状态。如果是Leader,那么服务器状态就变为LEADING。如果是Follower,那么服务器状态就变为FOLLOWING。

(2)服务器运行时的Leader选举流程概述

zk集群一旦选出一个Leader,所有服务器的集群角色一般不会再发生变化。如果有非Leader挂了或新机器加入,此时是不会影响Leader的。如果Leader挂了,那么整个集群将暂时无法服务,进入新一轮Leader选举。服务器运行期间的Leader选举和启动时的Leader选举过程是一致的。

一.变更状态

当Leader挂了,Follower服务器都会将其服务器状态变更为LOOKING。变更为LOOKING状态后,Follower服务器便开始进入Leader选举流程。

二.向其他服务器发出一个投自己的投票

三.接收来自其他服务器的投票

四.PK投票

五.统计投票

六.改变服务器状态

(3)Leader选举的规则

一.集群进入Leader选举的情况

二.一台机器进入Leader选举的情况

三.变更投票的规则

四.确定Leader的规则

一.集群进入Leader选举的情况

情况一:集群一开始启动时没有Leader

情况二:集群运行期间Leader挂了

二.一台机器进入Leader选举的情况

情况一:集群中本来就已经存在一个Leader了,即该机器是加入集群的。这种情况通常是集群中的某一台机器启动比较晚,在它启动前集群已工作。对于这种情况,当该机器试图去选举Leader时,会被告知当前的Leader。于是该机器只需要和Leader建立起连接,并进行数据同步即可。

情况二:集群中确实不存在Leader。

三.变更投票的规则

集群中的每台机器在发出自己的投票后,都会开始收到其他机器的投票。每台机器都会根据如下规则来PK收到的投票,并以此决定是否变更投票。每次PK投票,都是对比(vote_sid, vote_zxid)和(self_sid, self_zxid)的过程。

规则一:如果vote_zxid大于self_zxid,那么就认可收到的投票(vote_sid, vote_zxid),并再次将该投票发送出去。

规则二:如果vote_zxid小于self_zxid,那么就坚持自己的投票(self_sid, self_zxid),不做任何变更。

规则三:如果vote_zxid等于self_zxid,且vote_sid大于self_sid,那么就认可收到的投票(vote_sid, vote_zxid),并再次将该投票发送出去。

规则四:如果vote_zxid等于self_zxid,且vote_sid小于self_sid,那么就坚持自己的投票(self_sid, self_zxid),不做任何变更。

四.确定Leader的规则

如果一台机器收到了过半相同投票,那么这个投票对应的SID就是Leader。哪台服务器上的数据越新,ZXID越大,那么就越有可能成为Leader。

(4)Leader选举的实现细节

一.服务器状态

二.投票数据结构

三.网络连接管理器QuorumCnxManager

四.建立连接和消息接收与发送

五.FastLeaderElection的选票管理

一.服务器状态

QuorumPeer的ServerState枚举类列举了4种服务器状态。

public class QuorumPeer extends ZooKeeperThread implements QuorumStats.Provider {
    ...
    public enum ServerState {
        //寻找Leader状态
        LOOKING,//当服务器处于该状态时,认为集群中没有Leader,因此会进入Leader选举流程
        //跟随者状态
        FOLLOWING,//表明当前服务器的集群角色是Follower
        //领导者状态
        LEADING,//表明当前服务器的集群角色是Leader
        //观察者状态
        OBSERVING;//表明当前服务器的集群角色是Observer
    }
    ...
}

二.投票数据结构

public class Vote {
    final private long id;//被选举的Leader的SID
    final private long zxid;//被选举的Leader的ZXID
    final private long electionEpoch;//选举轮次,每次进入新一轮的投票后,都会对该值加1
    final private long peerEpoch;//被选举的Leader的epoch
    final private ServerState state;//当前服务器的状态
    ...
}

三.网络连接管理器QuorumCnxManager

ClientCnxn是zk客户端用于处理客户端请求的网络连接管理器;
ServerCnxnFactory是zk服务端用于处理客户端请求的网络连接工厂;
LearnerCnxAcceptor是Leader用来处理Learner连接的网络连接管理器;
LearnerHandler是Leader用来处理Learner请求的网络处理器;
QuorumCnxManager是QurumPeer处理Leader选举的网络连接管理器;

每个服务器启动时,都会启动一个QuorumCnxManager。QuorumCnxManager会负责Leader选举过程中服务器间的网络通信。

QuorumCnxManager的核心数据结构:

消息接收队列:recvQueue
各服务器的消息发送队列集合:queueSendMap
各服务器的发送器集合:senderWorkerMap
各服务器的最近发送消息集合:lastMessageSent
public class QuorumCnxManager {
    //消息接收队列,用于存放从其他服务器接收到的消息
    public final ArrayBlockingQueue<Message> recvQueue;
    //各服务器对应的消息发送队列集合,用于保存那些待发送的消息,按SID分组,保证各台机器间的消息发送互不影响
    final ConcurrentHashMap<Long, ArrayBlockingQueue<ByteBuffer>> queueSendMap;
    //各服务器对应的发送器集合,按SID分组,每一台服务器都对应一个SendWorker发送器负责消息的发送
    final ConcurrentHashMap<Long, SendWorker> senderWorkerMap;
    //各服务器对应的最近发送消息集合,在这个集合中会为每个SID保留最近发送过的一个消息
    final ConcurrentHashMap<Long, ByteBuffer> lastMessageSent;
    ...
}

四.建立连接和消息接收与发送

为了能够相互投票,zk集群中的所有机器都需要两两建立网络连接。QuorumCnxManager启动时,会创建一个ServerSocket来监听3888端口。开启监听后,服务器就能接收到其他服务器发起的创建连接请求。在QuorumPeer启动时,会通过Election的lookForLeader()方法来发起连接。

服务器在收到其他服务器的连接请求时,会由QuorumCnxManager的receiveConnection()方法处理。为了避免两台机器重复创建TCP连接,zk设计了一套建立TCP连接的规则:只允许SID大的服务器主动和其他服务器建立连接,否则断开当前连接。

在QuorumCnxManager的receiveConnection()方法中,服务器会通过对比自己和远程服务器的SID值来判断是否接受连接请求。如果当前服务器发现自己的SID值更大,那么会断开当前连接,然后自己主动去和远程服务器建立连接。

一旦建立起连接,就会根据远程服务器的SID,创建并启动相应的消息发送器SendWorker和消息接收器RecvWorker。

消息的接收过程是由消息接收器RecvWorker负责的,zk服务器会为每个远程服务器单独分配一个消息接收器RecvWorker。每个RecvWorker只需不断从TCP连接中读取消息保存到recvQueue队列中。

消息的发送过程是由消息发送器SendWorker负责的,zk服务器会为每个远程服务器单独分配一个消息发送器SendWorker。每个SendWorker只需不断从对应的消息发送队列获取消息进行发送即可。

一旦zk服务器发现针对当前远程服务器的消息发送队列为空,那么就从lastMessageSent中取出一个最近发送的消息进行再次发送,以此解决上次发送的消息没有被接收到和没有被正确处理的问题。

建立连接的代码如下:

public class QuorumPeerMain {
    protected QuorumPeer quorumPeer;
    
    public void runFromConfig(QuorumPeerConfig config) throws IOException, AdminServerException {
        ...
        quorumPeer.start();
        quorumPeer.join();
        ...
    }
    ...
}

public class QuorumPeer extends ZooKeeperThread implements QuorumStats.Provider {
    ...
    public synchronized void start() {
        loadDataBase();
        startServerCnxnFactory();
        adminServer.start();
        //初始化Leader选举(初始化当前投票+监听选举端口+启动选举守护线程)
        startLeaderElection();
        startJvmPauseMonitor();
        super.start();
    }
    
    @Override
    public void run() {
        ...
        while (running) {
            switch (getPeerState()) {
                case LOOKING:
                    ...
                    if (shuttingDownLE) {
                        shuttingDownLE = false;
                        startLeaderElection();
                    }
                    //调用QuorumPeer.electionAlg的lookForLeader(),也就是FastLeaderElection.lookForLeader()开启一轮选举
                    setCurrentVote(makeLEStrategy().lookForLeader());
                ...          
            }
            ...
        }
        ...
    }
    
    //开始Leader选举
    //创建选举服务端QuorumCnxManager并启动监听 + 创建选举算法FastLeaderElection并启动
    //将FastLeaderElection实例赋值给QuorumPeer.electionAlg
    synchronized public void startLeaderElection() {
        if (getPeerState() == ServerState.LOOKING) {
            //设置当前投票
            currentVote = new Vote(myid, getLastLoggedZxid(), getCurrentEpoch());
        }
        ...
        this.electionAlg = createElectionAlgorithm(electionType);
    }
    
    protected Election createElectionAlgorithm(int electionAlgorithm){
        Election le=null;
        ...
        //创建网络连接管理器QuorumCnxManager
        QuorumCnxManager qcm = createCnxnManager();
        ...
        QuorumCnxManager.Listener listener = qcm.listener;
        //启动服务器并监听3888端口,等待其他服务器过来建立连接
        listener.start();
        FastLeaderElection fle = new FastLeaderElection(this, qcm);
        fle.start();
        le = fle;
        return le;
    }
    
    public QuorumCnxManager createCnxnManager() {
        return new QuorumCnxManager(...);
    }
    ...
}

public class QuorumCnxManager {
    //消息接收队列,用于存放从其他服务器接收到的消息
    public final ArrayBlockingQueue<Message> recvQueue;
    //各服务器对应的消息发送队列集合,用于保存那些待发送的消息,按SID分组,保证各台机器间的消息发送互不影响
    final ConcurrentHashMap<Long, ArrayBlockingQueue<ByteBuffer>> queueSendMap;
    //各服务器对应的发送器集合,按SID分组,每一台服务器都对应一个SendWorker发送器负责消息的发送
    final ConcurrentHashMap<Long, SendWorker> senderWorkerMap;
    //各服务器对应的最近发送消息集合,在这个集合中会为每个SID保留最近发送过的一个消息
    final ConcurrentHashMap<Long, ByteBuffer> lastMessageSent;
    ...
    public class Listener extends ZooKeeperThread {
        volatile ServerSocket ss = null;
        ...
        public void run() {
            ...
            InetSocketAddress addr;
            Socket client = null;
            while ((!shutdown) && (portBindMaxRetry == 0 || numRetries < portBindMaxRetry)) {
                ...
                ss = new ServerSocket();
                ss.setReuseAddress(true);
                addr = new InetSocketAddress(port);
                ss.bind(addr);
                while (!shutdown) {
                    client = ss.accept();
                    ...
                    //处理其他服务发送过来的建立连接请求
                    receiveConnection(client);
                    ...
                }
            }
            ...
        }
        ...
    }
    
    public void receiveConnection(final Socket sock) {
        DataInputStream din = new DataInputStream(new BufferedInputStream(sock.getInputStream()));
        handleConnection(sock, din);
    }
    
    private void handleConnection(Socket sock, DataInputStream din) throws IOException {
        ...
        //通过对比当前服务器自己的SID和远程服务器的SID,来判断是否接受连接请求
        if (sid < self.getId()) {
            //当前服务器自己的SID更大,则断开连接
            SendWorker sw = senderWorkerMap.get(sid);
            if (sw != null) {//先关闭消息发送器
                sw.finish();
            }
            //断开当前连接
            closeSocket(sock);
            //当前服务器重新主动去和远程服务器建立连接
            if (electionAddr != null) {
                connectOne(sid, electionAddr);
            } else {
                connectOne(sid);
            }
        } else if (sid == self.getId()) {
            ...
        } else {
            //当前服务器自己的SID小
            //则创建并启动相应的消息发送器SendWorker和消息接收器RecvWorker
            SendWorker sw = new SendWorker(sock, sid);
            RecvWorker rw = new RecvWorker(sock, din, sid, sw);
            sw.setRecv(rw);//将消息接收器传入消息发送器中
            SendWorker vsw = senderWorkerMap.get(sid);
            if (vsw != null) {
                vsw.finish();
            }
            senderWorkerMap.put(sid, sw);
            queueSendMap.putIfAbsent(sid, new ArrayBlockingQueue<ByteBuffer>(SEND_CAPACITY));
            sw.start();
            rw.start();
        }
    }
    
    private void closeSocket(Socket sock) {
        ...
        sock.close();
    }
}

消息接收与发送的代码如下:

public class QuorumCnxManager {
    //消息接收队列,用于存放从其他服务器接收到的消息
    public final ArrayBlockingQueue<Message> recvQueue;
    //各服务器对应的消息发送队列集合,用于保存那些待发送的消息,按SID分组,保证各台机器间的消息发送互不影响
    final ConcurrentHashMap<Long, ArrayBlockingQueue<ByteBuffer>> queueSendMap;
    //各服务器对应的发送器集合,按SID分组,每一台服务器都对应一个SendWorker发送器负责消息的发送
    final ConcurrentHashMap<Long, SendWorker> senderWorkerMap;
    //各服务器对应的最近发送消息集合,在这个集合中会为每个SID保留最近发送过的一个消息
    final ConcurrentHashMap<Long, ByteBuffer> lastMessageSent;
    ...
    //消息发送器
    class SendWorker extends ZooKeeperThread {
        Long sid;
        Socket sock;
        RecvWorker recvWorker;
        ...
        SendWorker(Socket sock, Long sid) {
            super("SendWorker:" + sid);
            this.sid = sid;
            this.sock = sock;
            ...
        }
        
        synchronized void setRecv(RecvWorker recvWorker) {
            this.recvWorker = recvWorker;
        }
        ...
        public void run() {
            ...
            //一旦zk服务器发现针对sid服务器的消息发送队列为空,
            //那么就从lastMessageSent中取出一个最近发送的消息进行再次发送,
            //以此解决上次发送的消息没有被接收到和没有被正确处理的问题;
            ArrayBlockingQueue<ByteBuffer> 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);
                }
            }
            while (running && !shutdown && sock != null) {
                ByteBuffer b = null;
                //取出要发送给sid机器的消息队列
                ArrayBlockingQueue<ByteBuffer> bq = queueSendMap.get(sid);
                if (bq != null) {
                    //取出要发送的消息
                    b = pollSendQueue(bq, 1000, TimeUnit.MILLISECONDS);
                } else {
                    LOG.error("No queue of incoming messages for " + "server " + sid);
                    break;
                }
                if (b != null) {
                    //设置这台sid机器最近一次发送的消息
                    lastMessageSent.put(sid, b);
                    send(b);
                }
                ...
            }
            //关闭针对sid机器的消息发送线程
            this.finish();
        }
        
        //关闭针对sid机器的消息发送线程
        synchronized boolean finish() {
            LOG.debug("Calling SendWorker.finish for {}", sid);
            if (!running) return running;
            running = false;
            closeSocket(sock);
            this.interrupt();
            if (recvWorker != null) recvWorker.finish();
            LOG.debug("Removing entry from senderWorkerMap sid=" + sid);
            senderWorkerMap.remove(sid, this);
            threadCnt.decrementAndGet();
            return running;
        }
    }
    ...
    //消息接收器
    class RecvWorker extends ZooKeeperThread {
        Long sid;
        Socket sock;
        final SendWorker sw;
        final DataInputStream din;
        ...
        RecvWorker(Socket sock, DataInputStream din, Long sid, SendWorker sw) {
            super("RecvWorker:" + sid);
            this.sid = sid;
            this.sock = sock;
            this.sw = sw;
            this.din = din;
            ...
        }
        
        public void run() {
            ...
            while (running && !shutdown && sock != null) {
                int length = din.readInt();
                ...
                byte[] msgArray = new byte[length];
                din.readFully(msgArray, 0, length);
                ByteBuffer message = ByteBuffer.wrap(msgArray);
                //将消息添加到消息接收队列中
                addToRecvQueue(new Message(message.duplicate(), sid));
            }
            sw.finish();
            closeSocket(sock);
        }
        
        synchronized boolean finish() {
            LOG.debug("RecvWorker.finish called. sid: {}. myId: {}", sid, QuorumCnxManager.this.mySid);
            if (!running) return running;
            running = false;
            this.interrupt();
            threadCnt.decrementAndGet();
            return running;
        }
    }
    ...
    //将消息添加到消息接收队列中
    public void addToRecvQueue(Message msg) {
        synchronized(recvQLock) {
            if (recvQueue.remainingCapacity() == 0) {
                recvQueue.remove();
            }
            recvQueue.add(msg);
        }
    }
}

五.FastLeaderElection的选票管理

FastLeaderElection的核心数据结构:

选票发送队列:sendqueue

选票接收队列:recvqueue

选票管理器:messenger

选票接收器:WorkerReceiver

选票发送器:WorkerSender

选票接收器WorkerReceiver会不断从QuorumCnxManager中,获取其他服务器发来的选举投票消息并转换成一个选票,然后保存到recvqueue选票接收队列中。在此过程中,如果发现其他服务器发来的投票的选举轮次小于当前服务器,那么就直接忽略这个其他服务器发来的投票,同时立即发出自己的投票。

选票发送器WorkerSender会不断从sendqueue队列中获取待发送的选票,并将其传递给QuorumCnxManager中进行发送。

public class FastLeaderElection implements Election {
    LinkedBlockingQueue<ToSend> sendqueue;//选票发送队列,用于保存待发送的选票
    LinkedBlockingQueue<Notification> recvqueue;//选票接收队列,用于保存接收到的外部选票
    Messenger messenger;//选票管理器
    QuorumCnxManager manager;//Leader选举时的网络连接管理器
    ...
    //通过构造方法传入Leader选举时的网络连接管理器QuorumCnxManager
    public FastLeaderElection(QuorumPeer self, QuorumCnxManager manager) {
        this.stop = false;
        this.manager = manager;
        starter(self, manager);
    }
    
    private void starter(QuorumPeer self, QuorumCnxManager manager) {
        ...
        sendqueue = new LinkedBlockingQueue<ToSend>();//初始化选票发送器
        recvqueue = new LinkedBlockingQueue<Notification>();//初始化选票接收器
        this.messenger = new Messenger(manager);//创建选票管理器
    }
    
    public void start() {
        this.messenger.start();//启动选票管理器
    }
    
    //选票管理器
    protected class Messenger {
        WorkerReceiver wr;//选票接收器
        WorkerSender ws;//选票发送器
        Thread wsThread = null;
        Thread wrThread = null;
        
        Messenger(QuorumCnxManager manager) {
            this.ws = new WorkerSender(manager);
            this.wsThread = new Thread(this.ws, "WorkerSender[myid=" + self.getId() + "]");
            this.wsThread.setDaemon(true);
            this.wr = new WorkerReceiver(manager);
            this.wrThread = new Thread(this.wr, "WorkerReceiver[myid=" + self.getId() + "]");
            this.wrThread.setDaemon(true);
        }
      
        void start(){
            this.wsThread.start();
            this.wrThread.start();
        }
        ...
        //选票接收器
        class WorkerReceiver extends ZooKeeperThread {
            ...
            public void run() {
                Message response;
                while (!stop) {
                    //从QuorumCnxManager中获取其他服务器发来的投票消息
                    response = manager.pollRecvQueue(3000, TimeUnit.MILLISECONDS);
                    if (response == null) continue;
                    ...
                    //将获取到的其他服务器发来的投票消息转换成一个选票Notification
                    Notification n = new Notification();
                    ...
                    if (self.getPeerState() == QuorumPeer.ServerState.LOOKING) {
                        //将选票n保存到recvqueue选票接收队列中
                        recvqueue.offer(n);
                        //如果其他服务器发来的投票的选举轮次小于当前服务器
                        if ((ackstate == QuorumPeer.ServerState.LOOKING) && (n.electionEpoch < logicalclock.get())){
                            //发出自己的投票
                            Vote v = getVote();
                            QuorumVerifier qv = self.getQuorumVerifier();
                            ToSend notmsg = new ToSend(...);
                            sendqueue.offer(notmsg);
                        }
                        ...
                    }
                    ...
                }
            }
            ...
        }
        
        //选票发送器
        class WorkerSender extends ZooKeeperThread {
            volatile boolean stop;
            QuorumCnxManager manager;
            
            WorkerSender(QuorumCnxManager manager) {
                super("WorkerSender");
                this.stop = false;
                this.manager = manager;
            }
            
            public void run() {
                while (!stop) {
                    //选票发送器WorkerSender会不断从sendqueue队列中获取待发送的选票
                    ToSend m = sendqueue.poll(3000, TimeUnit.MILLISECONDS);
                    if (m == null) continue;
                    //将待发送的选票传递给QuorumCnxManager中进行发送
                    process(m);
                }
                LOG.info("WorkerSender is down");
            }
            
            void process(ToSend m) {
                ByteBuffer requestBuffer = buildMsg(...);
                manager.toSend(m.sid, requestBuffer);
            }
        }
    }
    ...
}

public class QuorumCnxManager {
    //消息接收队列,用于存放从其他服务器接收到的消息
    public final ArrayBlockingQueue<Message> recvQueue;
    ...
    public Message pollRecvQueue(long timeout, TimeUnit unit) throws InterruptedException {
        return recvQueue.poll(timeout, unit);
    }
    
    public void toSend(Long sid, ByteBuffer b) {
        //If sending message to myself, then simply enqueue it (loopback).
        if (this.mySid == sid) {
            b.position(0);
            addToRecvQueue(new Message(b.duplicate(), sid));
            //Otherwise send to the corresponding thread to send.
        } else {
            //Start a new connection if doesn't have one already.
            ArrayBlockingQueue<ByteBuffer> bq = new ArrayBlockingQueue<ByteBuffer>(SEND_CAPACITY);
            ArrayBlockingQueue<ByteBuffer> oldq = queueSendMap.putIfAbsent(sid, bq);
            if (oldq != null) {
                addToSendQueue(oldq, b);
            } else {
                addToSendQueue(bq, b);
            }
            connectOne(sid);
        }
    }
    ...
}

如下是选票管理各组件间的协作图:

图片

(5)Leader选举算法的实现流程

当zk服务器检测到当前服务器状态为LOOKING时,就会触发Leader选举,也就是调用FastLeaderElection的lookForLeader()方法来进行Leader选举。Leader选举算法的具体流程如下:

图片

一.自增选举轮次


FastLeaderElection.logicalclock用于标识当前Leader的选举轮次,Leader选举规定所有有效的投票都必须在同一轮次中。

二.初始化选票

在开始新一轮投票之前,每个服务器都会首先初始化自己的选票。在初始化阶段,每个服务器都会将自己推荐为Leader。

三.发送初始化选票

在完成选票的初始化后,服务器就会发起第一次投票。zk会将刚刚初始化好的选票放入sendqueue选票发送队列中,然后由选票发送器WorkerSender负责发送出去。

四.接收外部投票

接着通过一个while循环不断从recvqueue选票接收队列中获取外部投票。如果服务器发现无法获取到任何的外部投票,那么就会确认和其他服务器建立的连接是否还有效。如果发现连接没有效,那么就会马上建立连接。如果连接还有效,那么就再次发送服务器自己的内部投票。

五.判断选举轮次

判断选举轮次的原因:只有在同一个选举轮次的投票才是有效的投票。

情况一:如果外部投票的选举轮次大于内部投票,那么服务器会先更新自己的选举轮次logicalclock。然后清空所有已经收到的投票,即清空归档选票集合recvset。接着让内部投票和外部投票进行PK以确定是否要变更内部投票。

情况二:如果外部投票的选举轮次小于内部投票,那么服务器会直接忽略该外部投票,不做任何处理。

情况三:如果外部投票的选举轮次等于内部投票,那么就让内部投票和外部投票进行PK。

六.选票PK

在接收到来自其他服务器的有效的外部投票后,接着通过FastLeaderElection的totalOrderPredicate()方法进行选票PK。主要从选举轮次、ZXID、和SID来考虑。如果外部投票的选举轮次大,则进行投票变更。如果选举轮次一致,且外部投票的ZXID大,则进行投票变更。如果选举轮次+ZXID一致,且外部投票的SID大,也进行投票变更。

七.变更投票

也就是使用外部投票的选票信息来覆盖内部投票。

八.选票归档

无论是否变更投票,都会将收到的有效的外部投票放入选票集合recvset。recvset会按SID记录当前服务器在本轮次的选举中收到的所有外部投票。

九.统计投票

完成选票归档后,就可以开始统计投票了。统计投票就是确定是否已有过半服务器认可当前的内部投票。如果是,则终止投票;否则,继续接收外部投票进行处理。

十.更新服务器状态

判断被过半服务器认可的投票对应的Leader是否是自己。如果是自己,则更新服务器状态为LEADING,否则FOLLOWING。

注意finalizeWait:

如果统计投票发现已经有过半的服务器认可了当前的投票,那么zk并不会立即更新服务器状态,而是会等待一段finalizeWait时间(200毫秒)来确定是否有新的更优的投票。

public class FastLeaderElection implements Election {
    LinkedBlockingQueue<ToSend> sendqueue;//选票发送队列,用于保存待发送的选票
    LinkedBlockingQueue<Notification> recvqueue;//选票接收队列,用于保存接收到的外部选票
    Messenger messenger;//选票管理器
    QuorumCnxManager manager;//Leader选举时的网络连接管理器
    //Determine how much time a process has to wait once it believes that it has reached the end of leader election.
    final static int finalizeWait = 200;
    AtomicLong logicalclock = new AtomicLong();//标识当前Leader的选举轮次
    long proposedLeader;//SID
    long proposedZxid;//ZXID
    long proposedEpoch;//epoch
    ...
    //触发Leader选举
    public Vote lookForLeader() throws InterruptedException {
        //用于归档的选票集合
        HashMap<Long, Vote> recvset = new HashMap<Long, Vote>();
        ...
        int notTimeout = finalizeWait;
        synchronized(this) {
            //1.自增选举轮次
            logicalclock.incrementAndGet();
            //2.初始化选票
            updateProposal(getInitId(), getInitLastLoggedZxid(), getPeerEpoch());
        }
        //3.发送初始化选票
        sendNotifications();
        //4.接收外部投票
        while ((self.getPeerState() == ServerState.LOOKING) && (!stop)) {
            //不断从recvqueue中获取其他服务器发过来的投票
            Notification n = recvqueue.poll(notTimeout, TimeUnit.MILLISECONDS);
            //如果服务器发现无法获取到任何的外部投票,那么就会确认和其他服务器建立的连接是否还有效
            if (n == null) {
                if (manager.haveDelivered()) {
                    //如果连接还有效,那么就再次发送服务器自己的内部投票
                    sendNotifications();
                } else {
                    //如果连接没有效,那么就会马上建立连接
                    manager.connectAll();
                }
                ...
            } else if (validVoter(n.sid) && validVoter(n.leader)) {
                switch (n.state) {
                    case LOOKING:
                        //5.判断选举轮次
                        //外部投票的选举轮次n.electionEpoch,大于内部投票的选举轮次logicalclock
                        if (n.electionEpoch > logicalclock.get()) {
                            //更新自己的选举轮次logicalclock
                            logicalclock.set(n.electionEpoch);
                            //清空所有已经收到的投票,因为这样可以确保recvset保存的都是同一轮次的投票
                            recvset.clear();
                            //用初始化的投票通过totalOrderPredicate()方法来进行PK以确定是否变更内部投票
                            if (totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,getInitId(), getInitLastLoggedZxid(), getPeerEpoch())) {
                                updateProposal(n.leader, n.zxid, n.peerEpoch);
                            } else {
                                updateProposal(getInitId(), getInitLastLoggedZxid(), getPeerEpoch());
                            }
                            //最后将内部投票发送出去
                            sendNotifications();
                        } else if (n.electionEpoch < logicalclock.get()) {
                            ...
                            //外部头的选举轮次n.electionEpoch小于内部投票的选举轮次logicalclock
                            //直接忽略该外部投票,不做任何处理,break掉这次while循环
                            break;
                            //6.通过totalOrderPredicate()方法进行选票PK
                        } else if (totalOrderPredicate(n.leader, n.zxid, n.peerEpoch, proposedLeader, proposedZxid, proposedEpoch)) {
                            //如果外部投票的选举轮次和内部投票的选举轮次一致
                            //那么就在判断条件里通过totalOrderPredicate()方法进行选票PK
                            //totalOrderPredicate()方法返回true,说明外部投票胜出,于是变更投票
                            //7.变更投票
                            updateProposal(n.leader, n.zxid, n.peerEpoch);
                            sendNotifications();
                        }

                        // don't care about the version if it's in LOOKING state
                        //8.选票归档
                        recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, n.peerEpoch));
                        //9.统计投票
                        if (termPredicate(recvset, new Vote(proposedLeader, proposedZxid, logicalclock.get(), proposedEpoch))) {
                            //termPredicate()方法返回true说明有过半服务器认可当前服务器的内部投票了
                            while ((n = recvqueue.poll(finalizeWait, TimeUnit.MILLISECONDS)) != null) {
                                if (totalOrderPredicate(n.leader, n.zxid, n.peerEpoch, proposedLeader, proposedZxid, proposedEpoch)){
                                    recvqueue.put(n);
                                    break;
                                }
                            }

                            //This predicate is true once we don't read any new relevant message from the reception queue
                            if (n == null) {
                                //10.更新服务器状态
                                self.setPeerState((proposedLeader == self.getId()) ? ServerState.LEADING: learningState());
                                Vote endVote = new Vote(proposedLeader, proposedZxid, logicalclock.get(), proposedEpoch);
                                leaveInstance(endVote);
                                return endVote;
                            }
                        }
                        break;
                    case OBSERVING:
                        ...
                }
            } else {
                ...
            }
        }
        return null;
    }
    ...
    //更新选票
    synchronized void updateProposal(long leader, long zxid, long epoch){
        proposedLeader = leader;//SID
        proposedZxid = zxid;//ZXID
        proposedEpoch = epoch;
    }
    
    //发送选票给所有机器:Send notifications to all peers upon a change in our vote
    private void sendNotifications() {
        for (long sid : self.getCurrentAndNextConfigVoters()) {
            QuorumVerifier qv = self.getQuorumVerifier();
            ToSend notmsg = new ToSend(ToSend.mType.notification,
                proposedLeader,
                proposedZxid,
                logicalclock.get(),
                QuorumPeer.ServerState.LOOKING,
                sid,
                proposedEpoch, qv.toString().getBytes());
            sendqueue.offer(notmsg);
        }
    }
    
    //进行选票PK
    protected boolean totalOrderPredicate(long newId, long newZxid, long newEpoch, long curId, long curZxid, long curEpoch) {
        if (self.getQuorumVerifier().getWeight(newId) == 0) {
            return false;
        }
        //We return true if one of the following three cases hold:
        //1- New epoch is higher
        //2- New epoch is the same as current epoch, but new zxid is higher
        //3- New epoch is the same as current epoch, new zxid is the same as current zxid, but server id is higher.
        return ((newEpoch > curEpoch) ||
            ((newEpoch == curEpoch) &&
            ((newZxid > curZxid) || ((newZxid == curZxid) && (newId > curId)))));
    }
    
    //统计选票
    protected boolean termPredicate(Map<Long, Vote> votes, Vote vote) {
        SyncedLearnerTracker voteSet = new SyncedLearnerTracker();
        voteSet.addQuorumVerifier(self.getQuorumVerifier());
        if (self.getLastSeenQuorumVerifier() != null 
                && self.getLastSeenQuorumVerifier().getVersion() > self.getQuorumVerifier().getVersion()) {
            voteSet.addQuorumVerifier(self.getLastSeenQuorumVerifier());
        }
        //遍历归档的投票votes,将认可内部投票vote的那些投票添加到voteSet集合
        for (Map.Entry<Long, Vote> entry : votes.entrySet()) {
            if (vote.equals(entry.getValue())) {
                voteSet.addAck(entry.getKey());
            }
        }
        //是否有过半服务器认可内部投票vote
        return voteSet.hasAllQuorums();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值