1、3.4.5 源码编译

1、下载源码,执行以上批改
首先

build.xml更改<property name="ivy.url"              value="https://repo1.maven.org/maven2/org/apache/ivy/ivy" />ivysettings.xml更改<property name="repo.maven.org"    value="https://repo1.maven.org/maven2/" override="false"/>  <property name="repo.jboss.org"    value="https://repository.jboss.org/nexus/content/groups/public-jboss/" override="false"/>  <property name="repo.sun.org"    value="https://download.java.net/maven/2/" override="false"/>

其实都是把http改为https,否则这会无法访问,在2020年1月之后就不能够拜访了
2、执行 ant eclipse
3、import 指定 eclipse
4、/Users/xxx/IdeaProjects/zookeeper 间接根目录下执行 ant即可

2、Zookeeper三种角色 & 节点类型

Leader、Follower和Observer
只有Leader能够写数据,其余节点只能同步数据,Follower参加选举
Observer不参加选举
所以只能一个leader进行写,单机写入最多每秒上万QPS,这是没法扩大的,所以zk适宜是写少读多的场景
长久化节点、长期节点、程序节点(是和长久化节点和长期节点配合应用)

3、ZAB(Zookeeper Atomic Broadcast)

ZAB = 2PC + 过半写磁盘日志, proposal(os cache) + 过半ack + commit(forceSync磁盘)内存数据结构

4、程序一致性

程序一致性,其实是最终一致性。因为leader肯定会保障所有的proposal同步到follower上都是依照程序来走的,起码程序不会乱

5、Zookeeper集群部署 & 外围参数

4核8G的机器,一般来说,每秒并发搞到1000是能够的
8核16G的机器,每秒并发搞到几千是能够的
16核32G的机器,每秒并发搞到上万或者是几万都是有可能的

tickTime : 默认2s,其余一些参数会以这个tickTime为基准来进行设置
dataDir : 数据快照
dataLogDir : 日志数据,proposal日志文件
snapCount : 默认10万个事务,存储一次快照
initLimit : 默认是10, 10 * tickTime,20s,启动结束之后,follower和leader之间的数据同步,如果超过这个工夫,leader间接对外提供服务了
syncLimit : 默认值是5,5 * 2 = 10s,leader和follower之间会进行心跳,如果超过10s没有心跳,leader就把这个follower给踢出去了,认为他曾经死了
maxClientCnxns : 每个客户端对zk的连贯最大是60个
jute.maxbuffer : 一个znode最多能够存储1MB数据

server.x = zk01:2888:3888
3888 是用来leader选举的,2888是用来数据同步的

客户端反对非交互式
./zkCli.sh -server localhost:2181 get /zookeeper2

6、读写锁

[写锁,写锁,读锁,读锁,写锁,读锁,读锁,读锁]
写锁只关怀后面一个节点,对后面的这个节点做监听,读锁,应该计算排在离本人最近的一把写锁
所谓的羊群效应,就是不能我开释锁了,把所有不是监听我的锁都惊醒了,curator读锁应该是判断的离本人最后面的一个写锁

7、leader选举

三台机器myid 1,2,3
如果是空的集群,他们zxid都是0,如果是1,2,3顺次启动,则第二台机器入选为leader,当quorum=(3/2+1)=2,半数的机器的时候才会进行选举
leader选举
第一轮投票
myid=0的机器,投票(0,0),(myid,zxid),播送给集群其余节点
myid=1的机器,投票(1,0),(myid,zxid),播送给集群其余节点
myid=0的机器,接管到(1,0),和投出去(0,0)比照,因为不一样,须要从新调整抉择(1,0)
myid=1的机器,接管到(0,0),和投出去(1,0)比照,不须要从新调整持续(1,0)

第二轮投票
myid=0的机器,投票(1,0),(myid,zxid),播送给集群其余节点
myid=1的机器,投票(1,0),(myid,zxid),播送给集群其余节点
myid=0的机器,接管到(1,0)
myid=1的机器,接管到(1,0)

投票统一了,都抉择了myid=1的这台机器,所以leader就产生了,最初就算myid=3启动了,因为leader曾经存在,所以只能follower追随

8、客户端源码

8.1、创立一个Zookeeper对象,Zookeeper对象中蕴含了一个ClientCnxn,这个对象不简略,代表一个客户端
8.2、ClientCnxn 三个重要的组件,
第一个组件是 ClientCnxnSocketNIO,应用NIO进行网络连接
第二个组件是 SendThread线程 用于客户端申请的发送
第三个组件是 EventThread线程 用于客户端接管watcher的回调
8.3、客户端命令会放入到 outgoingQueue 队列中,判断是否 finished,同时本人wait住,同时唤醒SendThread selector,让其干活,进行read/write
8.4、一旦SendThread run将outgoingQueue中的Packet写出去之后,会放入到pendingQueue中(阐明已发送进来,然而还没有收到响应,一旦响应结束,才会革除pendingQueue中的Packet)
8.5、SendThread read事件触发,之前outgoingQueue发送到zk server收到了响应
读取后果如果没有回调监听,间接示意finished=true,nofityAll packet,让客户端操作实现
如果有监听,比如说创立节点的时候指定了一个Watch,会标记finished=true,同时将Packet放入到waitingEvents(在EventThread中)
8.6、EventThread线程run办法会阻塞获取 waitingEvents 事件,解决监听回调watcher.process(pair.event)和回调函数processResult

9、session id的生成

Session生成算法
以后工夫左移24,又挪动8位
myid 左移 56
前两者相或,说白了,其实就是不单单用零碎工夫作为SessionId,要加myid(其实说白就是机器id)能力保障唯一性

session进行分桶设计,这样的益处是批量进行超时,不做无用功,比如说4和5个这个工夫在同一个桶外面

(4/2 + 1) * 2 = 6
(5/2 + 1) * 2 = 6

(8/2 + 1) * 2 = 10
(9/2 + 1) * 2 = 10

SessionTrackerImpl 是一个线程,有后盾线程,一直check线程超时,check的时候也是有逻辑的
tickTime 默认2s roundToInterval = (time / expirationInterval + 1) * expirationInterval 等于下一次 超时工夫nextExpirationTime

ClientCnxn客户端周期性sendPing,通过 ClientCnxnSocketNIO 发送,维持和NIOServerCnxn也就是leader的心跳,每次申请也会进行touchSession
其实也是依据下面的 roundToInterval 算进去的,所以下面的 nextExpirationTime 工夫必定比要超时的桶工夫小tickTime 倍数

所以超时check就是逻辑就是如果以后工夫不到,就间接wait(nextExpirationTime - currentTime),而后间接超时session就能够,要晓得不论是ping还是
touchSession都会让session往前桶走,所以相当于 touch是前走,超时是后追

10、Leader选举流程(3888端口)


1、QuorumPeerMain是入口
2、解析zoo.cfg
3、创立并启动DatadirCleanupManager(用来周期性的清理log和snapshot)
4、创立网络通信组件,默认是 NIOServerCnxnFactory
5、创立QuourmPeer线程,是一个线程,这个可不简略,在 QuorumPeerMain 中会 start(是办法,不是线程start) QuourmPeer线程,外面做了很多要害事件
6、从快照文件中复原数据,复原log和snapshot
7、启动NIOServerCnxnFactory网络连接,是一个线程,让NIO开始监听干活,NIOServerCnxnFactory实现多路复用,ACCEPT、READ和WRITE事件,之后是
责任链的链条解决对应的是 LeaderZooKeeperServer
PreRequestProcessor -> ProposqlProcessor -> CommitProcessor -> toBeAppliedProcessor -> FinalProcessor
8、开始选举,两个事件
8.1、QuorumCnxManager.Listener创立3888,选举服务端端口监听,监听客户端的连贯,创立SendWorker和RecvWorker线程,并启动
8.2、创立FastLeaderElection的时候不得了,starter -> new Messenger构造方法中,启动了 WorkerSender 和 WorkerReceiver 线程
9、启动 QuorumPeer 线程,走run办法,因为状态是LOOKING,走的是 FastLeaderElection.lookForLeader
sendNotifications 给其余节点发送告诉的时候,会创立和Listener的连贯,这里其实有一个问题,Zookeeper选举网络互连的核心思想是 : 只有大的server id能力连贯小的server id,否则就算连贯上也会被close
然而在3.4.5版本中 sendNotifications就开始和其余的peer开始建设连贯,意思是说,我能够小的连贯大的,会在Listener,也就是服务端判断,如果过去的server id比我小,断开
socket连贯,其实应该在建设网络连接的时候判断,而不是建设结束了再close

一直从 recvqueue 拿票
归票,这里有一个点须要留神 recvset 这个外面其实是蕴含了其余peer节点的票和本人的票(怎么会有本人的票呢?没有看到放进去啊?)
其实在sendNotifications的时候会判断,org.apache.zookeeper.server.quorum.QuorumCnxManager#toSend

这段代码很重要,就是本人给本人发送,间接放入到 recvQueue 中,就不走网络通信了
还有一层意义是本人不和本人网络连接,这样最终其实就放入到了

if (self.getId() == sid) {             b.position(0);     addToRecvQueue(new Message(b.duplicate(), sid));    /*     * Otherwise send to the corresponding thread to send.     */}org.apache.zookeeper.server.quorum.FastLeaderElection#termPredicate走org.apache.zookeeper.server.quorum.flexible.QuorumMaj#containsQuorumpublic boolean containsQuorum(HashSet`<Long>` set){    // half = 1    return (set.size() > half);}

票的外围数据结构 : (epoch, zxid, myid),上返回后一一比拟,投票还有一个 logicalclock,能够不在同一个周期中进行选举

选举算法
sendQueue -> WorkerSender -> queueSendMap -> SendWorker -> RecvWorker -> recvQueue -> WorkerReceive -> recvqueue
归票的时候是会从 recvqueue 外面拿票

11、数据同步

选举结束leader节点进入LEADING状态,follower节点将进入FOLLOWING状态,此时集群中节点将数据进行同步操作,以保证数据一致性。只有
数据同步实现,能力对外提供服务

leader :
会启动一个 LearnerCnxAcceptor,用来监听follower对其的网络连接,针对每一个 follower的连贯都会创立一个 LearnerHandler线程服务于他

follower :
connectToLeader : 和leader建设网络连接
syncWithLeader 同步数据

次要要做的事件是三个:
1、leader 向 follower 发送 LEADERINFO 信息,告知 follower 新的 epoch 值
follower 接管解析 LEADERINFO 信息,若 new epoch 值大于 current accepted epoch 值则更新 acceptedEpoch
follower 向 leader 发送 ACKEPOCH 信息,反馈 leader 已收到新的 epoch 值,并附带 follower 节点的 last zxid
2、LearnerHandler 中 leader 在收到过半的 ACKEPOCH 信息之后将进入数据同步阶段

1和2其实在做一个事件,就是咱们版本一样了哈,能够同步数据了

3、同步策略
全量同步SNAP
若 follower 的 peerLastZxid 小于 leader 的 minCommittedLog 或者 leader 节点上不存在提案缓存队列时,将采纳 SNAP 全量同步形式。 该模式下 leader 首先会向 follower 发送 SNAP 报文,随后从内存数据库中获取全量数据序列化传输给 follower, follower 在接管全量数据后会进行反序列化加载到内存数据库中

TRUNC(回滚同步)
若 follower 的 peerLastZxid 大于 leader 的 maxCommittedLog,则告知 follower 回滚至 maxCommittedLog; 该场景能够认为是 TRUNC+DIFF 的简化模式

RUNC+DIFF(先回滚再差异化同步)
在上文 DIFF 差异化同步时会存在一个非凡场景就是 尽管 follower 的 peerLastZxid 介于 maxCommittedLog, minCommittedLog 两者之间,然而 follower 的 peerLastZxid 在 leader 节点中不存在; 此时 leader 需告知 follower 先回滚到 peerLastZxid 的前一个 zxid, 回滚后再进行差异化同步

最初UPTODATE对外服务

12、链条化解决

org.apache.zookeeper.server.NIOServerCnxn#readRequest,NIO服务端读取到客户端申请,甩到processor链条中进行解决

leader :
makeLeader -> new LeaderZooKeeperServer -> 创立一个责任链链条

PreRequestProcessor -> ProposalRequestProcessor 之后分两条分支
1、CommitProcessor -> ToBeAppliedRequestProcessor -> FinalRequestProcessor
2、SyncRequestprocessor -> AckRequestProcessor

follower :
两条线

存在意义是,其实客户端也是能够将申请发送给follower的,zk为了保障分布式数据一致性,应用ZAB协定,在客户端发动一次写申请的时候,假如申请到的是
follower,follower不会间接解决这个申请,而是转发给leader,由leader发动投票决定申请是最终是否执行胜利

FollowerRequestProcessor -> CommitProcessor -> FinalRequestProcessor 这个链条有分为两个
如果client申请间接打到follower上,会转发给leader,走的是FollowerRequestProcessor 之后的链条
如果是leader的commit申请走的是 CommitProcessor -> FinalRequestProcessor的链条

SyncRequestProcessor -> SendAckRequestProcessor 这个链条是承受leader的proposal申请

13、Watcher机制

大抵流程是Client向ZK中注册Watcher,如果注册胜利的话,会将对应的Watcher存储在本地。当ZK服务器端触发Watcher事件之后,会向客户端发送告诉,
客户端会从ClientWatchManager中取出对应的Watcher进行回调

1、客户端注册Watcher
2、服务端解决Watcher
3、客户端回调Watcher

客户的Watcher是由 ZKWatchManager 治理的
创立zookeeper客户端的时候给一个默认的Watcher
getData,getChildren,exist

ServerCnxn是一个要害,默认实现是 NIOServerCnxn ,代表了一个客户端和服务的连贯,ServerCnxn 实现了Watcher接口

FinalRequestProcessor#processRequest
比如说getData的时候,如果加了Watcher的话,其实传递过去就是一个boolean值,是否加了Watcher,如果服务端会加到WatchManager中
在setData的时候,就是进行 dataWatches.triggerWatch(path,EventType.NodeDataChanged)传递回去,其实是通过 NIOServerCnxn 传递回去的
NIOServerCnxn 也是一个网络连接,间接发送给客户端

客户端间接应用EventThread进行解决,其实也是在getData的时候客户端判断有watcher,在本地保留

org.apache.zookeeper.ClientCnxn#finishPacket// TODO 请解释分明这里,其实很简略,就是比如说我要注册一个Watcher对吧,怎么也得让我这次注册胜利之后,// TODO 才有可能是服务端告诉吧,所以这就是要做的注册Watcherif (p.watchRegistration != null) {    // 最初回到WatchRegistration将对应的Watcher注册到对应的Map<String,Set<Watcher>>中    p.watchRegistration.register(p.replyHeader.getErr());}

org.apache.zookeeper.ClientCnxn.SendThread#readResponse 如果有Watche告诉,会放入到
EventThread#queueEvent 封装 WatcherSetEventPair(就是从ZKWatchManager中移除,放入到他外面的watchers中)的,
而后将WatcherSetEventPair 放入 waitingEvents 队列中,EventThread 线程 run 遍历WatcherSetEventPair中的watchers,调用process办法

Watcher的个性
1、一次性 : 物理是客户端还是服务端,一旦Watcher触发、都会将其从存储中移除
2、客户端串行执行 : 出啊性同步执行的过程,千万不要因为一个Watcher而影响整个客户端回调Watcher
3、轻量 : WatchedEvent是告诉机制中最小的告诉单元,只蕴含三局部内容 : 告诉状态、工夫类型、节点门路。而不会将节点的内容以告诉的形式告知
客户端,而是须要客户端接管到告诉之后,被动去服务端获取数据

DataTree
ConcurrentHashMap<String, DataNode> nodes = new ConcurrentHashMap<String, DataNode>();
DataNode 是由层级children的

SASL 次要利用于跨节点通信时的认证与数据加密

14、Follower申请转发

如感兴趣,点赞加关注哦!