概述
这篇文章目的主要是为了讲解清楚zookeeper启动过程中启动各类服务,说白了就是启动了线程提供服务,希望通过这个文章的梳理能够让大家对整个zookeeper的线程架构有一个清晰的印象。
线程分析
整个zookeeper的线程按照功能进行划分,主要分为下面5类,分别是 选举相关线程、Client连接相关线程、Peer连接相关线程、Peer调用链线程、quorumPeer线程。

选举相关线程
选举相关线程顾名思义,就是在zookeeper集群发生重新选举的时候用于处理选举相关的线程,核心类在于FastLeaderElection,该类内部包含WorkerReceiver和WorkerSender两个核心线程,负责发送和接收选举报文。
FastLeaderElection本身其实就在一个单独的线程,也就是后面说的quorumPeer线程中运行的,本身内部具备一个while循环再处理选举相关的逻辑,所以可以认为FastLeaderElection也是一个线程。



Client连接相关线程
client连接相关线程顾名思义,就是zookeeper在处理client连接过程中相关的线程,其实zookeeper在实现的server的多线程模型其实就是Nio模型中提到的相关模型,有兴趣可以阅读《netty概念小结》。
该实现的核心类主要是NIOServerCnxnFactory,该类内部包含expirerThread、acceptThread、selectorThreads,这里我只关注后面两者,其中acceptThread主要负责处理client的连接,selectorThreads主要负责处理每个client具体的数据读写,也就是说我们把accept和io处理进行了分离。


Peer连接相关线程
peer之间的连接主要是zookeeper集群间通信的连接,一般是指follower或learner连接server的过程。当我们的一个zookeeper节点作为leader的时候我们会启动一个accept线程LearnerCnxAcceptor用于接受peer的连接,作为server端我们会为每个peer发送过来的连接建立一个线程进行处理,针对每个连接新建线程的核心类是LearnerHandler。
这部分的accept线程LearnerCnxAcceptor和处理线程LearnerHandler主要用于zookeeper的节点之间的通信,跟client无关,跟client无关,跟client无关。


Peer调用链线程


PrepRequestProcessor
PrepRequestProcessor线程。该线程消费请求队列submittedRequests,开始实施一致性算法。submittedRequests有两个来源,一是接入的客户端直接提交,提交的请求既包括写请求,也包括一些查询请求;另一个是由Follower转发,转发内容只包括写请求和同步请求。PrepRequestProcessor收到submittedRequest后,将请求转发给CommitProcessor线程和SyncRequestProcessor线程的输入队列;对于其中的写请求,向所有follower发送PROPOSAL消息(异步发送)。

CommitProcessor
该线程主要消费两个队列queuedRequests和committedRequests。queuedRequests保存PrepRequestProcessor线程下发的submittedRequest消息。committedRequests保存Proposal通过后,LearnerHanlder线程(后文会有说明)发来的提交请求。
CommitProcessor在这里做了如下处理:对于queuedRequests中客户端的查询request,直接返回本地数据;对于客户端提交的或follower转发来的写请求,作为一个pendingRequest等待相应的表决结果返回committedRequest到committedRequests队列。对于队列中到来的每一个committedRequest,如果当前有pendingRequest等待,并且其sessionId,zxid和这个请求匹配,则处理pendingRequest(如果原始请求发自客户端,pendingRequest会携带客户端连接对象,从而能够发送响应给客户端),否则直接处理committedRequest(这种情况对应Follower中的CommitProcessor直接接收到了commit消息)。处理的过程是记录committedLog,变更本地数据。如果请求从客户端来,发送响应给客户端。那么如果一个pendingRequest始终等不到对应的committedRequest到来呢?答案是会一直等待,从而会阻止之后所有queuedRequest请求的处理!开始看到这里以为是个bug,后来想想,如果发生这种情况,已经说明Zookeeper的voter节点超过半数Fault了(不管是消息丢失还是宕机)。这时整个Zookeeper服务只能是不可用了。否则只要过半的voter节点可用,一定会有相应的committedRequest返回。同时这里也保证了写请求按到达顺序生效。

SyncRequestProcessor
该线程负责将submittedRequest记录到Log。ZooKeeper使用一个简单的内存数据库ZKDatabase来处理日志、session信息和datatree(znode树,类似文件系统结构,用来组织存放实际数据。与文件系统不同的是目录也可以有数据)日志采用1000条批量flush到日志文件,满一定条数起单独线程生成snap文件。记录完日志后直接发送ACK消息给Leader对象—作为一个投票者投出自己的一票。

FollowerRequestProcessor
该线程主要是负责follower接收client连接后的报文处理链条,follower接收到client的报文后提交到queuedRequests队列,由FollowerRequestProcessor进行处理并提交到CommitProcessor进行处理。

quorumPeer线程
quorumPeer的在zookeeper集群模式下每个节点本身就是一个quorumPeer服务,内部启用线程来处理发生重新选举的场景,也就是说白了就是每个zk节点就是quorumPeer节点。
