Zookeeper ZAB协议
这篇博客是从源码的角度了解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
这个代码是比较清晰的了,NettyServerCnxnFactory
的构造函数中很经典的使用了netty
, 并且在pipline
中放入了一个名称为servercnxnfactory
的Handler, 这个Handler 是CnxnChannelHandler
对象,在接收客户端的数据都会经过这个handler,然后在start 方法中启动netty
CnxnChannelHandler
是数据接收的入口,我们重点分析一下这个,详情见CnxnChannelHandler
CnxnChannelHandler
CnxnChannelHandler 中包含了多个方式,包括读,写,心跳事件,连接事件,断开连接等,这里我们暂时只分析
读事件channlRead
,见channelRead
写事件write
CnxnChannelHandler#channelRead
在netty
中处理每一个socket 数据都会走pipline
中Handler的channelRead
方法,在channelRead
通过processMessage
进行处理,见processMessage
NettyServerCnxn#processMessage
NettyServerCnxn#receiveMessage
因为只关注主流程,所以折叠了一些代码,在分析客户端的源码的时候,了解到zk 客户端是将数据封装成Packet,然后序列化后发送给服务端,processPacket
方法就是开始正式解析数据了,见processPacket
ZookeeperServer#processPacket
在执行submitRequest
方法的时候,数据开始进入Processor 调用链,这个调用链在上一篇文章中讲过,他的创建就不进行具体描述,leader 和follower 都有自己调用链,下面来看一下submitRequest
,见submitRequest
ZookeeperServer#submitRequest
submitRequest
在上一篇博客中已经分析过了processor 生成时机和排列顺序,这里就不再进行重复说明
LeaderRequestProcessor#processRequest
根据调用链,这里的nextPricessor
是PrepRequestProcessor
,详情见PrepRequestProcessor#processRequest
PrepRequestProcessor#processRequest
PrepRequestProcessor#processRequest
这里很简单,会将request 请求数据放入到阻塞队列中,这里添加到队列中的数据会在什么地方被消费的,根据processor
调用链或者博客开始的流转图,可以很容易的看出来同样是被PrepRequestProcessor
消费了,PrepRequestProcessor
是个线程,在LeaderZookeeperServer
初始化已经启动,可见submitRequest的setupRequestProcessor
方法,所以我们来看一下PreRequestProcessor
的run 方法执行了哪些逻辑,见PrepRequestProcessor#processRequest
PrepRequestProcessor#run
可以看到PrepRequestProcessor
的run方法中从submittedRequests
中获取了数据,然后执行了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
总结一下:
1: 在CommitProcessor
线程刚启动的时候,队列中不存在数据,因此线程被阻塞
2: 当zk 服务端接收到数据,并且最终把请求数据放入到queueRequests
,并且唤醒了CommitProcessor
线程,线程从queueRequests
中获得数据,并且放入到等待commit 的队列中,这时候processCommitted
方法中if 判断暂时还不满足条件,直接结束
3: 这时候因为queueRequests
队列中数据被消费重新为空,因此当前线程重新处于wait 的状态
Leader#propose
propose 方法主要是构造了Proposal 数据,然后准备发送给各个Follower,见Leader#sendPacket
Leader#sendPacket
从LeaderZookeeperServer
启动的时候,就创建了一个socket 和Follower,Observer 连接,并且根据连接每个节点都创建了一个LearnerHandler
线程进行处理,每个线程里面都维护了一个阻塞队列,见LearnerHandler#queuePacket
LearnerHandler#queuePacket
同样的LearnerHandler
中的队列,由LearnerHandler
线程自己消费,来看一下他的run 方法,见LearnerHandler#queuePacket
LearnerHandler#run
因为run 方法中代码较多,只粘贴部分关键代码startSendingPackets
,这是直接发送packet 数据直接进入startSendingPackets
,见startSendingPackets
LearnerHandler#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
总结一下:
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#inform
CommitProcessor#commit
CommitProcessor#commit
在唤醒commitProcessor
线程以后,实际上就是在上文中的CommitProcessor#run 的run 方法中,执行processCommitted
方法,但是在之前没有进行解析,现在我们解析一下,再把图片拉下来看一下
processCommitted
见processCommitted
CommitProcessor#processCommitted
CommitProcessor#sendToNextProcessor
CommitProcessor#sendToNextProcessor
这里用了线程池的方式进行了一次处理,但是如果不存在线程池,那么直接调用当前线程进行处理, 最后会调用到CommitWorkRequest
的doWork
方法,这里流程比较简单就不过多介绍,来看doWork
,CommitWorkRequest#doWork
CommitWorkRequest#doWork
在doWork
中调用了ToBeAppliedRequestProcessor
的processRequest
方法
ToBeAppliedRequestProcessor#processRequest
在FinalRequestProcessor
中真正的执行了内存数据更改以及构造返回的数据FinalRequestProcessor#processRequest
FinalRequestProcessor#processRequest
FinalRequestProcessor#processRequest
总结一下:
1: 执行Zookeeper
内存数据库的修改Zookeeper#processTxn
2: 根据不同的请求类型构建返回参数
3: 还有getData
等请求的一些监听器的处理,监听器的源码分析等有空的时候补充一下,暂时不在这里解析
4: 给客户端返回数据sendResponse
ZooKeeperServer#processTxn
NettyServerCnxn#sendResponse
在初始化的时候,我们确定了server 服务端是利用了netty
进行通信的,所以这里的连接是NettyServerCnxn
,最后将resp 结果返回给Zookeeper客户端,至此Leader 节点的流程已经完成
Follower 节点
上述Leader 节点已经解析完成了,下面解析Follower 节点,在上一篇博文中解析了Follower 节点初始化,并且创建一个socket 和leader连接,我们从Follower的followLeader
方法开始,readPacket
读取packet 数据,然后processPacket
方法进行处理packet
Follower#processPacket
总结一下:这里根据不同的数据类型走不同的逻辑,我们这里暂时只分析PROPOSAL, COMMIT, 一个是将请求数据写入到log日志中,一个是更新内存数据库数据,在这执行的步骤中又是调用了Processor 调用链, Commit 走上面一条, Proposal 走下面一条,下面我们来进行分析
Proposal 见 logRequest
Commit 见 FollowerZookeeperServer#commit
Proposal
FollowerZookeeperServer#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协议的更多相关文章
- Zookeeper ZAB 协议分析[转]
写在开始:这是我找到一篇比较好的博客,转载到这来进行备份原文参考: Zookeeper ZAB 协议分析 前言 ZAB 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播 ...
- Zookeeper ZAB 协议分析
前言 ZAB 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议.在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,ZooKeep ...
- zookeeper ZAB协议 Follower和leader源码分析
Follower处理逻辑 void followLeader() throws InterruptedException { //... try { //获取leader server QuorumS ...
- zookeeper 入门系列-理论基础 – zab 协议
上一章讨论了paxos算法,把paxos推到一个很高的位置.但是,paxos有没有什么问题呢?实际上,paxos还是有其自身的缺点的: 1. 活锁问题.在base-paxos算法中,不存在leader ...
- ZooKeeper之ZAB协议
ZooKeeper为高可用的一致性协调框架,自然的ZooKeeper也有着一致性算法的实现,ZooKeeper使用的是ZAB协议作为数据一致性的算法,ZAB(ZooKeeper Atomic Broa ...
- zookeeper核心-zab协议-《每日五分钟搞定大数据》
上篇文章<paxos与一致性>说到zab是在paxos的基础上做了重要的改造,解决了一系列的问题,这一篇我们就来说下这个zab. zab协议的全称是ZooKeeper Atomic Bro ...
- zookeeper 负载均衡 核心机制-实现原理 包含ZAB协议(滴滴,阿里面试)
面试也经常问kafka的原理,以及zookeeper与kafka原理的区别:kafka 数据一致性-leader,follower机制与zookeeper的区别: zookeeper是如何实现负载均衡 ...
- Zookeeper一致性协议原理Zab
ZooKeeper为高可用的一致性协调框架,自然的ZooKeeper也有着一致性算法的实现,ZooKeeper使用的是ZAB协议作为数据一致性的算法, ZAB(ZooKeeper Atomic Bro ...
- Zookeeper架构、ZAB协议、选举
转载:深入浅出Zookeeper(一) Zookeeper架构及FastLeaderElection机制 (nice) ZooKeeper学习第六期---ZooKeeper机制架构 一.Zookee ...
- ZooKeeper(六)-- CAP和BASE理论、ZAB协议
一.CAP理论和BASE理论 1.CAP理论 CAP理论,指的是在一个分布式系统中,不可能同时满足Consistency(一致性). Availability(可用性).Partition toler ...
随机推荐
- Postgresql WAL日志浅析
一.预写日志(WAL) 预写式日志(Write Ahead Log,WAL)是保证数据完整性的一种标准方法.简单来说,WAL的中心概念是数据文件(存储着表和索引)的修改必须在这些动作被日志记录之后才被 ...
- LeetCode-357 统计各位数字都不同的数字个数
来源:力扣(LeetCode)链接:https://leetcode-cn.com/problems/count-numbers-with-unique-digits 题目描述 给你一个整数 n ,统 ...
- linux常用操作指令记录
https://maker.pro/linux/tutorial/basic-linux-commands-for-beginners ## 打开终端 ## **Ctrl+Alt+T** ## ls ...
- c/c++ 2d矢量库
Cairo Cairo is a 2D graphics library with support for multiple output devices. Currently supported o ...
- vue - 环境变量和模式
1.在项目根目录中创建.env 或者 .env.xxx 的文件来指定环境变量 .env # 在所有的环境中被载入 .env.local # 在所有的环境中被载入,但会被 git 忽略 .env.[mo ...
- PULPINO仿真建立
PULPINO仿真建立 PULPINO主要特点: systemverilog实现 有常规外设,GPIO.SPI.I2C.UART等 有调试接口 总线有AXI,外设总线为APB,扩展性好 脚本为cmak ...
- 一台电脑连接多个不同IP段
1.首先让电脑能联网,则在ipv4里面设置好能上网的ip地址,dns则为路由器ip 备用dns8.8.8.8 2.点击高级依次添加其他内网ip段 例如192.168.0.66 ----------19 ...
- .Babylon格式的模型转化为glb模型,并使用ThreeJS加载显示
问题描述:手里有几个.babylon格式的三维模型,但是没用过babylon,只对ThreeJS熟悉:但是最新的ThreeJS里发现已经没有BabylonLoader的加载器,能查到的暮志未晚提供的例 ...
- Java内存分析利器——Eclipse Memory Analyzer工具的使用
一.如何下载Java程序的dump内存文件并离线导入到MemoryAnalyser工具进行分析 1.jps查看Java应用的pid jps 11584216168084 Launcher24792 ...
- ShareWAF 软件&云形态安装部署说明
一.常用文件说明sharewaf.js:主程序daemon.js:主程序守护程序oem.js:OEM 定制文件developer.js:二次开发接口rules.js:自定义规则文件regexp.js: ...