手把手带你撸zookeeper源码-zookeeper故障重启时如何恢复数据(二)

上篇文章 手把手带你撸zookeeper源码-zookeeper故障重启时如何恢复数据(一) 分析了在zookeeper启动的时候从本地日志文件中如何恢复数据,先获取快照文件中的数据然后反序列化到内存中,接着再对日志文件进行增量逐条回放,本篇文章详细分析一下在启动的时候如何和leader建立连接,然后从leader中如何同步数据到本地内存的

 

如果当前存在一个zookeeper集群,现在不管是对原有的zookeeper进行重启,还是新加入一台zookeeper节点,此时zookeeper要么是observer角色、要么是follower角色。当然这是不考虑极端情况,比如刚好碰到zookeeper的leader挂掉,碰到leader选举等情况。因为启动一个zookeeper节点加入到集群中,那么集群中肯定有leader的,我们主要分析一个follower加入到集群中,现在直接定位到Follower.followLeader()方法,至于怎么走到这的可以去看之前zookeer源码剖析系列文章

// 查找leader所在服务器
            QuorumServer leaderServer = findLeader();            
            try {
                //向leader发起连接
                connectToLeader(leaderServer.addr, leaderServer.hostname);
                //向leader进行注册, 经过三次握手
                long newEpochZxid = registerWithLeader(Leader.FOLLOWERINFO);

                // 校验一下leader的zxid是否小于我们的, 这种情肯定不会发生,只是做个安全检查
                long newEpoch = ZxidUtils.getEpochFromZxid(newEpochZxid);
                if (newEpoch < self.getAcceptedEpoch()) {
                    throw new IOException("Error: Epoch of leader is lower");
                }
                // 直接跟leader进行数据同步
                // 集群启动的磁盘数据恢复、leader -> follower数据同步、leader重新选举之后的数据同步
                syncWithLeader(newEpochZxid);

只复制其中比较关键的代码,也是这次要分析的代码

第一行代码findLeader()就是查找当前leader是哪一台服务器,QuorumServer对象里面封装了选举的地址、leader地址

follower如何和leader建立连接的

第二行代码

connectToLeader(leaderServer.addr, leaderServer.hostname);

向leader发起连接,即要建立一个socket连接

 

然后我们转向Leader.lead()方法看看leader如何创建socket.accept()来等待客户端来连接的,把之前的文章前后关联起来

cnxAcceptor = new LearnerCnxAcceptor();
cnxAcceptor.start();

找到如上代码,创建了LearnerCnxAcceptor对象,它是一个线程,并且启动了这个线程

LearnerCnxAcceptor中的run方法

Socket s = ss.accept();
// start with the initLimit, once the ack is processed
// in LearnerHandler switch to the syncLimit
s.setSoTimeout(self.tickTime * self.initLimit);
s.setTcpNoDelay(nodelay);

也就是说,leader是单独起了一个线程,然后等待客户端连接,没有客户端时此线程会阻塞在accept()方法这,当有客户端进行连接时会接着向下执行代码

Leader如何接收follower发送过来的请求的

//socket 输入流,用来读取客户端发送过来的数据
BufferedInputStream is = new BufferedInputStream(s.getInputStream());
LearnerHandler fh = new LearnerHandler(s, is, Leader.this);
fh.start();

在leader接收到客户端发送过来的连接时,会执行上面的代码,创建一个LearnerHandler对象,封装了socket对象和socket输入流

LearnerHandler也是一个线程,这里提一点,其实在看很多开源源码的时候都会碰到这种xx.start()方法,其实十有八九都是启动一个线程来执行后续的代码。也有一些是自己封装的start方法,然后去启动多个组件的。此时应该执行去看这个对象的run方法中。即leader在接收到一个客户端发送过来的请求之后,会单独启动一个线程来处理此客户端发送过来的请求,接下来如果有客户端发送过来数据的时候我们直接去LearnerHandler中的run方法中看怎么处理数据的即可

follower如何发起注册请求

 //向leader进行注册, 经过三次握手
long newEpochZxid = registerWithLeader(Leader.FOLLOWERINFO);

在follower和leader建立连接之后,客户端接下里就是向客户端发送注册请求,来获取leader的epoch,我们详细看一下代码

protected long registerWithLeader(int pktType) throws IOException{
    	long lastLoggedZxid = self.getLastLoggedZxid();
        QuorumPacket qp = new QuorumPacket();                
        qp.setType(pktType);
        qp.setZxid(ZxidUtils.makeZxid(self.getAcceptedEpoch(), 0));
        
       
        LearnerInfo li = new LearnerInfo(self.getId(), 0x10000);
        ByteArrayOutputStream bsid = new ByteArrayOutputStream();
        BinaryOutputArchive boa = BinaryOutputArchive.getArchive(bsid);
        boa.writeRecord(li, "LearnerInfo");
        qp.setData(bsid.toByteArray());
        //发送sid和协议版本号给leader,
        writePacket(qp, true);

        // 读取到leader发送回来的leader中的zxid和version、type
        readPacket(qp);        
        final long newEpoch = ZxidUtils.getEpochFromZxid(qp.getZxid());
        // 第一次接收到leader发送过来的数据,leader发送过来的协议版本号
		if (qp.getType() == Leader.LEADERINFO) {
        	// we are connected to a 1.0 server so accept the new epoch and read the next packet
        	leaderProtocolVersion = ByteBuffer.wrap(qp.getData()).getInt();
        	byte epochBytes[] = new byte[4];
        	final ByteBuffer wrappedEpochBytes = ByteBuffer.wrap(epochBytes);
        	if (newEpoch > self.getAcceptedEpoch()) {
        	    // 如果leader中的epoch比我们本地的大, 则和leader保持同步
        		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());
        	}
        	// type = ackepoch  zxid = follower的lastZxid, 和 currentEpoch
        	QuorumPacket ackNewEpoch = new QuorumPacket(Leader.ACKEPOCH, lastLoggedZxid, epochBytes, null);
        	// 发送epoch ack回去
        	writePacket(ackNewEpoch, true);
            return ZxidUtils.makeZxid(newEpoch, 0);
        } else {
        	if (newEpoch > self.getAcceptedEpoch()) {
        		self.setAcceptedEpoch(newEpoch);
        	}
            if (qp.getType() != Leader.NEWLEADER) {
                throw new IOException("First packet should have been NEWLEADER");
            }
            return qp.getZxid();
        }
    } 

这是一大段代码,我们一段一段的分析,以一个请求发送数据为一段

long lastLoggedZxid = self.getLastLoggedZxid();
        QuorumPacket qp = new QuorumPacket();                
        qp.setType(pktType);
        qp.setZxid(ZxidUtils.makeZxid(self.getAcceptedEpoch(), 0));
        
        /*
         * Add sid to payload
         * 包装当前的sid为learnerInfo对象
         */
        LearnerInfo li = new LearnerInfo(self.getId(), 0x10000);
        ByteArrayOutputStream bsid = new ByteArrayOutputStream();
        BinaryOutputArchive boa = BinaryOutputArchive.getArchive(bsid);
        boa.writeRecord(li, "LearnerInfo");
        qp.setData(bsid.toByteArray());
        //发送sid和协议版本号给leader,
        writePacket(qp, true);

封装一个QuorumPacket包,里面包含了type = Leader.FOLLOWERINFO = 11,它其实就是代表了当前的请求类型,leader通过解析这个type来判断当前请求如何进行处理的,zxid是从数据日志文件中读取leader的epoch版本,如果没有读取到,则设置为0,另外还封装了一个LearnerInfo对象,里面有当前zookeeper的myid,和一个协议版本号,然后使用jute对数据进行序列化,通过writePacket发送到leader去

我们看看leader如何进行这部分数据如何处理的

// 保存处理当前连接进来的follower的LearnerHandler
            leader.addLearnerHandler(this);
            tickOfNextAckDeadline = leader.self.tick.get()
                    + leader.self.initLimit + leader.self.syncLimit;

            ia = BinaryInputArchive.getArchive(bufferedInput);
            bufferedOutput = new BufferedOutputStream(sock.getOutputStream());
            oa = BinaryOutputArchive.getArchive(bufferedOutput);

            QuorumPacket qp = new QuorumPacket();
            ia.readRecord(qp, "packet");//读取follower发送过来的注册数据包
            if(qp.getType() != Leader.FOLLOWERINFO && qp.getType() != Leader.OBSERVERINFO){
            	LOG.error("First packet " + qp.toString()
                        + " is not FOLLOWERINFO or OBSERVERINFO!");
                return;
            }
            // follower发送注册请求的时候data肯定不为空,包含了follower的zxid(8) + 协议号(4) = 14个字节
            byte learnerInfoData[] = qp.getData();
            if (learnerInfoData != null) {
            	if (learnerInfoData.length == 8) {
            		ByteBuffer bbsid = ByteBuffer.wrap(learnerInfoData);
            		this.sid = bbsid.getLong();
            	} else {//肯定走到这个分支
            		LearnerInfo li = new LearnerInfo();
            		ByteBufferInputStream.byteBuffer2Record(ByteBuffer.wrap(learnerInfoData), li);
            		this.sid = li.getServerid();
            		this.version = li.getProtocolVersion();
            	}
            } else {
            	this.sid = leader.followerCounter.getAndDecrement();
            }
lea
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值