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

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

posted @ 2022-12-12 23:52  苜蓿椒盐  阅读(118)  评论(0编辑  收藏  举报