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 ...
随机推荐
- Abp返回时间格式化
private void ConfigureDateTime() { Configure<MvcNewtonsoftJsonOptions>(options => { options ...
- 看图王 10.9.1.9791 With X64 去广告绿色版
修改历史:2022.12.14:自改官方 10.9.1.9791 最新正式版本2022.06.18:首个自改官方 10.9.0.9760 修改内容:美化部分关联图标:基于官方最新版本制作,精简部分非必 ...
- 挂上burpsuite代理之后显示505 HTTP Version Not Supported 解决方法
505 HTTP Version Not Supported 什么意思呢? HTTP505状态码代表的意思是 服务器不支持的HTTP版本,即 HTTP 505 HTTP Version Not Su ...
- 微信小程序中实现自定义省市区选择
背景: 1.小程序官方提供的省市区组件的数据与原有的数据对应不上,官方的数据太旧 2.小程序重构以后没有在使用vant等框架 实现过程: 1.安装依赖 a.微信小程序使用npm包配置: 1.初始化pa ...
- plc扫描周期
- word生成产生错误的原因
技术背景 很多时候我们网站或者系统需要提供一些word文件,例如证明.docx或者订单.docx等文件供用户下载打印等. 用Java操作word文档,毫无疑问,当下最流行apache poi,对于po ...
- mybatis读取blob类型
mybatis 读取blob数据 mybatis读取blob数据过程: 1.从数据库中读出blob数据类型,用pojo中的byte[]接收. 2.把文件保存成文件(或者变成base64也行). Cus ...
- k8s 1.20.5(补充)
1.根据前面1.15.0补充 2.初始化操作 selinux swap firewall关闭防火墙 swapoff -a 禁用交换空间 vim /etc/sysctl.d/k8s.conf net.b ...
- vs 工具 dumpbin & corflags
dumpbin 查看 dll 接口函数 > dumpbin /exports "/path/to/dll" dumpbin 查看 exe.dll 依赖的动态库 > du ...
- 关于vCenter6.7 导出的ovf 不能导入vCenter6.5/6.0的问题
正常从6.7导出ovf文件,包含四个文件. 编辑ovf文件,删除下面五条条目, 导入6.5时, 只用 ovf 和 vmdk 这两个文件导入 ,即可!