ZooKeeper(四):从源码角度分析Leader选举机制

本文深入解析ZooKeeper集群中Leader选举的流程与源码,包括节点状态、选举触发条件、投票机制、选举算法FastLeaderElection的实现细节,以及如何通过延迟检查确保选举的正确性和稳定性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录说明 

  • 节点状态说明
  • Leader选举流程
  • Leader选举源码解析
  • Leader流程总述

节点状态说明
       在ZK集群中服务器有participant和observer两种类型(区别在于前者参与选举而后者不参与),Leader、Follower、Observer三种角色及以下四种状态

  • LOOKING:寻找状态,表明当前集群环境中没有Leader,该状态下节点间将发起投票选举。
  • FOLLOWING:跟随者状态。表明当前节点为Follower角色
  • LEADING:领导者状态,表明当前节点为Leader角色
  • OBSERVING:观察者状态,表明当前节点为observer角色

Leader选举流程
       
前面的博客中说过在ZK集群中,所有事务请求都由Leader进行处理,然后发起事务提议,进行数据同步。所以关于Leader的选举是Zookeeper中保证分布式数据一致性的关键所在,通常有两种情况会触发选举:集群启动、Leader宕机,虽然触发场景不一样,但选举的流程基本一致:
0)变更状态:宕机场景下,所有Follower会先将自己的状态变更为LOOKING,以便开始新一轮的选举。
1)每个Server广播自己的投票:每个Server默认都会投自己当Leader,并且将该投票广播到整个集群(包括自己)。投票信息中包含自己的myid、最大事务ID、当前的选举轮次等信息,具体的投票结构体如下

idsid(也就是myid)
zxid最大事务ID
electionEpoch当前的选举轮次
peerEpoch被投票的Leader的选举轮次,在首次投票时与上面的electionEpoch应该保持一致
state节点状态

2)接收其它Server的投票:判断投票的有效性,如对比投票轮次、是否来自LOOKING服务器、Observer不允许参与投票等。
3)投票PK:源码如下,由于epoch是zxid的高32位,所以可以理解成先比较zxid,zxid大的胜出,相等的情况下sid大的胜出。

protected boolean totalOrderPredicate(long newId, long newZxid, long newEpoch, long curId, long curZxid, long curEpoch) {
        ...
        return ((newEpoch > curEpoch) ||  ((newEpoch == curEpoch) && ((newZxid > curZxid) || ((newZxid == curZxid) && (newId > curId)))));
    }

4)更新投票并广播:更新自己的投票,然后再次广播最新投票
5)统计投票:每收到一次投票,服务器都会进行统计,判断是否已经有过半机器支持该投票。在已过半支持的情况下,仍然每200ms<最多200ms>检查一次投票接收队列,如果没有新投票到来,则认定该投票为Leader。如果有新的更大的投票到来,则将新的投票重新放入投票接收队列,然后终止本轮选举,进行下一轮的选举(选举轮次+1),在下一轮选举中再与新的投票进行比较....依次循环,直到选出Leader。如果新的投票比已过半支持投票小,则忽略该投票,继续下一次检查。这样一个延迟检查,更好的处理了启动延迟和网络不稳定这种情况。判断投票是否结束的相关源码如下  

while ((self.getPeerState() == ServerState.LOOKING) && (!stop)){
....
                    if (termPredicate(recvset,
                                new Vote(proposedLeader, proposedZxid,
                                        logicalclock.get(), proposedEpoch))) {

                            // 确认所提议的leader是否有变化
                            while((n = recvqueue.poll(finalizeWait,
                                    TimeUnit.MILLISECONDS)) != null){
                                //有变化,且新投票比所提议的Leader要大,则重新入队
                                if(totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
                                        proposedLeader, proposedZxid, proposedEpoch)){
                                    recvqueue.put(n);
                                    break;
                                }
                            }

                            if (n == null) {
                                //没有新投票到来,更新自己的状态
                                self.setPeerState((proposedLeader == self.getId()) ?
                                        ServerState.LEADING: learningState());
                                //并将endVote设置成自己的currVote
                                Vote endVote = new Vote(proposedLeader,
                                                        proposedZxid,
                                                        logicalclock.get(),
                                                        proposedEpoch);
                                leaveInstance(endVote);
                                return endVote;
                            }
                        }
                        break;  //退出循环
}

6)改变服务器状态:如上源码,在确定了Leader后,每个Server将更新自己的状态,要么为Leading或FOLLOWING
最后这里贴一张从启动到选举结束的内部通信流程图,如果对生产-消费者模式很熟练的话,对于下图及源码的理解应该很容易

Leader选举源码解析
    在列出源码之前,先简单介绍一下与zookeeper选举相关的几个核心类

  • QuorumPeerMain:Zookeeper选举入口类(主函数入口)
  • QuorumPeer:选举真正的启动类,Thread的子类,负责恢复zk数据、创建QuorumCnxManager对象、选举对象、启动Leader选举等
  • QuorumCnxManager:负责选举网络通信的I/O
  • FastLeaderElection:选举类

QuorumPeerMain
        篇幅关系源码暂略,在QuorumPeerMain中主要是读取zoo.cfg配置,封装成QuorunPeerConfig对象,根据配置创建一个ServerCnxnFactory对象管理客户端连接(默认监听2181端口),关于连接的处理,这里是典型的NIO应用,本篇暂不讨论。最终构造一个QuorumPeer对象并启动(QuorunPeer是Thread的子类)。

QuorumPeer
        在QuorumPeer的start()方法中将会产生多个流程分支并发运行,主要有加载ZK数据、创建QuorumCnxManager对象并启动监听、创建选举对象并启动读写线程、开始选举,这里我们对与选举相关后三条分支作一个详细的介绍,相关源码整理如下

public class QuorumPeer extends ZooKeeperThread implements QuorumStats.Provider {    
    private InetSocketAddress myQuorumAddr;  //当前服务器地址
    volatile private Vote currentVote;       //当前投票
    private ZKDatabase zkDb;                 //DB
    private int electionType;                //采用的选举算法,默认为3
    Election electionAlg;                    //选举算法对应的选举对象
    private long currentEpoch = -1;          //当前选举周期,每选举一次该值递增
    private long myid;                       //myid

    public enum ServerState {                //节点状态
        LOOKING, FOLLOWING, LEADING, OBSERVING;
    }
    public enum LearnerType {                //除Leader外,其余节点类型,要么是参与者,要么是观察者
        PARTICIPANT, OBSERVER;
    }     
    //重写Thread的start()方法
    @Override
    public synchronized void start() {
        loadDataBase();         //加载数据
        cnxnFactory.start();        
        startLeaderElection();  //开始选举
        super.start();          //真正启动线程,调起run()
    }    
    synchronized public void startLeaderElection() {    	        
        currentVote = new Vote(myid, getLastLoggedZxid(), getCurrentEpoch());  //设置投票
        ...       
        this.electionAlg = createElectionAlgorithm(electionType); //创建选举对象
    }
    protected Election createElectionAlgorithm(int electionAlgorithm){
        Election le=null;   
        switch (electionAlgorithm) {
        ...
        case 3:
            qcm = createCnxnManager();
            QuorumCnxManager.Listener listener = qcm.listener;
            if(listener != null){
                listener.start();       //启动监听:监听其它服务器过来的连接
                le = new FastLeaderElection(this, qcm);     //创建选举对象
            }
            break;...
        }
        return le;
    }    

    @Override
    public void run() {
        //...JMS注册相关代码,略...
        try {           
            while (running) {
                switch (getPeerState()) {
                case LOOKING:
                    LOG.info("LOOKING");                                        
                    try {
                            setBCVote(null);
                            setCurrentVote(makeLEStrategy().lookForLeader());
                    }... 
                }         
            }
        }...
    }
}

分支一:QuorumCnxManager
        从上方源码中可以看出QuorumCnxManager对象的创建、监听的启动、FastLeaderElection选举对象的创建都位于createElectionAlgorithm()方法中。从下方QuorumCnxManager源码中可以看出相出QuorumCnxManager维护的一系列成员:

  • QuorumCnxManager.Listener:负责监听选举端口(默认3888),为了避免重复建立TCP连接,只允许SID大的节点向SID小的节点建立连接;连接建立完成后,针对每个连接创建对应的RecvWoeker与SendWorker。
  • QuorumCnxManager$RecvWorker:负责从Socket中接收数据,转存到recvQueue队列中
  • QuorumCnxManager$SendWorker:负责从queueSendMap中读取数据,然后通过Socket发出
  • QuorumCnxManager.senderWorkerMap:该Map负责维护sid与其Sendworker映射关系,即Sid与其对应的Socket连接。
  • QuorumCnxManager.queueSendMap:存储sid与其发送队列的映射
  • QuorumCnxManager.recvQueue:用来存储从Socket接收到的数据。
public class QuorumCnxManager {    
    final long mySid;
    final int socketTimeout;
    final Map<Long, QuorumPeer.QuorumServer> view; //key=myid,value=服务器节点对象        
    //Mapping from Peer to Thread number
    final ConcurrentHashMap<Long, SendWorker> senderWorkerMap;    //k=myid,v=对应的Sender线程
    final ConcurrentHashMap<Long, ArrayBlockingQueue<ByteBuffer>> queueSendMap; //k==myid,v=对应的发送队列,该队列则Sender线程消费
    final ConcurrentHashMap<Long, ByteBuffer> lastMessageSent;    //k=myid,v=最后发送的消息
    //Reception queue    
    public final ArrayBlockingQueue<Message> recvQueue;    //默认capacity为100
    private final Object recvQLock = new Object();         //recvQueue锁对象

    volatile boolean shutdown = false;   
    public final Listener listener;    
    
    public class Listener extends ZooKeeperThread {
        volatile ServerSocket ss = null;
        @Override
        public void run() {
            int numRetries = 0;
            InetSocketAddress addr;
            while((!shutdown) && (numRetries < 3)){
                try {
                    ss = new ServerSocket();
                    ...
                    addr = view.get(QuorumCnxManager.this.mySid).electionAddr; //默认监听3888端口上的连接
                    LOG.info("My election bind port: " + addr.toString());                   
                    ss.bind(addr);
                    while (!shutdown) {
                        Socket client = ss.accept();        //等待连接,传统的阻塞式IO
                        setSockOpts(client);
                        LOG.info("Received connection request "+ client.getRemoteSocketAddress());
                        receiveConnection(client); //连接处理                      
                        numRetries = 0;           
                    }
                } ...
            }
        }        
       ...
    }
    public void receiveConnection(final Socket sock) {
        DataInputStream din = null;
        try {
            din = new DataInputStream(new BufferedInputStream(sock.getInputStream()));
            handleConnection(sock, din);
        } ...
    }
    private void handleConnection(Socket sock, DataInputStream din) throws IOException {
        Long sid = null;
        try {
            // Read server id
            sid = din.readLong();            
        } ...
       
        if (sid < this.mySid) {            
            //如果请求建立连接的服务器id < 当前节点myid,则断开连接,并由当前节点发起重新建立连接
            SendWorker sw = senderWorkerMap.get(sid);
            if (sw != null) {
                sw.finish();
            }           
            LOG.debug("Create new connection to server: " + sid);
            closeSocket(sock);
            connectOne(sid);           
        } else {
            //否则启动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();            
            return;
        }
    }
    //该线程主要是当前节点对应的发送队列中读取数据,然后通过socket发出   
    class SendWorker extends ZooKeeperThread {
        Long sid;
        Socket sock;
        RecvWorker recvWorker; //与SendWorker配对的RecvWorker
        volatile boolean running = true;
        DataOutputStream dout; // = new DataOutputStream(sock.getOutputStream());
        ...   
        synchronized void send(ByteBuffer b) throws IOException {
            byte[] msgBytes = new byte[b.capacity()];
            try {
                b.position(0);
                b.get(msgBytes);
            } catch (BufferUnderflowException be) {
                LOG.error("BufferUnderflowException ", be);
                return;
            }
            dout.writeInt(b.capacity());
            dout.write(b.array());
            dout.flush();
        }
        @Override
        public void run() {
            threadCnt.incrementAndGet();
            ...            
            try {
                while (running && !shutdown && sock != null) {
                    ByteBuffer b = null;
                    try {
                        ArrayBlockingQueue<ByteBuffer> bq = queueSendMap.get(sid);
                        if (bq != null) { //消费队列
                            b = pollSendQueue(bq, 1000, TimeUnit.MILLISECONDS);
                        }...
                        if(b != null){
                            lastMessageSent.put(sid, b);
                            send(b);
                        }
                    } ...
                }
            }...
        }
    }
    //该线程主要是从socket中读取数据,然后封装成Message对象,加入RecvQueue队列中
    class RecvWorker extends ZooKeeperThread {
        Long sid;
        Socket sock;
        volatile boolean running = true;
        final DataInputStream din; // = new DataInputStream(new BufferedInputStream(sock.getInputStream()))
        final SendWorker sw;   //与Recvwoeker对应的SendWorker
        ... 
        @Override
        public void run() {
            threadCnt.incrementAndGet();
            try {
                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));
                }
            }...
        }
    }

}

        流程综述:

  1. Listener启动后,监听Leader选举端口(默认3888),等待集群中其它服务器建立连接。在收到连接后会交给receiveConnection()进行处理,为了避免两台机器之间重复创建TCP连接,在receiveConnection()中会对比申请连接的服务器SID与自身SID,如果发现自身的SID大,则断开连接,然后自己主动去和远程服务器建立连接。也就是说只允许SID大的节点向SID小的节点建立连接。
  2.  连接建立成功之后,Listener线程内部会启动两个线程:SendWorker与RecvWorker,分别负责消息的发送与接收。其中SendWorker线程负责从当前节点对应的消息发送队列中读取数据,然后通过socket发出;RecvWorker线程负责从从socket流中读取数据,然后封装成Message对象,加入RecvQueue消息接收队列中。

        那么消息发送队列中的数据是何时写入的?而recvQueue消息接收队列的由谁消费?接下来看分支二
 
分支二:FastLeaderElection的创建
       同样从下方FastLeaderElection创建的源码中可以看出相出FastLeaderElection维护的一系列成员

  • FastLeaderElection.manager: 负责网络I/O的QuorumCnxManager通信对象
  • FastLeaderElection$Messenge.messager:通信对象的包装类,内部维护了一对读写线程
  • FastLeaderElection$Messenge$WorkerReceiver:选票接收器,该线程会不断的从QuorumCnxManager.RecvQueue队列中取出其它服务器发送过来的数据,并转换成一个Notification通知对象,然后转存到FastLeaderElection.recvqueue队列中。
  • FastLeaderElection$Messenge$WorkerSender:选票发送器,该线程主要负责将FastLeaderElection.sendqueue中的数据取出,放到QuorumCnxManager.queueSendMap中当前节点对应的发送队列中。
  • LinkedBlockingQueue<ToSend> sendqueue:选票发送队列
  • LinkedBlockingQueue<Notification> recvqueue:选票接收队列
public class FastLeaderElection implements Election {
    
    QuorumCnxManager manager;                    //负责网络I/O的通信对象
    Messenger messenger;                         //通信对象的包装内,内部维护了一对读写线程
    LinkedBlockingQueue<ToSend> sendqueue;       //选票发送队列
    LinkedBlockingQueue<Notification> recvqueue; //选票接收队列
    
    public FastLeaderElection(QuorumPeer self, QuorumCnxManager manager){
        this.stop = false;
        this.manager = manager;
        starter(self, manager);
    }

    private void starter(QuorumPeer self, QuorumCnxManager manager) {
        this.self = self;
        proposedLeader = -1;
        proposedZxid = -1;

        sendqueue = new LinkedBlockingQueue<ToSend>();
        recvqueue = new LinkedBlockingQueue<Notification>();
        this.messenger = new Messenger(manager);
    }
    protected class Messenger 
        WorkerSender ws;        //发送线程
        WorkerReceiver wr;      //接收线程
       
        Messenger(QuorumCnxManager manager) {
            this.ws = new WorkerSender(manager);

            Thread t = new Thread(this.ws, "WorkerSender[myid=" + self.getId() + "]");
            t.setDaemon(true);
            t.start();

            this.wr = new WorkerReceiver(manager);
            t = new Thread(this.wr,"WorkerReceiver[myid=" + self.getId() + "]");
            t.setDaemon(true);
            t.start();
        }

        //选票接收器,该线程会不断的从QuorumCnxManager.RecvQueue队列中取出其它服务器发送过来的数据,并转换成一个Notification通知对象,然后保存到FastLeaderElection.recvqueue队列中。
        class WorkerReceiver extends ZooKeeperThread {
            volatile boolean stop;
            QuorumCnxManager manager;           

            public void run() {
                Message response;
                while (!stop) {
                    // Sleeps on receive
                    try{
                        response = manager.pollRecvQueue(3000, TimeUnit.MILLISECONDS);
                        if(response == null) continue;                       
                        if(!validVoter(response.sid)){  //If it is from an observer 
                            ...
                        } else {                            
                            // 读取响应数据封装成一个Notification对象
                            Notification n = new Notification();                          
                            ...                            
                            n.leader = response.buffer.getLong();
                            n.zxid = response.buffer.getLong();
                            n.electionEpoch = response.buffer.getLong();
                            n.state = ackstate;
                            n.sid = response.sid;
                            if(!backCompatibility){
                                n.peerEpoch = response.buffer.getLong();
                            } else {                                
                                n.peerEpoch = ZxidUtils.getEpochFromZxid(n.zxid);
                            }
                            if(LOG.isInfoEnabled()){
                                printNotification(n);
                            }
                            //如果当前节点状态也处于Looking状态
                            if(self.getPeerState() == QuorumPeer.ServerState.LOOKING){
                                recvqueue.offer(n);
                                //如果发送方节点状态也为Looking,则比较两个投票
                                //如果发送方其选举轮次<当前服务器,则忽略,并向其发起自己的投票
                                if((ackstate == QuorumPeer.ServerState.LOOKING)
                                        && (n.electionEpoch < logicalclock.get())){
                                    Vote v = getVote();
                                    ToSend notmsg = new ToSend(ToSend.mType.notification,
                                            v.getId(),
                                            v.getZxid(),
                                            logicalclock.get(),
                                            self.getPeerState(),
                                            response.sid,
                                            v.getPeerEpoch());
                                    sendqueue.offer(notmsg);
                                }
                            } else {
                                //如果当前节点不是Looking,而发送方为Looking,则认为当前节点为Leader,则向其发送Leader信息。
                                Vote current = self.getCurrentVote();
                                if(ackstate == QuorumPeer.ServerState.LOOKING){
                                    ....                                    
                                    ToSend notmsg = new ToSend(
                                                ToSend.mType.notification,
                                                current.getId(),
                                                current.getZxid(),
                                                current.getElectionEpoch(),
                                                self.getPeerState(),
                                                response.sid,
                                                current.getPeerEpoch()); 
                                    sendqueue.offer(notmsg);
                                }
                            }
                        }
                    } ...
                }               
            }
        }
        
        //sender线程主要负责将FastLeaderElection.sendqueue中的数据取出,放到QuorumCnxManager.queueSendMap中当前节点对应的发送队列中。
        class WorkerSender extends ZooKeeperThread {
            volatile boolean stop;
            QuorumCnxManager manager;
            ...
            public void run() {
                while (!stop) {
                    try {
                        ToSend m = sendqueue.poll(3000, TimeUnit.MILLISECONDS);
                        if(m == null) continue;
                        process(m);
                    } ...
                }
            }
           
            void process(ToSend m) {
                ByteBuffer requestBuffer = buildMsg(m.state.ordinal(),m.leader,m.zxid, m.electionEpoch,m.peerEpoch);
                manager.toSend(m.sid, requestBuffer);
            }
        }        
       ...
    }

}

        流程综述:

  1. 创建FastLeaderElection()对象,其内部Manager对象在初始化的同时启动一个WorkerSender与WorkerReceiver线程。
  2. WorkerSender线程为选票发送器,负责将FastLeaderElection.sendqueue选票发送队列中的数据提取到QuorumCnxManager. queueSendMap中当前节点对应的消息发送队列中。所以它是生产者的同时也是消费者。
  3. WorkerReceiver为选票接收器,该线程不断的从QuorumCnxManager.recvQueue消息接收队列中取出其它服务器发送过来的数据,并转换成一个Notification通知对象,然后保存到FastLeaderElection.recvqueue选票接收队列中。

       同样,那么FastLeaderElection.sendqueue选票发送队列的数据由谁生产?其FastLeaderElection.recvqueue选票接收队列又由谁消费?接下来我们看下剩下的一条业务线,QuorumPeer线程自己的任务

分支三:QuorumPeer.run()

public class QuorumPeer extends ZooKeeperThread implements QuorumStats.Provider {    
    ...   
    @Override
    public void run() {
        //...JMS注册相关代码,略...
        try {           
            while (running) {
                switch (getPeerState()) {
                case LOOKING:   //如果Looking状态,则发起投票
                    LOG.info("LOOKING");                                        
                    try {
                            setBCVote(null);
                            setCurrentVote(makeLEStrategy().lookForLeader());
                    }... 
                }         
            }
        }...
    }
}

public class FastLeaderElection implements Election {

     public Vote lookForLeader() throws InterruptedException {
        ...
        try {
            HashMap<Long, Vote> recvset = new HashMap<Long, Vote>();
            HashMap<Long, Vote> outofelection = new HashMap<Long, Vote>();
            int notTimeout = finalizeWait;            
            synchronized(this){
                logicalclock.incrementAndGet();
                //设置投票信息,投自己
                updateProposal(getInitId(), getInitLastLoggedZxid(), getPeerEpoch());
            }

            LOG.info("New election. My id =  " + self.getId() + ", proposed zxid=0x" + Long.toHexString(proposedZxid));
            //广播自己的投票
            sendNotifications();           
            //在没有选出Leader之前,将一直循环
            while ((self.getPeerState() == ServerState.LOOKING) && (!stop)){               
                //接收投票
                Notification n = recvqueue.poll(notTimeout,TimeUnit.MILLISECONDS);                
                //如果未接收到外部投票,则检查连接,若未连接则建立连接。否则为了防止网络抖动出现消息丢失的情况,这里再次发送自己投票。
                if(n == null){
                    if(manager.haveDelivered()){
                        sendNotifications();
                    } else {
                        manager.connectAll();
                    }
                    int tmpTimeOut = notTimeout*2;
                    notTimeout = (tmpTimeOut < maxNotificationInterval?
                            tmpTimeOut : maxNotificationInterval);
                    LOG.info("Notification time out: " + notTimeout);
                }                
                else if(validVoter(n.sid) && validVoter(n.leader)) {
                    switch (n.state) {
                    case LOOKING:
                        // 接收到的节点epoch大于logicalclock,则表示已经远程节点已经开始新一轮选举了
                        if (n.electionEpoch > logicalclock.get()) {
                            logicalclock.set(n.electionEpoch); //更新本节点选举轮次                            
                            recvset.clear();                   //清空本节点上一轮次已归档的投票数据
                            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()) {                            
                            break;
                        } else if (totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
                                proposedLeader, proposedZxid, proposedEpoch)) {
                            updateProposal(n.leader, n.zxid, n.peerEpoch);
                            sendNotifications();
                        }
                        //本轮选票归档,无论是否变更投票,都将接收到的投票进行归档,以便进行选举统计
                        recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, n.peerEpoch));
                        //本轮次中,是否对于某个节点的投票数>server.size/2
                        if (termPredicate(recvset,
                                new Vote(proposedLeader, proposedZxid,
                                        logicalclock.get(), proposedEpoch))) {

                            // Verify if there is any change in the proposed leader
                            //如果超过半数投票,仍然不会马上确定其为最终的Leader,如果在200ms内没有更适合的leader的话,才算选举成功。为什么这么设计?比如同时启动1,2,3三台机器,但是当3启动完成后,可能12已经全启动并完成了选举,但其实3应该成为Leader,这里增加200ms的延迟,就是为了解决类似延迟的情况。
                            while((n = recvqueue.poll(finalizeWait,
                                    TimeUnit.MILLISECONDS)) != null){
                                if(totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
                                        proposedLeader, proposedZxid, proposedEpoch)){
                                    recvqueue.put(n);
                                    break;
                                }
                            }

                            //更新节点状态
                            if (n == null) {
                                self.setPeerState((proposedLeader == self.getId()) ?
                                        ServerState.LEADING: learningState());

                                Vote endVote = new Vote(proposedLeader,
                                                        proposedZxid,
                                                        logicalclock.get(),
                                                        proposedEpoch);
                                leaveInstance(endVote);
                                return endVote;
                            }
                        }
                        break;
                    case OBSERVING:
                        LOG.debug("Notification from observer: " + n.sid);
                        break;
                    case FOLLOWING:
                    case LEADING:
                        /*
                         * Consider all notifications from the same epoch
                         * together.
                         */
                        if(n.electionEpoch == logicalclock.get()){
                            recvset.put(n.sid, new Vote(n.leader,
                                                          n.zxid,
                                                          n.electionEpoch,
                                                          n.peerEpoch));
                           
                            if(ooePredicate(recvset, outofelection, n)) {
                                self.setPeerState((n.leader == self.getId()) ?
                                        ServerState.LEADING: learningState());

                                Vote endVote = new Vote(n.leader, 
                                        n.zxid, 
                                        n.electionEpoch, 
                                        n.peerEpoch);
                                leaveInstance(endVote);
                                return endVote;
                            }
                        }

                        /*
                         * Before joining an established ensemble, verify
                         * a majority is following the same leader.
                         */
                        outofelection.put(n.sid, new Vote(n.version,
                                                            n.leader,
                                                            n.zxid,
                                                            n.electionEpoch,
                                                            n.peerEpoch,
                                                            n.state));
           
                        if(ooePredicate(outofelection, outofelection, n)) {
                            synchronized(this){
                                logicalclock.set(n.electionEpoch);
                                self.setPeerState((n.leader == self.getId()) ?
                                        ServerState.LEADING: learningState());
                            }
                            Vote endVote = new Vote(n.leader,
                                                    n.zxid,
                                                    n.electionEpoch,
                                                    n.peerEpoch);
                            leaveInstance(endVote);
                            return endVote;
                        }
                        break;
                    default:
                        LOG.warn("Notification state unrecognized: {} (n.state), {} (n.sid)",
                                n.state, n.sid);
                        break;
                    }
                } else {
                    if (!validVoter(n.leader)) {
                        LOG.warn("Ignoring notification for non-cluster member sid {} from sid {}", n.leader, n.sid);
                    }
                    if (!validVoter(n.sid)) {
                        LOG.warn("Ignoring notification for sid {} from non-quorum member sid {}", n.leader, n.sid);
                    }
                }
            }
            return null;
        } finally {
            try {
                if(self.jmxLeaderElectionBean != null){
                    MBeanRegistry.getInstance().unregister(
                            self.jmxLeaderElectionBean);
                }
            } catch (Exception e) {
                LOG.warn("Failed to unregister with JMX", e);
            }
            self.jmxLeaderElectionBean = null;
            LOG.debug("Number of connection processing threads: {}",
                    manager.getConnectionThreadCount());
        }
    }

    //广播投票信息(包括发送给自己),注意这里会针对每个节点创建一个ToSend对象,而不是广播一个ToSend,原因在于目标主机的ID是封装在ToSend中的。
    private void sendNotifications() {        
        for (QuorumServer server : self.getVotingView().values()) {
            long sid = server.id;
            ToSend notmsg = new ToSend(ToSend.mType.notification,
                    proposedLeader,
                    proposedZxid,
                    logicalclock.get(),
                    QuorumPeer.ServerState.LOOKING,
                    sid,
                    proposedEpoch);           
            sendqueue.offer(notmsg);
        }
    }

     protected boolean totalOrderPredicate(long newId, long newZxid, long newEpoch, long curId, long curZxid, long curEpoch) {
        ...        
        //如果投票节点的选举轮次>当前节点轮次,或者投票节点的Zxid>curZxid,事务ID的情况下比较Sid,由于epoch是zxid的高32位,所以也就是说优先选举zxid最大的节点为Leader,zxid相同的情况下,选举sid最大的节点。
        return ((newEpoch > curEpoch) || 
                ((newEpoch == curEpoch) &&
                ((newZxid > curZxid) || ((newZxid == curZxid) && (newId > curId)))));
    }
   
}

        从上方源码可以看出,发起投票是在QuorumPeer.run()中,该分支的主要流程是发起投票->接收投票->投票PK->投票归档->投票统计->根据统计结果来决定是继续投票还是投票结束。下面对投票流程从源码层面做一个总述

投票流程总述

  1. 选举启动后,首先创建一个QuorumCnxManager网络通信对象,并启动其监听线程Listener:负责监听选举端口(默认3888),针对每个连接创建对应的RecvWoeker与SendWorker。
  2. 创建选举对象,并启动其WorkerReceiver选票接收器及WorkerSender选票发送器,负责接收和发送投票信息。
  3. 分支三开始投票,在QuorumPeer.run()的updateProposal()方法将投票对象设为自己,并广播自己的投票,通过sendNotifications(),将投票信息及sid封装成一个ToSend对象,注意这里针对每一个集群节点都会生成一个ToSend对象(而不是一个投票发给多个节点),放入FastLeaderElection.sendqueue选票发送队列中。
  4. 分支二中FastLeaderElection的WorkerSender选票发送器将消费该队列,将其中的ToSend对象根据sid进行拆分,生成Message对象,再放入QuorumCnxManager. queueSendMap中对应节点的对应消息发送队列中。
  5. 分支一的QuorumCnxManager.SendWorker线程将消费消息发送队列,通过socket发送出去。   
  6. 分支三广播投票后,将等待其它节点投票信息的到来,如果一定时间内未收到投票,则检测连接。连接正常的情况下继续广播一次自己的投票,否则尝试建立连接。
  7. 若接收到的投票信息,则进行投票PK。
    4.1)先比较选举轮次,若远程节点的选举轮次 > 当前节点的选举轮次,则表示远程节点已经开始新一轮选举了,自己的选举轮次是落后的,则更新自己的选举轮次,并清空选票统计列表,然后拿自己与接收到的投票PK。
    4.2)PK之后 ,更新自己投票为胜出方,再次广播
  8. 选票归档,无论是否变更投票,都将接收到的投票进行归档,以便进行选举统计
  9. 选票统计,如果归档列表某投票存在的数量大于server.size/2,则每200ms<最多200ms>检查一次投票接收队列,如果没有新投票到来,则认定该投票为Leader。如果有新的更大的投票到来,则将新的投票重新放入投票接收队列,然后终止本轮选举,进行下一轮的选举(选举轮次+1),在下一轮选举中再与新的投票进行比较....依次循环,直到选出Leader。如果新的投票比已过半支持投票小,则忽略该投票,继续下一次检查。这样一个延迟检查,更好的处理了启动延迟和网络不稳定这种情况
  10. 状态变更,选举结束后,变更自己状态,并记录Leader信息。


    



 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值