zookeeper技术内幕,简要记录;

服务端:

1,启动核心流程【以分布式为例】

启动类:QuorumPeerMain

首先初始化数据,因为整个流程十分复杂,所以只记录核心的主流程;

首先,zookeeper会为每台机器创建一个QuorumPeer实例,代表着一台服务器;接着创建FileTxnSnapLog,用来处理事务日志的持久化(包括append追加和快照持久化),然后创建内存数据容器ZKDatabase,接着调用QuorumPeer实例的start方法启动;

启动流程:

1)根据初始化时创建的FileTxnSnapLog来将磁盘中的数据转储到内存ZKDatabase中;

2)绑定本机端口

3)开始选举

4)独立线程启动

 

启动过程子流程详解:

1)根据初始化时创建的FileTxnSnapLog来将磁盘中的数据转储到内存ZKDatabase中

因为zookeeper的文件名包含了zxid,所以这一步可以拿到磁盘文件中最大的zxid,放到内存中;

找磁盘文件时,每次只会找100个文件;

2)绑定本机端口

有NIO和Netty两种方式,逻辑比较简单

3)开始选举

启动过程的选举,首先会确定一张选票,格式(long id, long zxid, long peerEpoch)

然后创建一个网络连接器QuorumCnxManager用来跟其他服务器进行通信

QuorumCnxManager的核心:

一个消息接收队列BlockingQueue<Message> recvQueue

消息发送队列集合queueSendMap<Long, BlockingQueue<ByteBuffer>>

连接监听器Listener;

如果创建的连接监听器有有效(不为空),则开始以下流程:

监听器线程start,然后在会初始化连接处理器ListenerHandler线程;

连接处理器ListenerHandler线程start,然后ListenerHandler会创建socket连接,并且不断监听来自其他服务器的网络连接socket请求,并且只会跟sid比自己大的服务器连接;

当连接建立后,SendWorker线程和RecvWorker线程开始工作,start;

SendWorker主要是负责从queueSendMap取出消息,发送给其他服务器;

RecvWorker主要负责接收其他服务器的消息,保存到recvQueue中;

 

做完以上步骤后,进入核心选举算法FastLeaderElection的流程中:

FastLeaderElection 维护了

一个消息发送队列LinkedBlockingQueue<ToSend> sendqueue,

一个消息接收队列LinkedBlockingQueue<Notification> recvqueue

消息发送线程WorkerSender

消息接收线程WorkerReceiver

紧接着start 

WorkerSender线程和WorkerReceiver线程;

WorkerSender会不断轮询从sendqueue取出消息进行发送;

 

WorkerReceiver的处理逻辑:

从recvQueue中取出消息

1)如果是来非投票服务器(例如Observer)的消息,直接将自身选票放入sendqueue中,响应回去

2)如果是来自投票服务器的消息,首先看消息中的状态是不是LOOKING,如果是LOOKING,并且消息中逻辑时钟小于当前

服务器的逻辑时钟(逻辑时钟:logicalclock,也就是epoch—选举周期,用于标识当前选举轮次,每次选举轮次都会对该值自增,并且所有有效的投票必须在同一轮次中),则将自身选票放入sendqueue中,响应回去;否则看,如果消息中的状态是LOOKING,并且当前服务器不是LOOKING,也将

自身选票放入sendqueue中,响应回去;

以上主要为WorkerSender和WorkerReceiver的重要工作流程;

 

Zookeeper中主要有两种情况需要进行选举;

1,服务器启动(QuorumPeer线程调用start方法)

2,服务器运行期间无法与leader保持连接

针对第一种情况,当QuorumPeer线程调用start方法后,会进入到服务器启动的选举流程,会走到FastLeaderElection最为核心的lookForLeader

方法中:

最开投票的时候,每台服务器都会将自己推举为leader进行投票;

1)如果当前服务器状态为LOOKING:首先自增逻辑时钟logicalclock,然后初始化自身选票,并且发送出去;

紧接着接收外部投票(从recvQueue中获取),如果获取不到,会检查自己与其他服务器的连接,如果连接成功,会继续将初始化的选票发送出去;

然后对比选举轮次:

 

如果外部投票的选举轮次大于当前选举轮次logicalclock,那么立即更新当前选举轮次,并且清空所有已经收到的选票,

接着用本地内部选票与外部投票进行PK,new代表外部选票,cur代表本地选票,

(((newEpoch > curEpoch)|| ((newEpoch == curEpoch)&& ((newZxid > curZxid)|| ((newZxid == curZxid)&& (newId > curId)))))),PK完成后,更新

本地选票,并且将其发送出去;

 

如果外部投票的选举轮次小于当前选举轮次logicalclock,不做任何操作;

 

如果外部投票的选举轮次等于当前选举轮次logicalclock,那么直接PK,PK完成后,更新本地选票,并且将其发送出去;

 

上面的操作完成之后,进行选票归档,将收到的所有外部选票放入Map<Long, Vote> recvset中,其中key为外部服务器id

 

然后统计投票:确认集群中是否有过半服务器(可以看看是怎么确认过半的。)接收了当前的内部选票,如果有,则终止投票,否则继续接收外部选票;

统计完成之后,服务器开始更新服务器状态,看自己是否为leader,follwer或者observer;

服务端通过processor调用链处理各种请求,包括客户端的请求;

 

客户端:

客户端与服务端建立连接后,监听服务端的socket请求,然后ClientCnxn中的EventThread读取请求数据,并且将请求放到

EventThread中的LinkedBlockingQueue<Object> waitingEvents 事件等待队列中,开始不断轮询其中的请求事件,包括watcher监听事件;

还有Session会话等内容,后续有时间补充。

posted on 2021-10-21 01:54  哈皮的玩偶  阅读(36)  评论(0编辑  收藏  举报