Follower处理逻辑

void followLeader() throws InterruptedException {
//...
try {
//获取leader server
QuorumServer leaderServer = findLeader();
try {
//主动向leader发起连接,TCP连接
connectToLeader(leaderServer.addr, leaderServer.hostname);
//发送follower的,包括last zxid sid,并从leader读取最新的zxid,再把last zxid发送给leader。返回leader zxid
//1. 首先follower发送自己的last zxid和sid,目的是为了leader确认epoch。FOLLOWERINFO
//2. leader返回确认后的epoch。LEADERINFO
//3. follower再次发送自己的最新zxid。ACKEPOCH
//4. 返回new epoch
long newEpochZxid = registerWithLeader(Leader.FOLLOWERINFO); //check to see if the leader zxid is lower than ours
//this should never happen but is just a safety check
//(注释有点问题,是判断的epoch而不是zxid)
long newEpoch = ZxidUtils.getEpochFromZxid(newEpochZxid);
if (newEpoch < self.getAcceptedEpoch()) {
LOG.error("Proposed leader epoch " + ZxidUtils.zxidToString(newEpochZxid)
+ " is less than our accepted epoch " + ZxidUtils.zxidToString(self.getAcceptedEpoch()));
throw new IOException("Error: Epoch of leader is lower");
}
//和leader开始同步,首先收到一条消息,判断DIFF,TRUNC,SNAP
//
syncWithLeader(newEpochZxid);
QuorumPacket qp = new QuorumPacket();
while (this.isRunning()) {
readPacket(qp);
processPacket(qp);
}
}
}

Leader处理逻辑

void lead() throws IOException, InterruptedException {
try {
self.tick = 0;
//初始化,清理旧的session和创建状态机树
zk.loadData(); leaderStateSummary = new StateSummary(self.getCurrentEpoch(), zk.getLastProcessedZxid()); // 建立监听,同时处理和follower的发现,同步阶段逻辑
cnxAcceptor = new LearnerCnxAcceptor();
cnxAcceptor.start(); newLeaderProposal.packet = new QuorumPacket(NEWLEADER, zk.getZxid(),
null, null); waitForEpochAck(self.getId(), leaderStateSummary);
self.setCurrentEpoch(epoch); try {
//等待NEWLEADER_ACK,说明已经同步完成
waitForNewLeaderAck(self.getId(), zk.getZxid(), LearnerType.PARTICIPANT);
} //开始服务,先发送UPTODATA,
startZkServer();
}

发现阶段逻辑处理

建立LearnerCnxAcceptor监听后,会启动LearnerHandler线程

public void run() {
try {
//读取FOLLOWERINFO,里面包含了follower的sid和peerLastZxid
QuorumPacket qp = new QuorumPacket();
ia.readRecord(qp, "packet");
byte learnerInfoData[] = qp.getData(); long lastAcceptedEpoch = ZxidUtils.getEpochFromZxid(qp.getZxid());
peerLastZxid = ss.getLastZxid(); /* the default to send to the follower */
//默认为全量同步
int packetToSend = Leader.SNAP;
long zxidToSend = 0;
long leaderLastZxid = 0;
/** the packets that the follower needs to get updates from **/
long updates = peerLastZxid; ReentrantReadWriteLock lock = leader.zk.getZKDatabase().getLogLock();
ReadLock rl = lock.readLock();
try {
rl.lock();
//读取缓存队列中最小的zxid,所有需要同步的最小值
final long maxCommittedLog = leader.zk.getZKDatabase().getmaxCommittedLog();
//读取缓存队列中最大的zxid,所有需要同步的最大值
final long minCommittedLog = leader.zk.getZKDatabase().getminCommittedLog();
//获取当前leader的所有日志CommittedLog
//根据上面可知,只会同步到和leader最后一个已提交日志
//不需要同步,发送一个空的DIFF
if (peerLastZxid == leader.zk.getZKDatabase().getDataTreeLastProcessedZxid()) {
// Follower is already sync with us, send empty diff
LOG.info("leader and follower are in sync, zxid=0x{}",
Long.toHexString(peerLastZxid));
packetToSend = Leader.DIFF;
zxidToSend = peerLastZxid;
} else if (proposals.size() != 0) {
//minCommittedLog <= peerLastZxid <=maxCommittedLog,进行DIFF同步
if ((maxCommittedLog >= peerLastZxid)
&& (minCommittedLog <= peerLastZxid)) {
//这里有一种特殊情况,需要先TRUNK,再DIFF同步
//leader的日志是50001,50002,60001,60002
//follower的日志是50003
//把需要同步的数据加入发送队列
} else if (peerLastZxid > maxCommittedLog) {
//大于maxCommittedLog,直接TRUCK
packetToSend = Leader.TRUNC;
} else {
LOG.warn("Unhandled proposal scenario");
}
}
leaderLastZxid = leader.startForwarding(this, updates); }
QuorumPacket newLeaderQP = new QuorumPacket(Leader.NEWLEADER,
ZxidUtils.makeZxid(newEpoch, 0), null, null); //NEWLEADER报文加入发送队列,这时还没有发送任何报文
queuedPackets.add(newLeaderQP);
bufferedOutput.flush();
//Need to set the zxidToSend to the latest zxid
if (packetToSend == Leader.SNAP) {
zxidToSend = leader.zk.getZKDatabase().getDataTreeLastProcessedZxid();
}
//发送SNAP,DIFF,或者TRUNK
oa.writeRecord(new QuorumPacket(packetToSend, zxidToSend, null, null), "packet");
bufferedOutput.flush(); /*如果不是DIFF和TRUNK,直接发送全量信息 */
if (packetToSend == Leader.SNAP) {
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();
//等待NEWLEADER_ACK,等到了NEWLEADER_ACK说明已经同步完成
leader.waitForNewLeaderAck(getSid(), qp.getZxid(), getLearnerType()); //等待大多数同步完成,leader starts up
synchronized(leader.zk){
while(!leader.zk.isRunning() && !this.isInterrupted()){
leader.zk.wait(20);
}
//发送UPTODATE报文,learn开始服务
queuedPackets.add(new QuorumPacket(Leader.UPTODATE, -1, null, null));
//正常处理流程
while (true) {
switch (qp.getType()) {
//处理propose,commit
case Leader.ACK:
if (this.learnerType == LearnerType.OBSERVER) {
if (LOG.isDebugEnabled()) {
LOG.debug("Received ACK from Observer " + this.sid);
}
}
syncLimitCheck.updateAck(qp.getZxid());
leader.processAck(this.sid, qp.getZxid(), sock.getLocalSocketAddress());
break;
//和follower保持session信息
case Leader.PING:
// Process the touches
ByteArrayInputStream bis = new ByteArrayInputStream(qp
.getData());
DataInputStream dis = new DataInputStream(bis);
while (dis.available() > 0) {
long sess = dis.readLong();
int to = dis.readInt();
leader.zk.touch(sess, to);
}
break;
case Leader.REVALIDATE:
//延长session时间
case Leader.REQUEST:
//加入处理队列
default:
}
}
}
}

总结

源码差不多看完了,整体挺复杂的,这里总结一下发现和同步的过程。

  • newEpoch:提供服务的epoch
  • acceptedEpoch:没有确认的epoch,LEADERINFO阶段
  • currentEpoch:确认的epoch,接收到UPTODATE后
  • lastLoggedZxid:最后处理的日志(包括提交,未提交)

zookeeper ZAB协议 Follower和leader源码分析的更多相关文章

  1. dubbo源码分析4-基于netty的dubbo协议的server

    dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...

  2. dubbo源码分析6-telnet方式的管理实现

    dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...

  3. dubbo源码分析1-reference bean创建

    dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...

  4. dubbo源码分析2-reference bean发起服务方法调用

    dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...

  5. dubbo源码分析3-service bean的创建与发布

    dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...

  6. dubbo源码分析5-dubbo的扩展点机制

    dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...

  7. SOFABolt 源码分析

    SOFABolt 是一个轻量级.高性能.易用的远程通信框架,基于netty4.1,由蚂蚁金服开源. 本系列博客会分析 SOFABolt 的使用姿势,设计方案及详细的源码解析.后续还会分析 SOFABo ...

  8. 【Zookeeper】源码分析之Leader选举(二)

    一.前言 前面学习了Leader选举的总体框架,接着来学习Zookeeper中默认的选举策略,FastLeaderElection. 二.FastLeaderElection源码分析 2.1 类的继承 ...

  9. 【Zookeeper】源码分析之Leader选举(二)之FastLeaderElection

    一.前言 前面学习了Leader选举的总体框架,接着来学习Zookeeper中默认的选举策略,FastLeaderElection. 二.FastLeaderElection源码分析 2.1 类的继承 ...

随机推荐

  1. (转载)Android开发——Android中常见的4种线程池(保证你能看懂并理解)

    0.前言 转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52415337 使用线程池可以给我们带来很多好处,首先通过线程池中线程的重用 ...

  2. python中for循环的三种遍历方式

    #!/usr/bin/env python# -*- coding: utf-8 -*-if __name__ == '__main__': list = ['A', 'B', 'C', 'D'] # ...

  3. Chapter3_操作符_逻辑操作符

    逻辑操作符与(&&)或(||)非(^)能够对布尔类型的数据类型进行操作,并且生成布尔值,和关系操作符的产生的数据类型是一样的.需要注意的不多,有以下几点: (1)在需要使用string ...

  4. 走进JDK(五)------AbstractList

    接下来的一段时间重点介绍java.util这个包中的内容,这个包厉害了,包含了collection与map,提供了集合.队列.映射等实现.一张图了解java中的集合类: AbstractList 一. ...

  5. Spring通过在META-INF/spring.handlers中的属性进行配置文件解析

    在Spring的入口函数refresh()之中进行的. AbstractApplicationContext ConfigurableListableBeanFactory beanFactory = ...

  6. 消息中间件——kafka

    1.1.1 什么是消息中间件 消息中间件利用高效可靠的消息传递机制进行平台无关的数据交流,并基于数据通信来进行分布式系统的集成.通过提供消息传递和消息排队模型,它可以在分布式环境下扩展进程间的通信.对 ...

  7. java基础要点总结

    无意间看到youtube上的一组java基础的视频,顺便做了笔记,整理如下: 出处: 作者:Edward Shi 视频链接:https://www.youtube.com/watch?v=IQE9jH ...

  8. .NET Core微服务之路:让我们对上一个Demo通讯进行修改,完成RPC通讯

    最近一段时间有些事情耽搁了更新,抱歉各位了. 上一篇我们简单的介绍了DotNetty通信框架,并简单的介绍了基于DotNetty实现了回路(Echo)通信过程. 我们来回忆一下上一个项目的整个流程: ...

  9. Vue学习笔记八:v-for,v-if,v-show指令

    目录 v-for指令:遍历 HTML和效果图 v-for讲解 v-if和v-show:创建,删除,显示,隐藏 HTML和效果图 v-if和v-show的原理 v-for指令:遍历 HTML和效果图 有 ...

  10. maya2014卸载/安装失败/如何彻底卸载清除干净maya2014注册表和文件的方法

    maya2014提示安装未完成,某些产品无法安装该怎样解决呢?一些朋友在win7或者win10系统下安装maya2014失败提示maya2014安装未完成,某些产品无法安装,也有时候想重新安装maya ...