Zookeeper源码解析:3、zk数据同步流程

折月煮酒 提交于 2020-01-29 20:48:06

接着上篇选举流程。当zk选举成功后,zk会进行(Leader-Follower)数据同步,数据同步成功后,整个集群才开始正常运作。这篇我们就来分析下数据同步流程。

首先我们还是回到org.apache.zookeeper.server.quorum.QuorumPeer的run方法为主入口

    @Override
    public void run() {
		// ....省略一些无关紧要的代码

        try {
            /*
             * 主循环
             */
            while (running) {
                switch (getPeerState()) {
                // 根据zk的状态,执行对应的逻辑
                case LOOKING:
                	// LOOKING,会触发选举逻辑,这个我们上篇已经说过了。
                    LOG.info("LOOKING");
					// ... 省略一些无关紧要的代码
					/*
					 * 当选举结束后。
					 * 会然后break跳出switch,然后会继续循环,根据选举后的状态
					 * 执行对应角色的逻辑
					 */ 
                    break;
                case OBSERVING:
                	// OBSERVING,Observer需要执行的逻辑。我们主要说Leader、Follower
                	// ... 省略一些无关紧要的代码
                    break;
                case FOLLOWING:
                	// FOLLOWING,Follower需要执行的逻辑
                    try {
                        LOG.info("FOLLOWING");
                        // 创建Follower对象,并且设置Follower
                        setFollower(makeFollower(logFactory));
                        /*
                         * 这里会进行一个死循环,主要做的逻辑有
                         * 1、连接Leader
                         * 2、和Leader进行数据同步
                         * 3、同步完毕后,正常接收Leader的请求,并且执行对应的逻辑,
                         * 包括Request、Propose、Commit等请求
                         */
                        follower.followLeader();
                    } catch (Exception e) {
                        LOG.warn("Unexpected exception",e);
                    } finally {
                    	// 如果集群超过半数服务宕机或者Leader宕机,那么首先设置调用shutdown(),然后设置状态为LOOKING,会重新触发选举
                        follower.shutdown();
                        setFollower(null);
                        setPeerState(ServerState.LOOKING);
                    }
                    break;
                case LEADING:
                	// LEADING,Leader需要执行的逻辑
                    LOG.info("LEADING");
                    try {
                    	// 创建Leader对象,并且设置Leader
                        setLeader(makeLeader(logFactory));
                        /*
                         * 这里会进行一个死循环,主要做的逻辑有
                         * 1、接受Follower的连接
                         * 2、进行Follower进行数据同步
                         * 3、同步完成后,正常接收请求(主要包括客户端发来的请求、
                         * 集群Follower转发的事务请求等等)                         
                         */
                        leader.lead();
                        // 如果集群超过半数服务宕机,那么首先设置Leader对象为null
                        setLeader(null);
                    } catch (Exception e) {
                        LOG.warn("Unexpected exception",e);
                    } finally {
                        if (leader != null) {
                            leader.shutdown("Forcing shutdown");
                            setLeader(null);
                        }
                        // 设置zk状态为LOOKING,需要重新触发选举
                        setPeerState(ServerState.LOOKING);
                    }
                    break;
                }
            }
        } finally {
            LOG.warn("QuorumPeer main thread exited");
            try {
                MBeanRegistry.getInstance().unregisterAll();
            } catch (Exception e) {
                LOG.warn("Failed to unregister with JMX", e);
            }
            jmxQuorumBean = null;
            jmxLocalPeerBean = null;
        }
    }

下面分别说一下Leader和Follower进行数据同步的逻辑。

Leader

当节点在选举后角色确认为 leader 后将会进入 LEADING 状态,需要执行的源码简单如下:

	setLeader(makeLeader(logFactory));
	leader.lead();

类:org.apache.zookeeper.server.quorum.QuorumPeer

    protected Leader makeLeader(FileTxnSnapLog logFactory) throws IOException {
    	/*
    	 * 创建Leader对象,并且创建了LeaderZooKeeperServer对象(该对象主要是处理客户端
    	 * 请求、或者Follower转发的事务请求,采用责任链的方式,后面单独那一篇出来讲解
    	 * 请求的执行)
    	 * 
    	 */
        return new Leader(this, new LeaderZooKeeperServer(logFactory,
                this,new ZooKeeperServer.BasicDataTreeBuilder(), this.zkDb));
    }
    
    Leader(QuorumPeer self,LeaderZooKeeperServer zk) throws IOException {
        this.self = self;
        try {
            if (self.getQuorumListenOnAllIPs()) {
            	// 创建一个ServerSocket,并且监听端口
                ss = new ServerSocket(self.getQuorumAddress().getPort());
            } else {
                ss = new ServerSocket();
            }
            ss.setReuseAddress(true);
            if (!self.getQuorumListenOnAllIPs()) {
                ss.bind(self.getQuorumAddress());
            }
        } catch (BindException e) {
            if (self.getQuorumListenOnAllIPs()) {
                LOG.error("Couldn't bind to port " + self.getQuorumAddress().getPort(), e);
            } else {
                LOG.error("Couldn't bind to " + self.getQuorumAddress(), e);
            }
            throw e;
        }
        this.zk=zk;
    }
	
	synchronized protected void setLeader(Leader newLeader){		
        leader=newLeader;
    }

类:org.apache.zookeeper.server.quorum.Leader

    void lead() throws IOException, InterruptedException {
        self.end_fle = System.currentTimeMillis();
        LOG.info("LEADING - LEADER ELECTION TOOK - " +
              (self.end_fle - self.start_fle));
        self.start_fle = 0;
        self.end_fle = 0;

        zk.registerJMX(new LeaderBean(this, zk), self.jmxLocalPeerBean);

        try {
            self.tick = 0;
            /*             
             * 从快照和事务日志中加载数据。
             */
            zk.loadData();
            
            leaderStateSummary = new StateSummary(self.getCurrentEpoch(), zk.getLastProcessedZxid());
            // 创建一个线程,接收Follower/Observer的连接
            cnxAcceptor = new LearnerCnxAcceptor();
            // 开启线程
            cnxAcceptor.start();
            
            readyToStart = true;
            
         // 等待超过一半的(Follower和Observer)连接,这里才会往下执行,返回新的朝代epoch
         // 反之,阻塞在这里
            long epoch = getEpochToPropose(self.getId(), self.getAcceptedEpoch());
            
            // 根据新的epoch,设置新的起始zxid
            zk.setZxid(ZxidUtils.makeZxid(epoch, 0));
            
            synchronized(this){
                lastProposed = zk.getZxid();
            }
            
            newLeaderProposal.packet = new QuorumPacket(NEWLEADER, zk.getZxid(),
                    null, null);


            if ((newLeaderProposal.packet.getZxid() & 0xffffffffL) != 0) {
                LOG.info("NEWLEADER proposal has Zxid of "
                        + Long.toHexString(newLeaderProposal.packet.getZxid()));
            }
            
            // 等待超过一半的(Follower和Observer)获取了新的epoch,并且返回了Leader.ACKEPOCH
            // 这里才会往下执行。 反之,阻塞在这里
            waitForEpochAck(self.getId(), leaderStateSummary);
            // 设置当前新的朝代epoch
            self.setCurrentEpoch(epoch);
            
            try {
            	// 等待超过一半的(Follower和Observer)进行数据同步成功,并且返回了Leader.ACK
            	// 这里才会往下执行。 反之,阻塞在这里
                waitForNewLeaderAck(self.getId(), zk.getZxid(), LearnerType.PARTICIPANT);
            } catch (InterruptedException e) {
                shutdown("Waiting for a quorum of followers, only synced with sids: [ "
                        + getSidSetString(newLeaderProposal.ackSet) + " ]");
                HashSet<Long> followerSet = new HashSet<Long>();
                for (LearnerHandler f : learners)
                    followerSet.add(f.getSid());
                    
                if (self.getQuorumVerifier().containsQuorum(followerSet)) {
                    LOG.warn("Enough followers present. "
                            + "Perhaps the initTicks need to be increased.");
                }
                Thread.sleep(self.tickTime);
                self.tick++;
                return;
            }
            
            // 走到这里说明集群中数据已经同步完成,可以正常运行
            // 开启zkServer,并且同时开启请求调用链接收请求执行
            startZkServer();
            
            String initialZxid = System.getProperty("zookeeper.testingonly.initialZxid");
            if (initialZxid != null) {
                long zxid = Long.parseLong(initialZxid);
                zk.setZxid((zk.getZxid() & 0xffffffff00000000L) | zxid);
            }
            
            if (!System.getProperty("zookeeper.leaderServes", "yes").equals("no")) {
                self.cnxnFactory.setZooKeeperServer(zk);
            }
            boolean tickSkip = true;
    
    		// 进行一个死循环,每次休眠self.tickTime / 2,和对所有的(Observer/Follower)发起心跳检测
            while (true) {
                Thread.sleep(self.tickTime / 2);
                if (!tickSkip) {
                    self.tick++;
                }
                HashSet<Long> syncedSet = new HashSet<Long>();

                // lock on the followers when we use it.
                syncedSet.add(self.getId());
				
				
                for (LearnerHandler f : getLearners()) {
                    // Synced set is used to check we have a supporting quorum, so only
                    // PARTICIPANT, not OBSERVER, learners should be used
                    if (f.synced() && f.getLearnerType() == LearnerType.PARTICIPANT) {						
                    	// 将Follower加入该容器	
                        syncedSet.add(f.getSid());
                    }
                    f.ping();
                }
				
			 // 判断是否有超过一半Follower在集群中
              if (!tickSkip && !self.getQuorumVerifier().containsQuorum(syncedSet)) {
                //if (!tickSkip && syncedCount < self.quorumPeers.size() / 2) {
                    // Lost quorum, shutdown
                    // 如果没有,那么调用shutdown关闭一些对象,然后return,重新选举
                    shutdown("Not sufficient followers synced, only synced with sids: [ "
                            + getSidSetString(syncedSet) + " ]");
                    // make sure the order is the same!
                    // the leader goes to looking
                    return;
              } 
              tickSkip = !tickSkip;
            }
        } finally {
            zk.unregisterJMX(this);
        }
    }

从上面我们可以了解到,主要分为以下几个步骤

  • 接收Follower/Observer的连接
  • 获取新的朝代epoch
  • 与Follower/Observer进行数据同步
  • 与所有服务器进行心跳通信

1、接收Follower/Observer的连接

类: org.apache.zookeeper.server.quorum.Leader.LearnerCnxAcceptor

        public void run() {
            try {
                while (!stop) {
                    try{
                        Socket s = ss.accept();
                        s.setSoTimeout(self.tickTime * self.initLimit);
                        s.setTcpNoDelay(nodelay);
                        LearnerHandler fh = new LearnerHandler(s, Leader.this);
                        fh.start();
                    } catch (SocketException e) {
                        if (stop) {
                            LOG.info("exception while shutting down acceptor: "
                                    + e);

                            // When Leader.shutdown() calls ss.close(),
                            // the call to accept throws an exception.
                            // We catch and set stop to true.
                            stop = true;
                        } else {
                            throw e;
                        }
                    }
                }
            } catch (Exception e) {
                LOG.warn("Exception while accepting follower", e);
            }
        }

这里采用了传统的一个Thread处理一个Socket的模式。

2、Socket的连接处理器

类:org.apache.zookeeper.server.quorum.LearnerHandler

    public void run() {
        try {
            tickOfNextAckDeadline = leader.self.tick
                    + leader.self.initLimit + leader.self.syncLimit;

            ia = BinaryInputArchive.getArchive(new BufferedInputStream(sock
                    .getInputStream()));
            bufferedOutput = new BufferedOutputStream(sock.getOutputStream());
            oa = BinaryOutputArchive.getArchive(bufferedOutput);
			// 等待读取Follower/Observer发出的请求 请求包的类型Leader.FOLLOWERINFO或者Leader.OBSERVERINFO
            QuorumPacket qp = new QuorumPacket();
            ia.readRecord(qp, "packet");
            if(qp.getType() != Leader.FOLLOWERINFO && qp.getType() != Leader.OBSERVERINFO){
                return;
            }
            // 读取请求的字节数据
            byte learnerInfoData[] = qp.getData();
            if (learnerInfoData != null) {
            	// 根据字节数据的长度判断,默认不等于8
            	if (learnerInfoData.length == 8) {
            		ByteBuffer bbsid = ByteBuffer.wrap(learnerInfoData);
            		this.sid = bbsid.getLong();
            	} else {            		
            		LearnerInfo li = new LearnerInfo();            		ByteBufferInputStream.byteBuffer2Record(ByteBuffer.wrap(learnerInfoData), li);
            		// 获取myid
            		this.sid = li.getServerid();
            		// 获取协议版本号,默认为 0x10000
            		this.version = li.getProtocolVersion();
            	}
            } else {
            	this.sid = leader.followerCounter.getAndDecrement();
            }
            // 如果请求包的类型为Leader.OBSERVERINFO,那么说明是OBSERVER
            if (qp.getType() == Leader.OBSERVERINFO) {
                  learnerType = LearnerType.OBSERVER;
            }            
            // 根据传输的zxid,获取它上一个朝代。
            long lastAcceptedEpoch = ZxidUtils.getEpochFromZxid(qp.getZxid());
            
            long peerLastZxid;
            StateSummary ss = null;
            long zxid = qp.getZxid();
            // 获取当前的新的朝代。这里需要等待超过一半的(Follower/Observer)服务器
            // 连接并且线程执行到这里,才会往下走。否则 会阻塞在这里
            long newEpoch = leader.getEpochToPropose(this.getSid(), lastAcceptedEpoch);
            // 根据协议版本号执行对应的逻辑,上面说过默认是0x10000
            if (this.getVersion() < 0x10000) {
                // we are going to have to extrapolate the epoch information
                long epoch = ZxidUtils.getEpochFromZxid(zxid);
                ss = new StateSummary(epoch, zxid);
                // fake the message
                leader.waitForEpochAck(this.getSid(), ss);
            } else {
                byte ver[] = new byte[4];
                ByteBuffer.wrap(ver).putInt(0x10000);
                QuorumPacket newEpochPacket = new QuorumPacket(Leader.LEADERINFO, ZxidUtils.makeZxid(newEpoch, 0), ver, null);
                // 向Follower/Observer发出的请求,包的类型为Leader.LEADERINFO,传输数据为版本号0x10000
                oa.writeRecord(newEpochPacket, "packet");
                bufferedOutput.flush();
                QuorumPacket ackEpochPacket = new QuorumPacket();
                // 等待读取Follower/Observer发出的请求,包的类型为Leader.ACKEPOCH
                ia.readRecord(ackEpochPacket, "packet");
                if (ackEpochPacket.getType() != Leader.ACKEPOCH) {
                    return;
				}
				/*
				 * 将该请求的数据转换为StateSummary对象,其实主要是最后事务执行的zxid。
				 * 下面会根据该zxid来判断使用哪一种同步类型
				 */
                ByteBuffer bbepoch = ByteBuffer.wrap(ackEpochPacket.getData());                
                ss = new StateSummary(bbepoch.getInt(), ackEpochPacket.getZxid());
                // 这里需要等待超过一半的(Follower/Observer)服务器
            	// 发出了Leader.ACKEPOCH类型的请求并且线程执行到这里,才会往下走。否则 会阻塞在这里
                leader.waitForEpochAck(this.getSid(), ss);
            }
            // 获取(Follower/Observer)最后事务执行的zxid
            peerLastZxid = ss.getLastZxid();
            
            // 下面是最重要的数据同步过程
            /*
             * 主要有4种同步类型
             * Leader.DIFF:差异化同步
             * Leader.SNAP:快照同步
             * Leader.TRUNC:回滚
             * Leader.TRUNC + Leader.DIFF:先回滚,在差异化同步
             */
            // 默认设置需要发送的同步类型为快照同步
            int packetToSend = Leader.SNAP;
            // 设置需要发送的zxid=0
            long zxidToSend = 0;
            long leaderLastZxid = 0;
            long updates = peerLastZxid;
                        
            // 获取zkDatabase的读写锁
            ReentrantReadWriteLock lock = leader.zk.getZKDatabase().getLogLock();
            // 获取读锁
            ReadLock rl = lock.readLock();
            try {
            	// 加读锁
                rl.lock();
                // 获取leader内存中最大的提交事务zxid        
                final long maxCommittedLog = leader.zk.getZKDatabase().getmaxCommittedLog();
                // 获取leader内存中最小的提交事务zxid 
                final long minCommittedLog = leader.zk.getZKDatabase().getminCommittedLog();
				
				// 获取内存中保存的所有的Proposal提议对象
                LinkedList<Proposal> proposals = leader.zk.getZKDatabase().getCommittedLog();
				// 如果proposals大小不为0
                if (proposals.size() != 0) {
                    // 如果最后事务执行的zxid在maxCommittedLog和minCommittedLog之间
                    // 那么可以使用Leader.DIFF:差异化同步
                    if ((maxCommittedLog >= peerLastZxid)
                            && (minCommittedLog <= peerLastZxid)) {
                        long prevProposalZxid = minCommittedLog;
                        boolean firstPacket=true;
                        // 将需要发送同步类型设置为Leader.DIFF
                        packetToSend = Leader.DIFF;
                        // 将需要发送的zxid设置为maxCommittedLog
                        zxidToSend = maxCommittedLog;

                        for (Proposal propose: proposals) {
                            // skip the proposals the peer already has
                            if (propose.packet.getZxid() <= peerLastZxid) {
                            	// 跳过zxid小于等于peerLastZxid的Proposal提议
                                prevProposalZxid = propose.packet.getZxid();
                                continue;
                            } else {
                                if (firstPacket) {
                                /**
                                 * 此时说明有部分 proposals 提案在 leader 节点上不存在,则需告诉 follower 丢弃这部分 proposals
                                 * 也就是告诉 follower 先执行回滚 TRUNC ,需要回滚到 prevProposalZxid 处,也就是 follower 需要丢弃 prevProposalZxid ~ peerLastZxid 范围内的数据
                                 * 剩余的 proposals 则通过 DIFF 进行同步
                                 */
                                    firstPacket = false;
                                    if (prevProposalZxid < peerLastZxid) {
                                        packetToSend = Leader.TRUNC;                                        
                                        zxidToSend = prevProposalZxid;
                                        updates = zxidToSend;
                                    }
                                }
                                // 将需要DIFF同步的propose加入发送队列,等待传输。
                                queuePacket(propose.packet);
                                // 创建该propose的COMMIT包,并且加入发送队列,等待传输
                                QuorumPacket qcommit = new QuorumPacket(Leader.COMMIT, propose.packet.getZxid(),
                                        null, null);
                                queuePacket(qcommit);
                            }
                        }
                    // 如果最后的zxid大于maxCommittedLog,那么说明需要回滚Leader.TRUNC
                    } else if (peerLastZxid > maxCommittedLog) {
                        packetToSend = Leader.TRUNC;
                        zxidToSend = maxCommittedLog;
                        updates = zxidToSend;
                    // 最后说明小于minCommittedLog,则需要使用Leader.SNAP快照同步
                    } else {                    	
                        LOG.warn("Unhandled proposal scenario");
                    }
                } else if (peerLastZxid == leader.zk.getZKDatabase().getDataTreeLastProcessedZxid()) {
					// 如果proposals大小为0,而且和leader的最大zxid一样,说明不需要同步
					// 这里proposals大小为0,可能leader刚保存了快照		
                    // 将需要发送同步类型设置为Leader.DIFF
                    packetToSend = Leader.DIFF;
                    // 将需要发送zxid设置为peerLastZxid
                    zxidToSend = peerLastZxid;
                } else {
                // 如果proposals大小为0,而且zxid和leader的最大zxid不一样,使用快照同步
                    // just let the state transfer happen
                    LOG.debug("proposals is empty");
                }               
                leaderLastZxid = leader.startForwarding(this, updates);

            } finally {
                rl.unlock();
            }
			 // 创建一个Leader.NEWLEADER类型的请求包,将请求包加入待发送的队列
             QuorumPacket newLeaderQP = new QuorumPacket(Leader.NEWLEADER,
                    ZxidUtils.makeZxid(newEpoch, 0), null, null);
             if (getVersion() < 0x10000) {             	
                oa.writeRecord(newLeaderQP, "packet");
            } else {
                queuedPackets.add(newLeaderQP);
            }
            bufferedOutput.flush();
            
        // 如果为快照同步,那么将需要发送的同步zxid设置为leader的zkdatabase的最大zxid
            if (packetToSend == Leader.SNAP) {
                zxidToSend = leader.zk.getZKDatabase().getDataTreeLastProcessedZxid();
            }
            // 向(Follower/Observer)发送一个请求包,包的类型为同步方式
            oa.writeRecord(new QuorumPacket(packetToSend, zxidToSend, null, null), "packet");
            bufferedOutput.flush();
            
            // 如果为快照同步,那么需要将整个zkDasbase序列化传输
            if (packetToSend == Leader.SNAP) {
                // Dump data to peer
                leader.zk.getZKDatabase().serializeSnapshot(oa);
                oa.writeString("BenWasHere", "signature");
            }
            bufferedOutput.flush();
            // 创建一个线程来发送请求包。。。。
            new Thread() {
                public void run() {
                    Thread.currentThread().setName(
                            "Sender-" + sock.getRemoteSocketAddress());
                    try {
                        sendPackets();
                    } catch (InterruptedException e) {
                        LOG.warn("Unexpected interruption",e);
                    }
                }
            }.start();
            
            qp = new QuorumPacket();
            // 等待Follower/Observer发送的Leader.ACK类型请求包
            ia.readRecord(qp, "packet");
            if(qp.getType() != Leader.ACK){
                return;
            }

			// 阻塞等待超过一半的Follower/Observer同步完成
            leader.waitForNewLeaderAck(getSid(), qp.getZxid(), getLearnerType());

            syncLimitCheck.start();

            // 设置socket超时时间
            sock.setSoTimeout(leader.self.tickTime * leader.self.syncLimit);
             // 等待直到leader的zkServer启动完毕
            synchronized(leader.zk){
                while(!leader.zk.isRunning() && !this.isInterrupted()){
                    leader.zk.wait(20);
                }
            }

            // leader 向 follower/Observer 发送 UPTODATE,告知其可对外提供服务
            queuedPackets.add(new QuorumPacket(Leader.UPTODATE, -1, null, null));

			
            while (true) {
				// 略。
            }
        } catch (IOException e) {
           // 略。
        }
    }

Follower

当节点在选举后角色确认为 follower后将会进入 FOLLOWING 状态,需要执行的源码简单如下:

	  setFollower(makeFollower(logFactory));
	  follower.followLeader();

类:org.apache.zookeeper.server.quorum.QuorumPeer

	protected Follower makeFollower(FileTxnSnapLog logFactory) throws IOException {
        // 创建Follower和FollowerZooKeeperServer对象
        return new Follower(this, new FollowerZooKeeperServer(logFactory, 
                this,new ZooKeeperServer.BasicDataTreeBuilder(), this.zkDb));
    }
    synchronized protected void setFollower(Follower newFollower){
        follower=newFollower;
    }
	void followLeader() throws InterruptedException {
		// 省略一些代码
        try {
        	// 首先找到Leader的地址
            InetSocketAddress addr = findLeader();            
            try {
            	// 跟Leader建立连接
                connectToLeader(addr);
                // 	从Leader获取新的朝代epoch
                long newEpochZxid = registerWithLeader(Leader.FOLLOWERINFO);                
                long newEpoch = ZxidUtils.getEpochFromZxid(newEpochZxid);
                if (newEpoch < self.getAcceptedEpoch()) 
                    throw new IOException("Error: Epoch of leader is lower");
                }
                // 和Leader进行数据同步
                syncWithLeader(newEpochZxid);                
                QuorumPacket qp = new QuorumPacket();
                // 进行一个死循环,与Leader进行交互
                while (self.isRunning()) {
                    readPacket(qp);
                    processPacket(qp);
                }
            } catch (IOException e) {
				// 省略一些代码
            }
        } finally {
            zk.unregisterJMX((Learner)this);
        }
    }

从上面我们可以了解到,主要由以下几点

  • 与Leader建立连接
  • 从Leader获取新的朝代epoch
  • 与Leader进行数据同步
  • 进行一个死循环,与Leader进行交互

1、与Leader建立连接

    protected InetSocketAddress findLeader() {
    	// 根据最后胜出的投票,找到Leader的地址
        InetSocketAddress addr = null;
        // Find the leader by id
        Vote current = self.getCurrentVote();
        for (QuorumServer s : self.getView().values()) {
            if (s.id == current.getId()) {
                addr = s.addr;
                break;
            }
        }
        if (addr == null) {
            LOG.warn("Couldn't find the leader with id = "
                    + current.getId());
        }
        return addr;
    }
    protected void connectToLeader(InetSocketAddress addr) 
    throws IOException, ConnectException, InterruptedException {
        sock = new Socket();        
        sock.setSoTimeout(self.tickTime * self.initLimit);
        for (int tries = 0; tries < 5; tries++) {
            try {
            	// 连接Leader,如果连接成功break。如果连接失败,那么继续循环重试5次
                sock.connect(addr, self.tickTime * self.syncLimit);
                sock.setTcpNoDelay(nodelay);
                break;
            } catch (IOException e) {
                if (tries == 4) {
                    LOG.error("Unexpected exception",e);
                    throw e;
                } else {
                    LOG.warn("Unexpected exception, tries="+tries+
                            ", connecting to " + addr,e);
                    sock = new Socket();
                    sock.setSoTimeout(self.tickTime * self.initLimit);
                }
            }
            Thread.sleep(1000);
        }
        leaderIs = BinaryInputArchive.getArchive(new BufferedInputStream(
                sock.getInputStream()));
        bufferedOutput = new BufferedOutputStream(sock.getOutputStream());
        leaderOs = BinaryOutputArchive.getArchive(bufferedOutput);
    }
    protected long registerWithLeader(int pktType) throws IOException{
        /*
         * Send follower info, including last zxid and sid
         */
        // 获取最后执行的zxid
    	long lastLoggedZxid = self.getLastLoggedZxid();
    	// 设置请求包 请求类型为Leader.FOLLOWERINFO(因为我这里说的Follower,所以是Leader.FOLLOWERINFO)
        QuorumPacket qp = new QuorumPacket();                
        qp.setType(pktType);
        qp.setZxid(ZxidUtils.makeZxid(self.getAcceptedEpoch(), 0));
		
		// 请求数据为LearnerInfo,包括myid和协议版本号0x10000
        LearnerInfo li = new LearnerInfo(self.getId(), 0x10000);
        ByteArrayOutputStream bsid = new ByteArrayOutputStream();
        BinaryOutputArchive boa = BinaryOutputArchive.getArchive(bsid);
        boa.writeRecord(li, "LearnerInfo");
        qp.setData(bsid.toByteArray());
        // 向leader传输该请求
        writePacket(qp, true);
        // 等待读取leader传输的请求包,该请求包的类型为Leader.LEADERINFO
        readPacket(qp);     
        // 从请求包的zxid解析出新的朝代epoch   
        final long newEpoch = ZxidUtils.getEpochFromZxid(qp.getZxid());
        // 设置新的朝代epoch   
		if (qp.getType() == Leader.LEADERINFO) {
        	leaderProtocolVersion = ByteBuffer.wrap(qp.getData()).getInt();
        	byte epochBytes[] = new byte[4];
        	final ByteBuffer wrappedEpochBytes = ByteBuffer.wrap(epochBytes);
        	if (newEpoch > self.getAcceptedEpoch()) {
        		wrappedEpochBytes.putInt((int)self.getCurrentEpoch());
        		self.setAcceptedEpoch(newEpoch);
        	} else if (newEpoch == self.getAcceptedEpoch()) {
                wrappedEpochBytes.putInt(-1);
        	} else {
        		throw new IOException("Leaders epoch, " + newEpoch + " is less than accepted epoch, " + self.getAcceptedEpoch());
        	}
        	// 创建请求包,请求类型为Leader.ACKEPOCH,并且传输处理的最大zxid,准备进行数据同步
        	QuorumPacket ackNewEpoch = new QuorumPacket(Leader.ACKEPOCH, lastLoggedZxid, epochBytes, null);
        	// 向leader发送请求
        	writePacket(ackNewEpoch, true);
            return ZxidUtils.makeZxid(newEpoch, 0);
        } else {
        	// 省略一些代码
        }
    } 
    protected void syncWithLeader(long newLeaderZxid) throws IOException, InterruptedException{
        QuorumPacket ack = new QuorumPacket(Leader.ACK, 0, null, null);
        QuorumPacket qp = new QuorumPacket();
        long newEpoch = ZxidUtils.getEpochFromZxid(newLeaderZxid);
        /*
         * 读取从leader传输的数据,数据包的类型为同步类型。
         * Leader.DIFF:差异化同步
         * Leader.SNAP:快照同步
         * Leader.TRUNC:回滚
         */
        readPacket(qp);   
        LinkedList<Long> packetsCommitted = new LinkedList<Long>();
        LinkedList<PacketInFlight> packetsNotCommitted = new LinkedList<PacketInFlight>();
        synchronized (zk) {
        	// 根据不同的同步类型,进行相应的擦做做
            if (qp.getType() == Leader.DIFF) {
				// 差异化同步,在下面进行处理             
            }
            else if (qp.getType() == Leader.SNAP) {
            	// 快照同步
            	// 首先清除自身的zkDatabase,也就是内存中的数据
                zk.getZKDatabase().clear();
                // 从leader传输过来的输入流中序列化zkDatabase
                zk.getZKDatabase().deserializeSnapshot(leaderIs);
                // 同步处理完后,最后读取签名
                String signature = leaderIs.readString("signature");
                // 校验签名是否正确
                if (!signature.equals("BenWasHere")) {
                    LOG.error("Missing signature. Got " + signature);
                    throw new IOException("Missing signature");                   
                }
            } else if (qp.getType() == Leader.TRUNC) {
            	// 回滚同步
            	// 根据传输的zxid进行回滚
                boolean truncated=zk.getZKDatabase().truncateLog(qp.getZxid());
                if (!truncated) {
                    LOG.error("Not able to truncate the log "
                            + Long.toHexString(qp.getZxid()));
                    System.exit(13);
                }
            }
            else {
                LOG.error("Got unexpected packet from leader "
                        + qp.getType() + " exiting ... " );
                System.exit(13);

            }            
            zk.getZKDatabase().setlastProcessedZxid(qp.getZxid());
            // 创建session管理器
            zk.createSessionTracker();                       
            long lastQueued = 0;
            // 判断快照是否生成成功标识
            boolean snapshotTaken = false;
            outerLoop:
            while (self.isRunning()) {	            	
            	// 循环从Leader读取请求,知道接收到Leader.UPTODATE类型的请求包为止
                readPacket(qp);
                switch(qp.getType()) {
                case Leader.PROPOSAL:
                	// 读取到Leader.PROPOSAL,说明是DIFF差异化同步,需要接收新的提案
                    PacketInFlight pif = new PacketInFlight();
                    pif.hdr = new TxnHeader();
                    pif.rec = SerializeUtils.deserializeTxn(qp.getData(), pif.hdr);
                    if (pif.hdr.getZxid() != lastQueued + 1) {
                    LOG.warn("Got zxid 0x"
                            + Long.toHexString(pif.hdr.getZxid())
                            + " expected 0x"
                            + Long.toHexString(lastQueued + 1));
                    }
                    lastQueued = pif.hdr.getZxid();
                    // 加入等待Commit队列
                    packetsNotCommitted.add(pif);
                    break;
                case Leader.COMMIT:
                	// 读取到Leader.PROPOSAL,说明提案需要提交
                    if (!snapshotTaken) {
                    	// 从等待Commit队列获取提案
                        pif = packetsNotCommitted.peekFirst();
                        if (pif.hdr.getZxid() != qp.getZxid()) {
                            LOG.warn("Committing " + qp.getZxid() + ", but next proposal is " + pif.hdr.getZxid());
                        } else {
                        	// 执行事务提案
                            zk.processTxn(pif.hdr, pif.rec);
                            packetsNotCommitted.remove();
                        }
                    } else {
                        packetsCommitted.add(qp.getZxid());
                    }
                    break;
                case Leader.INFORM:
                    PacketInFlight packet = new PacketInFlight();
                    packet.hdr = new TxnHeader();
                    packet.rec = SerializeUtils.deserializeTxn(qp.getData(), packet.hdr);
                    // Log warning message if txn comes out-of-order
                    if (packet.hdr.getZxid() != lastQueued + 1) {
                        LOG.warn("Got zxid 0x"
                                + Long.toHexString(packet.hdr.getZxid())
                                + " expected 0x"
                                + Long.toHexString(lastQueued + 1));
                    }
                    lastQueued = packet.hdr.getZxid();
                    if (!snapshotTaken) {
                        // Apply to db directly if we haven't taken the snapshot
                        zk.processTxn(packet.hdr, packet.rec);
                    } else {
                        packetsNotCommitted.add(packet);
                        packetsCommitted.add(qp.getZxid());
                    }
                    break;
                case Leader.UPTODATE:
                	// 读取到Leader.UPTODATE,说明数据Leader已经可对外提供服务
                    if (!snapshotTaken) { // true for the pre v1.0 case
                        zk.takeSnapshot();
                        self.setCurrentEpoch(newEpoch);
                    }
                    // 设置zkServer
                    self.cnxnFactory.setZooKeeperServer(zk); 
                    // 跳出循环               
                    break outerLoop;
                case Leader.NEWLEADER:
                	// 读取到Leader.NEWLEADER,说明Leader已经同步完毕
                    File updating = new File(self.getTxnFactory().getSnapDir(),
                                        QuorumPeer.UPDATING_EPOCH_FILENAME);
                    if (!updating.exists() && !updating.createNewFile()) {
                        throw new IOException("Failed to create " +
                                              updating.toString());
                    }
                    // 生成新的快照
                    zk.takeSnapshot();
                    // 设置新的朝代epoch
                    self.setCurrentEpoch(newEpoch);
                    if (!updating.delete()) {
                        throw new IOException("Failed to delete " +
                                              updating.toString());
                    }
                    // 表示快照生成成功
                    snapshotTaken = true;
                    // 向Leader传输一个请求包,包的类型为Leader.ACK,表示数据已同步完毕
                    writePacket(new QuorumPacket(Leader.ACK, newLeaderZxid, null, null), true);
                    break;
                }
            }
        }
        ack.setZxid(ZxidUtils.makeZxid(newEpoch, 0));
        // 向Leader传输一个请求包,包的类型为Leader.ACK。表示Follower可以
        writePacket(ack, true);
        sock.setSoTimeout(self.tickTime * self.syncLimit);
        // 启动zkServer
        zk.startup();
        self.updateElectionVote(newEpoch);
        
		// 忽略一些代码
    }

总结一下zk同步,主要分以下4种方式:

DIFF(差异化同步):

  • follower 的 peerLastZxid 等于 leader 的 peerLastZxid

    此时说明 follower 与 leader 数据一致,采用 DIFF 方式同步,也即是无需同步

  • follower 的 peerLastZxid 介于 maxCommittedLog, minCommittedLog 两者之间

    此时说明 follower 与 leader 数据存在差异,需对差异的部分进行同步;首先 leader 会向 follower 发送 DIFF 报文告知其同步方式,随后会发送差异的提案及提案提交报文

TRUNC+DIFF(先回滚再差异化同步)

在上文 DIFF 差异化同步时会存在一个特殊场景就是 虽然 follower 的 peerLastZxid 介于,maxCommittedLog minCommittedLog 两者之间,但是 follower 的 peerLastZxid 在 leader 节点中不存在; 此时 leader 需告知 follower 先回滚到 peerLastZxid 的前一个 zxid, 回滚后再进行差异化同步。

示例: 假设集群中三台节点 A, B, C 某一时刻 A 为 Leader 选举周期为 5, zxid 包括: (0x500000001, 0x500000002, 0x500000003); 假设某一时刻 leader A 节点在处理完事务为 0x500000004 的请求进行广播时 leader A 节点服务器宕机导致 0x500000004 该事务没有被同步出去;在集群进行下一轮选举之后 B 节点成为新的 leader,选举周期为 6 对外提供服务处理了新的事务请求包括 0x600000001, 0x600000002;


集群列表 ZXID 列表
A 0x500000001, 0x500000002, 0x500000003, 0x500000004
B 0x500000001, 0x500000002, 0x500000003,0x600000001, 0x600000002
C 0x500000001, 0x500000002, 0x500000003,0x600000001, 0x600000002

此时节点 A 在重启加入集群后,在与 leader B 节点进行数据同步时会发现事务 0x500000004 在 leader 节点中并不存在,此时 leader 告知 A 需先回滚事务到 0x500000003,在差异同步事务 0x600000001,0x600000002;

TRUNC( 回滚同步)

  • follower 的 peerLastZxid 大于 leader 的 maxCommittedLog,则告知 follower 回滚至 maxCommittedLog; 该场景可以认为是 TRUNC+DIFF 的简化模式

SNAP(全量同步)

  • follower 的 peerLastZxid 小于 leader 的 minCommittedLog
  • leader 节点上不存在提案缓存队列时

该模式下 leader 首先会向 follower 发送 SNAP 报文,随后从内存数据库中获取全量数据序列化传输给 follower, follower 在接收全量数据后会进行反序列化加载到内存数据库中。

上面的同步流程可以简化为如下流程图:
在这里插入图片描述

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!