上篇文章 手把手带你撸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