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 ...
随机推荐
- .netcore全局异常处理
一.背景 某天,应用程序进程无缘无故退出,也就是我们通常说的崩溃.通常情况下,windows事件会记录一条消息.但是有时候,我们发现这样的信息,对于查找问题,还是远远不够的,因为它说RunTime报错 ...
- 五子棋 framebuffer版
要在家目录下 makefile 1 main : main.o fun.o input.o fb_draw.o 2 gcc -Wall -o $@ $^ 3 clean : 4 rm -rf *.o ...
- vs2019 代码片段管理
工具/代码片段管理 上面地址是vs内部代码片段,可以参考写自定义片段 写好的模板保存为.snippet文件,放到固定文件夹中,然后使用添加,直接找到文件夹添加即可 <?xml version=& ...
- Android 数据回显
public class EchoDataUtils { /** * 保存文件到手机内存 * @param context * @param number * @param psw * @return ...
- pdf.js 跨域完美解决!
在网上查看很多方法去解决此类跨域问题,及如何动态加载pdf文件.看来看去 请求的由后台处理加header头的 pdf.js 自带的 获取地址栏param参数值的 都是很麻烦的步骤并且有时不能有效解决 ...
- 解决用flex布局时内容溢出的问题
1,2正常现象如下: 2,点击折叠图标 再点折叠 无图标了 解决:flex:1,width:0 就可以了
- nginx 安全漏洞(CVE-2021-23017) 版本升级
查看当前nginx版本信息 # ./sbin/nginx -V nginx version: nginx/1.20.1 built by gcc 4.8.5 20150623 (Red Hat 4.8 ...
- aop切面记日志
package com.netauth.utils.component; import java.lang.annotation.ElementType; import java.lang.annot ...
- 阿里播放器Aliplayer遇到的所有坑
1,关于阿里播放器使用过的几种播放方式 url (source) ① 要在创建播放器前要拿到资源否则会报错 ② 在有不同清晰度的资源时 直接调用 player.loadByUrl() 方法会报错 官 ...
- 067_VFPage中Js与controller交互方式(二) RemoteAction
上篇文章介绍了Toolkit API,是一种js的前台写法 同步调用格式:sforce.connection.method("argument1","argument2& ...



















