这篇博客是从源码的角度了解Zookeeper 从接收客户端请求开始,到返回数据为止,有很多涉及到的对象创建因为在前几篇文章已经说明过了,这里就不再重复的说明了,如果不是很明白的的,可以先看前几篇博文了解一下,先了解一下整体架构,对整个架构图有清晰的认识后,再带着线程流转模型去看源码感觉效率会有比较大的提升



另外因为zk 中大量的使用了线程和队列,代码相对来说比较绕,所以我这里画了一张整体的线程队列的流程图,便于理清思路,建议看博文或者源码的时候基于这张图看,便于理清线程队列之间的关系

QuorumPeer启动的时候,ZK 会启动一个socket 来接收客户端的信息,这个socket 会有两种,ZK 默认的是NIO, 但是可以在启动的时候配置参数-Dzookeeper.serverCnxnFactory=org.apache.zookeeper.server.NettyServerCnxnFactory 的方式指定使用netty

Leader节点

QuorumPeer#start

QuorumPeer#startServerCnxnFactory

因为Client 源码分析我们使用的是NIO 的方式,这里我们就用Netty 进行解析,这里看一下构造函数和start 方法,见start

NettyServerCnxnFactory

start

这个代码是比较清晰的了,NettyServerCnxnFactory的构造函数中很经典的使用了netty, 并且在pipline 中放入了一个名称为servercnxnfactory的Handler, 这个Handler 是CnxnChannelHandler对象,在接收客户端的数据都会经过这个handler,然后在start 方法中启动netty

CnxnChannelHandler是数据接收的入口,我们重点分析一下这个,详情见CnxnChannelHandler

CnxnChannelHandler

CnxnChannelHandler

CnxnChannelHandler 中包含了多个方式,包括读,写,心跳事件,连接事件,断开连接等,这里我们暂时只分析

读事件channlRead,见channelRead

写事件write

CnxnChannelHandler#channelRead

channelRead

netty中处理每一个socket 数据都会走pipline中Handler的channelRead方法,在channelRead通过processMessage进行处理,见processMessage

NettyServerCnxn#processMessage

processMessage

NettyServerCnxn#receiveMessage

因为只关注主流程,所以折叠了一些代码,在分析客户端的源码的时候,了解到zk 客户端是将数据封装成Packet,然后序列化后发送给服务端,processPacket方法就是开始正式解析数据了,见processPacket

ZookeeperServer#processPacket

processPacket

在执行submitRequest方法的时候,数据开始进入Processor 调用链,这个调用链在上一篇文章中讲过,他的创建就不进行具体描述,leader 和follower 都有自己调用链,下面来看一下submitRequest,见submitRequest

ZookeeperServer#submitRequest

submitRequest



在上一篇博客中已经分析过了processor 生成时机和排列顺序,这里就不再进行重复说明

LeaderRequestProcessor#processRequest

根据调用链,这里的nextPricessorPrepRequestProcessor,详情见PrepRequestProcessor#processRequest

PrepRequestProcessor#processRequest

PrepRequestProcessor#processRequest

这里很简单,会将request 请求数据放入到阻塞队列中,这里添加到队列中的数据会在什么地方被消费的,根据processor调用链或者博客开始的流转图,可以很容易的看出来同样是被PrepRequestProcessor消费了,PrepRequestProcessor是个线程,在LeaderZookeeperServer初始化已经启动,可见submitRequestsetupRequestProcessor方法,所以我们来看一下PreRequestProcessor的run 方法执行了哪些逻辑,见PrepRequestProcessor#processRequest

PrepRequestProcessor#run

PrepRequestProcessor.run

可以看到PrepRequestProcessor的run方法中从submittedRequests中获取了数据,然后执行了pRequest方法进行处理,见PrepRequestProcessor#pRequest

PrepRequestProcessor#pRequest

PrepRequestProcessor#pRequest

客户端每种类型,在zk服务端都有一种类型与之匹配,这里我们就直接分析create 类型,最终我们会调用到pRequest2TxnCreate方法,见PrepRequestProcessor#pRequest2TxnCreate

在执行完创建事务以后,会执行nextProcessor,这个Processor 是ProposalRequestProcessor,见ProposalRequestProcessor#processRequest

PrepRequestProcessor#pRequest2TxnCreate

PrepRequestProcessor#pRequest2TxnCreate

ProposalRequestProcessor#processRequest

ProposalRequestProcessor#processRequest



总结一下

1:执行下一个Processor 的方法,将request 放入到一个队列中,下一个Processor 是CommitProcessor, 是一个线程,在run方法中处理数据CommitProcessor#processRequest

2: 找到所有和leader 连接的follower, 然后将数据放入到每一个follower 对应的队列中,由队列对应的线程进行处理Leader#propose

3: 执行syncprocessor的方法,将请求数据放入到队列中,由syncprocessor 是一个线程,在syncprocessor 的run 方法中处理数据,见SyncRequestProcessor#processRequest

CommitProcessor#processRequest

CommitProcessor#processRequest

这里将请求数据放入到队列,在CommitProcessor#run方法中消费这个数据,然后wekeup唤醒CommitProcessor线程,见CommitProcessor#run

CommitProcessor#run

CommitProcessor#run



总结一下:

1: 在CommitProcessor线程刚启动的时候,队列中不存在数据,因此线程被阻塞

2: 当zk 服务端接收到数据,并且最终把请求数据放入到queueRequests ,并且唤醒了CommitProcessor线程,线程从queueRequests 中获得数据,并且放入到等待commit 的队列中,这时候processCommitted方法中if 判断暂时还不满足条件,直接结束

3: 这时候因为queueRequests队列中数据被消费重新为空,因此当前线程重新处于wait 的状态

Leader#propose

Leader#propose

propose 方法主要是构造了Proposal 数据,然后准备发送给各个Follower,见Leader#sendPacket

Leader#sendPacket

Leader#sendPacket

LeaderZookeeperServer启动的时候,就创建了一个socket 和Follower,Observer 连接,并且根据连接每个节点都创建了一个LearnerHandler线程进行处理,每个线程里面都维护了一个阻塞队列,见LearnerHandler#queuePacket

LearnerHandler#queuePacket

LearnerHandler#queuePacket

同样的LearnerHandler中的队列,由LearnerHandler线程自己消费,来看一下他的run 方法,见LearnerHandler#queuePacket

LearnerHandler#run

LearnerHandler#queuePacket

因为run 方法中代码较多,只粘贴部分关键代码startSendingPackets,这是直接发送packet 数据直接进入startSendingPackets,见startSendingPackets

LearnerHandler#startSendingPackets

startSendingPackets

这里我们发现,每次发送数据的时候都新建了一个线程用于数据发送,整体的线程架构图,还是建议看博客顶部的图作为对照,线程中调用sendPacket方法发送数据,见LearnerHandler#sendPacket

LearnerHandler#sendPacket

LearnerHandler#sendPacket





这里发送的数据就是ProposalRequestProcessor#processRequest中执行zks.getLeader().propose(request)添加到队列中的数据,经过jute 序列化后发送

SyncRequestProcessor#processRequest

SyncRequestProcessor#processRequest

SyncRequestProcessor对象在上一篇博文中介绍过,在LeaderZookeeperServer 初始化的时候设置了ProposalRequestProcessor,在ProposalRequestProcessor 构造函数中创建了SyncRequestProcessor对象, 这里又出现了一个queueRequests队列,这个队列专属于SyncRequestProcessor,就是架构图中的队列3, 看一下这个类的run 方法,见SyncRequestProcessor#run

SyncRequestProcessor#run

SyncRequestProcessor#run



在run 方法中会将队列中的数据写入到log 日志文件中, 见SyncRequestProcessor#flush

SyncRequestProcessor#flush

SyncRequestProcessor#flush



这里会执行两点

1: 将当前请求写入到log 日志文件中ZKDatabase#commit

2: 执行下一个processor, 这里leader 和 follower 是不一样的,因为现在是在分析leader 源码,所以现在只分析leader 部分,follower 在follower 解析中进行分析,见AckRequestProcessor#processRequest

ZKDatabase#commit

ZKDatabase#commit



因为代码比较简单,就根据输出流写入到日志文件中,不再进行解析

AckRequestProcessor#processRequest

AckRequestProcessor#processRequest

主节点的数据写入到log 日志以后,也和follower 节点一样参与ack 确认,只有超过半数以上的ack 确认以后,后续才会执行修改内存数据库的数据,看一下具体的执行流程Leader#processAck

Leader#processAck

Leader#processAck



方法中执行了2点

1: 将ack 数据添加到set 列表中

2: leader 节点根据set 中接收到的ack 数据判断是否满足提交要求,如果满足,那么将更改内存数据库中的数据Leader#tryToCommit

Leader#tryToCommit

Leader#tryToCommit

总结一下:

1: 判断接收ack 的set 队列中接收到的ack 是否已经满足半数,如果不满足直接返回继续等待,如果满足执行后续流程,SyncedLearnerTracker#hasAllQuorums

2: 在ack 满足条件的情况下, 通知所有的follower节点提交,更改节点中的内存数据,Leader#commit

3: 将消息同步给所有的observer 节点Leader#inform

4: 唤醒处于wait的commitProcessor线程,CommitProcessor#commit

SyncedLearnerTracker#hasAllQuorums

SyncedLearnerTracker#hasAllQuorums

Leader#commit

Leader#commit

Leader#inform

Leader#inform

CommitProcessor#commit

CommitProcessor#commit

在唤醒commitProcessor线程以后,实际上就是在上文中的CommitProcessor#run 的run 方法中,执行processCommitted方法,但是在之前没有进行解析,现在我们解析一下,再把图片拉下来看一下

processCommittedprocessCommitted

CommitProcessor#processCommitted

processCommitted

CommitProcessor#sendToNextProcessor

CommitProcessor#sendToNextProcessor

这里用了线程池的方式进行了一次处理,但是如果不存在线程池,那么直接调用当前线程进行处理, 最后会调用到CommitWorkRequestdoWork方法,这里流程比较简单就不过多介绍,来看doWork,CommitWorkRequest#doWork

CommitWorkRequest#doWork

CommitWorkRequest#doWork

doWork中调用了ToBeAppliedRequestProcessorprocessRequest方法

ToBeAppliedRequestProcessor#processRequest

FinalRequestProcessor中真正的执行了内存数据更改以及构造返回的数据FinalRequestProcessor#processRequest

FinalRequestProcessor#processRequest

FinalRequestProcessor#processRequest



总结一下:

1: 执行Zookeeper内存数据库的修改Zookeeper#processTxn

2: 根据不同的请求类型构建返回参数

3: 还有getData等请求的一些监听器的处理,监听器的源码分析等有空的时候补充一下,暂时不在这里解析

4: 给客户端返回数据sendResponse

ZooKeeperServer#processTxn

Zookeeper#processTxn

NettyServerCnxn#sendResponse

sendResponse

在初始化的时候,我们确定了server 服务端是利用了netty进行通信的,所以这里的连接是NettyServerCnxn,最后将resp 结果返回给Zookeeper客户端,至此Leader 节点的流程已经完成

Follower 节点

上述Leader 节点已经解析完成了,下面解析Follower 节点,在上一篇博文中解析了Follower 节点初始化,并且创建一个socket 和leader连接,我们从Follower的followLeader方法开始,readPacket读取packet 数据,然后processPacket方法进行处理packet

Follower#processPacket

Follower#processPacket

总结一下:这里根据不同的数据类型走不同的逻辑,我们这里暂时只分析PROPOSAL, COMMIT, 一个是将请求数据写入到log日志中,一个是更新内存数据库数据,在这执行的步骤中又是调用了Processor 调用链, Commit 走上面一条, Proposal 走下面一条,下面我们来进行分析

Proposal 见 logRequest

Commit 见 FollowerZookeeperServer#commit

Proposal

FollowerZookeeperServer#logRequest

logRequest

这里SyncProcessor是在FollowerZookeeperServer初始化设置的SyncRequestProcessor, 这里的逻辑和Leader节点中的SyncRequestProcessor#processRequest 是一致的,都是将request 添加到一个队列中,然后在SyncRequestProcessor的run 方法进行消费SyncRequestProcessor#run, 这里就不进行重复解析了,也是将数据写入到log 日志中,然后执行next.processRequest,可以参照上文的代码,但是在执行了next.processRequest 代码的时候,next 指代和Leader 中的不一样,是指SendAckRequestProcessor

SendAckRequestProcessor#processRequest

Commit

FollowerZookeeperServer#commit

FollowerZookeeperServer#commit

这里commitProcessor和Leader中的CommitProcessor是一样的,所以后续执行的逻辑和Leader 节点也是一样的的,建议参考CommitProcessor#run

Zookeeper ZAB协议的更多相关文章

  1. Zookeeper ZAB 协议分析[转]

    写在开始:这是我找到一篇比较好的博客,转载到这来进行备份原文参考: Zookeeper ZAB 协议分析 前言 ZAB 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播 ...

  2. Zookeeper ZAB 协议分析

    前言 ZAB 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议.在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,ZooKeep ...

  3. zookeeper ZAB协议 Follower和leader源码分析

    Follower处理逻辑 void followLeader() throws InterruptedException { //... try { //获取leader server QuorumS ...

  4. zookeeper 入门系列-理论基础 – zab 协议

    上一章讨论了paxos算法,把paxos推到一个很高的位置.但是,paxos有没有什么问题呢?实际上,paxos还是有其自身的缺点的: 1. 活锁问题.在base-paxos算法中,不存在leader ...

  5. ZooKeeper之ZAB协议

    ZooKeeper为高可用的一致性协调框架,自然的ZooKeeper也有着一致性算法的实现,ZooKeeper使用的是ZAB协议作为数据一致性的算法,ZAB(ZooKeeper Atomic Broa ...

  6. zookeeper核心-zab协议-《每日五分钟搞定大数据》

    上篇文章<paxos与一致性>说到zab是在paxos的基础上做了重要的改造,解决了一系列的问题,这一篇我们就来说下这个zab. zab协议的全称是ZooKeeper Atomic Bro ...

  7. zookeeper 负载均衡 核心机制-实现原理 包含ZAB协议(滴滴,阿里面试)

    面试也经常问kafka的原理,以及zookeeper与kafka原理的区别:kafka 数据一致性-leader,follower机制与zookeeper的区别: zookeeper是如何实现负载均衡 ...

  8. Zookeeper一致性协议原理Zab

    ZooKeeper为高可用的一致性协调框架,自然的ZooKeeper也有着一致性算法的实现,ZooKeeper使用的是ZAB协议作为数据一致性的算法, ZAB(ZooKeeper Atomic Bro ...

  9. Zookeeper架构、ZAB协议、选举

    转载:深入浅出Zookeeper(一) Zookeeper架构及FastLeaderElection机制  (nice) ZooKeeper学习第六期---ZooKeeper机制架构 一.Zookee ...

  10. ZooKeeper(六)-- CAP和BASE理论、ZAB协议

    一.CAP理论和BASE理论 1.CAP理论 CAP理论,指的是在一个分布式系统中,不可能同时满足Consistency(一致性). Availability(可用性).Partition toler ...

随机推荐

  1. Postgresql WAL日志浅析

    一.预写日志(WAL) 预写式日志(Write Ahead Log,WAL)是保证数据完整性的一种标准方法.简单来说,WAL的中心概念是数据文件(存储着表和索引)的修改必须在这些动作被日志记录之后才被 ...

  2. LeetCode-357 统计各位数字都不同的数字个数

    来源:力扣(LeetCode)链接:https://leetcode-cn.com/problems/count-numbers-with-unique-digits 题目描述 给你一个整数 n ,统 ...

  3. linux常用操作指令记录

    https://maker.pro/linux/tutorial/basic-linux-commands-for-beginners ## 打开终端 ## **Ctrl+Alt+T** ## ls ...

  4. c/c++ 2d矢量库

    Cairo Cairo is a 2D graphics library with support for multiple output devices. Currently supported o ...

  5. vue - 环境变量和模式

    1.在项目根目录中创建.env 或者 .env.xxx 的文件来指定环境变量 .env # 在所有的环境中被载入 .env.local # 在所有的环境中被载入,但会被 git 忽略 .env.[mo ...

  6. PULPINO仿真建立

    PULPINO仿真建立 PULPINO主要特点: systemverilog实现 有常规外设,GPIO.SPI.I2C.UART等 有调试接口 总线有AXI,外设总线为APB,扩展性好 脚本为cmak ...

  7. 一台电脑连接多个不同IP段

    1.首先让电脑能联网,则在ipv4里面设置好能上网的ip地址,dns则为路由器ip 备用dns8.8.8.8 2.点击高级依次添加其他内网ip段 例如192.168.0.66 ----------19 ...

  8. .Babylon格式的模型转化为glb模型,并使用ThreeJS加载显示

    问题描述:手里有几个.babylon格式的三维模型,但是没用过babylon,只对ThreeJS熟悉:但是最新的ThreeJS里发现已经没有BabylonLoader的加载器,能查到的暮志未晚提供的例 ...

  9. Java内存分析利器——Eclipse Memory Analyzer工具的使用

    一.如何下载Java程序的dump内存文件并离线导入到MemoryAnalyser工具进行分析 1.jps查看Java应用的pid   jps 11584216168084 Launcher24792 ...

  10. ShareWAF 软件&云形态安装部署说明

    一.常用文件说明sharewaf.js:主程序daemon.js:主程序守护程序oem.js:OEM 定制文件developer.js:二次开发接口rules.js:自定义规则文件regexp.js: ...