服务端:
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会话等内容,后续有时间补充。