❶ zk源码阅读37:ZooKeeperServer源码分析
前面针对server启动到选举leader进行了一个小结,现在进入leader和follower的启动交互过程,需要先讲ZooKeeperServer,
在之前源码阅读的25节里面带过了一部分,这里详细讲解ZooKeeperServer的源码
继承关系如下
本节主要讲解内容如下
在源码阅读第24节讲解了,这里不赘述
是SessionTracker的内部接口
如下图
除去log,jmx相关部分,源码如下
ChangeRecord是ZooKeeperServer的内部类,下面会介绍
ServerStats,ZooKeeperServerListener都在25节的源码介绍过
这个类并没有调用,不用管
定义异常
这个数据结构为了促进PrepRequestProcessor以及FinalRequestProcessor的信息共享,讲到调用链的时候再讲。
其中,StatPersisted在源码阅读7中讲DataNode的时候讲过了
描述当前server所处的状态
这里列举处两个底层调用的构造函数
启动涉及到db的数据加载,这里也有集群和单机两种,调用顺序为
主要是集群的时候,server选完了leader,由leader才能调用数据加载loadData
下面按照单机版startdata函数展开
初始化zkDb完成数据加载
恢复session和数据,单机版启动或者集群版leader选举之后调用lead方法时,会调用该方法。
主要完成设置zxid以及把无效的session给kill掉的工作
这里注意,为什么需要干这件事情,在下面思考中会说
里面调用了setZxid(不展开)以及killSession函数
清除db中临时会话记录,会话跟踪器也清除记录
入口是ZooKeeperServer#startup,zkServer都是在上述加载了db的数据之后,调用startup来完成启动
启动的入口函数
调用了createSessionTracker等函数,介绍如下
createSessionTracker 完成会话跟踪器的创建
这里是默认的单机版实现,在集群版不同的角色有不同的实现,主要是参数sid不会传1,而是配置中的sid
startSessionTracker 启动会话跟踪器
设置服务器运行状态,对于ERROR和SHUTDOWN的state,进行对应的操作
在 源码阅读25:服务器异常报警,关闭机制 讲过,这里不赘述
安装请求处理链路,是PrepRequestProcessor -> SyncRequestProcessor -> FinalRequestProcessor顺序
具体在后面请求处理链路再讲
两个函数getServerId和expire
processConnectRequest用于处理client的连接请求,不展开
值得注意的地方是重连的调用
展开如下
重连的核心函数
验证sessionId和传递来的密码的正确性
根据sessionId生成密码
在会话跟踪器SessionTracker中判断会话是否还有小
完成会话初始化,根据参数valid代表认证通过与否,用来判断server是接收连接请求,还是发出closeConn的请求,不展开,重要部分如下
除去的get,set,jmx,shutdown相关函数,剩下重要函数如下
部分函数列举如下
获取下一个server的zxid,调用方需要确保控制并发顺序
上面ZooKeeperServer#expire调用了close函数,介绍如下
该函数用于提交一个 关闭某个sessionId 的请求
这里有两个函数
之前在源码21节 会话管理中讲解了会话清除,在sessionTracker的记录是马上清除的,而DateTree中临时会话的清除是通过调用链一步步来的,也就是说两个步骤不是同步的,所以如果中间服务器状态改变了,会出现不一致的情况
requestsInProcess代表正在处理的请求个数
就是说发出请求时,requestsInProcess+1,最后完成请求时,requestsInProcess-1.涉及到请求处理链。
ZooKeeperServer#checkPasswd调用
ZooKeeperServer#generatePasswd
就是sessionId要和sessionId^superSecret生成的第一个随机数相匹配即可
密码不是client端设置的,是根据sessionId生成的
ZooKeeperServer#processConnectRequest 里面调用reopenSession中
在上面已经讲了,核心就是
这里还没有深入看,先存疑
比如思考中提到的loadData为什么会出现数据不一致,属于某种异常情况的处理
为什么不放到另外一个类里面去
❷ Zookeeper之两阶段提交源码分析
zookeeper集群为了保证数据一致性,使用了两阶段提交。
在zookeeper集群的角色有:leader、follower、observer。
在这几个角色中处理读写请求是不同的:
读请求:从当前节点直接读取数据
写请求:在leader直接进行两阶段提交、在非leader则是把请求转交给leader处理
所以,分析两阶段提交就是分析集群模式下的请求处理。在单机模式在请求处理是经过RequestProcessor请求处理链处理。
单个zookeeprt请求处理主要有以下几步:
1、对当前请求生成日志txn
2、持久化日志txn
3、根据日志txn更新Database
两阶段提交(2PC)步骤:
其中标绿色的PrepRequestProcessor、SyncRequestProcessor、CommitProcessor都继承了ZooKeeperCriticalThread是一个线程。
org.apache.zookeeper.server.quorum.LeaderZooKeeperServer#setupRequestProcessors
org.apache.zookeeper.server.quorum.ProposalRequestProcessor#ProposalRequestProcessor
org.apache.zookeeper.server.quorum.LeaderRequestProcessor#processRequest
①、检查是不是local session本地session,创建临时节点会升级session
org.apache.zookeeper.server.quorum.QuorumZooKeeperServer#checkUpgradeSession
②、交给下一个请求处理器处理
作用与单机模式相同,给请求Request的Hdr和Txn赋值,然后交给下一个请求处理器处理
如果是写请求(request.getHdr() != null),则会把当前请求封装为协议并发送给follower。发送之后交给SyncRequestProcessor持久化处理
org.apache.zookeeper.server.quorum.Leader#propose
org.apache.zookeeper.server.quorum.Leader#sendPacket
发送到所有其他Followe节点forwardingFollowers
把请求放入到queuedRequests阻塞队列
①、对请求进行持久化与单机相同
org.apache.zookeeper.server.SyncRequestProcessor#run
向lead发送自己的ack(2PC发送ACK)
org.apache.zookeeper.server.quorum.Leader#processAck
org.apache.zookeeper.server.quorum.Leader#tryToCommit
org.apache.zookeeper.server.quorum.CommitProcessor#commit
提交当前请求,放入到committedRequests,最终会更新database
CommitProcessor类参数:
queuedRequests:表示接收到的请求,没有进行两阶段的提交
queuedWriteRequests:表示接收到的写请求,没有进行两阶段的提交
committedRequests:表示可以提交的请求,在两阶段验证过半之后进行会在本地进行committe操作,便添加到这个队列
commitIsWaiting:表示存在可以提交的请求(committedRequests是否有值,有true)
pendingRequests:是一个map集合,表示每个客户端sessionId的请求
Leader类参数:
outstandingProposals:表示记录提议的请求的队列,符合过半机制之后会移除
toBeApplied:表示记录待生效的请求,在FinalRequestProcessor移除
①、processRequest
org.apache.zookeeper.server.quorum.CommitProcessor#processRequest
首先判断是否需要两阶段提交。如果需要则会添加到queuedWriteRequests队列
org.apache.zookeeper.server.quorum.CommitProcessor#needCommit
如果是更改操作则返回true
d、然后,再看一下这个while的退出条件。
①、从queuedRequests取出的是空
②、如果queuedRequests数据不为空,那么requestsToProcess是大于0的。这时只有maxReadBatchSize < 0或readsProcessed <= maxReadBatchSize才能退出。
maxReadBatchSize < 0表示默认是-1,如果配置了这个参数当连续读了readsProcessed时,也会退出。
③、pendingRequests和committedRequests不为空
e、commitIsWaiting有待提交的
org.apache.zookeeper.server.quorum.Leader.ToBeAppliedRequestProcessor#processRequest
删除toBeApplied
其中标绿色的FollowerRequestProcessor、CommitProcessor、SyncRequestProcessor都继承了ZooKeeperCriticalThread是一个线程。
org.apache.zookeeper.server.quorum.FollowerZooKeeperServer#setupRequestProcessors
开了两条链:
FollowerRequestProcessor(firstProcessor)---->CommitProcessor----->FinalRequestProcessor
SyncRequestProcessor---->SendAckRequestProcessor
org.apache.zookeeper.server.quorum.FollowerRequestProcessor#processRequest
请求添加到queuedRequests队列
FollowerRequestProcessor是一个线程,会从queuedRequests获取请求
org.apache.zookeeper.server.quorum.FollowerRequestProcessor#run
createSession和closeSession也会转发给lead节点处理
org.apache.zookeeper.server.quorum.SendAckRequestProcessor#processRequest
在用SendAckRequestProcessor处理之前会先调用SyncRequestProcessor进行持久化处理,由于与单机或lead处理相同就不单独列出来了。
向领导者发送确认ack包
org.apache.zookeeper.server.quorum.Learner#writePacket
在经过FollowerRequestProcessor处理后,lead端会得到一个Request的请求
org.apache.zookeeper.server.quorum.LearnerHandler#run
org.apache.zookeeper.server.quorum.Leader#submitLearnerRequest
在连接Follower节点的客户端发送更改命令请求会转发到leader节点的prepRequestProcessor进行处理
1、run
org.apache.zookeeper.server.quorum.QuorumPeer#run
2、followLeader
org.apache.zookeeper.server.quorum.Follower#followLeader
不断读取从lead端的数据包
org.apache.zookeeper.server.quorum.FollowerZooKeeperServer#logRequest
其中标绿色的ObserverRequestProcessor、CommitProcessor、SyncRequestProcessor都继承了ZooKeeperCriticalThread是一个线程。
org.apache.zookeeper.server.quorum.ObserverZooKeeperServer#setupRequestProcessors
也是开了两条链:
ObserverRequestProcessor(firstProcessor)---->CommitProcessor----->FinalRequestProcessor
SyncRequestProcessor---->null
observer节点不参与两阶段提交,所以同步SyncRequestProcessor之后没有ACK确认提交。这样既提高了读效率,又对写效率没有影响。请求处理链与leader、follower的功能相同不再累述。
zookeeper集群的两阶段提交,是在写操作的情况下发生的。2PC的整体实现逻辑是在RequestProcessor请求处理链处理的。只有在接受到的ACK超过一半才会进行提交,提交的实现逻辑是在CommitProcessor中实现的,CommitProcessor处理器中里面涉及多种集合、队列等参数(需要首先了解这些参数意义,然后再读CommitProcessor源码)。
❸ Zookeeper选主过程,理论和源码结合,看这一篇足够了
【共4239字,阅读需要15分钟】
Zookeeper作为Dubbo生态的默认注册中心,得到了非常的普遍的应用,虽然后来阿里又出了nacos,但是不可否认的是ZK仍然是一款非常优秀的开源产品,非常优秀的注册中心备选方案。
ZK有很多特性,本篇文章主要介绍ZK的选主过程(后宫佳丽三千,我就独宠你一人)
要说选主的过程,我们首先得了解ZK到底有哪些节点,这些节点充当得角色是什么?
ZK本身得节点主要分为三类:
Leader:主要是负责数据的写入,如果超过半数同意,那么就广播进行写入;
Follower:主要负责查询请求并将写入请求发送给leader,参与选举和写入投票;
Observe:也是负责查询请求并将写入请求发送给leader,不参加投票,只被动接收结果
获取半数投票以上的节点成为leader节点。
万事万物都有一个准则,好的比较坏的,坏的比较更坏的,世上本没有痛苦,痛苦都是自己寻找的结果,海燕你可长点心吧,哎呀跑偏了。
ZK比较的时候有三个指标或者三个维度:
(1)任期
(2)事务ID(ZK中的事务ID)
(3)节点编号(集群中每个节点的编号)
根据以上三个指标就可以说出最终的结论了:选择任期大的,任期一样选择事务ID大的,前两个都一样,选择节点编号大的。
就这么简单?是的。规则就是这么简单,但是源码还是有那么一丢丢的绕。
源码看着相对比较枯燥,但是作为一个手艺人,怎么能不去了解怎么做的呢,我们先来梳理一下代码的流程,方便更好的看第四部分内容。
节点先投自己一票,然后进行广播
节点内部循环进行消息接收
收到消息后
如果消息为空,就进行重新发送消息或者建立连接
如果消息不为空,且消息接收者和投票的leader都是合法节点就进行下边步骤。
如果节点为looking节点
根据当前节点的投票和接收到的投票进行比较来决定是否需要再次发送投票并且记录投票的结果
每次都判断记录的票数,如果过半就进行节点状态的设置
选主的逻辑是在lookForLeader开始的,像金字塔的第一块砖一样,我们先看ZK选主的第一块砖lookForLeader,第一次看源码得时候一定要把握主线,忽略从线,等主线完全理清楚了之后才去处理从线,要不会陷入迷宫之中。
下边就是主要的投票代码,看里边的注释:
更新投票或者投票的方法为:
发送通知的方法为:
待到山花烂漫时,她在丛中笑,消息都已经发完了,肯定就到了接收到选票的时候应该怎么操作了,接收选票的代码也是在lookForLeader中:
接上代码继续讨论,校验发送投票节点的状态,我们从本文的第一章节知道Observe节点是不参与投票的,只是转发写请求和被动接收数据,负责查询请求,所以从代码中我们也可以看出来:
当发送投票的节点状态是FOLLOWING和LEADING时,代表发送节点已经选举完成,所以处理方法的逻辑都是一样滴,这部分限于篇幅太长,暂时就不深入讨论了,感兴趣的朋友可以私信我或者加我微信号M_P_E_D进行交流和沟通。
终于到重头戏了,咱们看看LOOKING状态时的代码:
我们先把totalOrderPredicate方法放前边,这个其实就是选举leader的规则的实现。
道阻且长,行则将至,行而不辍,未来可期,加油。