一、整体框架

Zookeeper介绍

ZooKeeper是一个分布式的,开放源码的应用程序协调服务。

Zookeeper使用场景

ZooKeeper是一个分布式应用程序协调服务,分布式应用程序可以基于它实现分布式同步服务,配置维护、命名服务、服务发现、分布式锁等。

Zookeeper特性

顺序一致性:从同一个客户端发起的事务请求,将会严格按照其发起顺序被应用到zookeeper中

原子性:所有事物请求的处理结果在整个集群中所有机器上的应用情况是一致的,要么整个集群中所有机器都成功应用了某一事务,要么都没有应用某一事务,不会出现集群中部分机器应用了事务,另一部分没有应用的情况。

单一视图:无论客户端连接的是哪个zookeeper服务端,其获取的服务端数据模型都是一致的。

可靠性:一旦服务端成功的应用了一个事务,并完成对客户端的响应,那么该事务所引起的服务端状态变更将会一直保留下来,直到有另一个事务又对其进行了改变。

实时性:一旦服务端成功的应用了一个事物,那客户端立刻能看到变更后的状态

zookeeper缺点

zookeeper不是为高可用性设计:zookeeper集群只能有一个master,Zookeeper 集群中的Master一旦宕机,该集群就要进行 Leader 的选举,选举区间导致服务不可用;或者有一半的节点不可用,则zookeeper集群就无法对外提供服务,故不能保证服务可用性。

zookeeper的性能是有限:典型的zookeeper的tps大概是一万多,无法覆盖系统内部每天动辄几十亿次的调用。每次请求都去zookeeper获取业务系统master信息不可能。

业内其他方案对比

分布式协调服务方案 优点 cap Consistency(一致性)Availability(可用性)Partition tolerance(分区容错性) 多语言能力 安全检查 Watch机制 一致性算法
Zookeeper 1.功能强大,可用于服务发现等 2.提供watcher机制能实时获取服务提供者的状态 3.JAVA体系内等框架支持全面 CP JAVA实现,跨语言支持较弱 ACL 支持服务器端推送节点变化 ZAB协议(基于Fast Paxos)
Etcd 1.简单易用,不需要集成sdk 2.可配置性强 CP Go语言实现,支持GRPC及HPPT的多语言调用 Https 长轮询的方式来实现变化的感知) Raft
Consul 1.简单易用,不需要集成sdk 2.自带健康检查 3.支持多数据中心 CP Go语言实现,支持Rest和DNS服务API Https 长轮询的方式来实现变化的感知) Raft

ZooKeeper如何解决分布式一致性问题

ZooKeeper是以Fast Paxos算法为基础的,Paxos 算法存在活锁的问题,即当有多个proposer交错提交时,有可能互相排斥导致没有一个proposer能提交成功,而Fast Paxos作了一些优化,通过选举产生一个leader (领导者),同步数据,并只有leader才能提交提案,并从而确保原子广播。

ZAB协议

Zookeeper的原子广播,这个机制保证了各个Server之间的同步。实现这个机制的协议,我们称之为Zab协议。

Zookeeper的ZAB原子广播协议,是一种支持崩溃恢复的原子广播协议。它依赖它选举出一个Leader,同步数据,通过Leader按顺序广播修改内容,并且能从故障中快速恢复正常状态。

ZAB协议有两种模式,它们分别是恢复模式和广播模式。

恢复模式下主要涉及到leader选举及数据同步:当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数server完成了和leader的状态同步以后(保证leader和server具有相同的系统状态),恢复模式就结束了。

一旦Leader已经和多数的Follower状态同步完成后,集群就可以开始广播消息了,即进入广播状态。广播模式极其类似于分布式事务中的2pc(two-phrase commit 两阶段提交):即Leader按照FIFO队列提起一个决议,由Followers进行投票,Leader汇总Followers的投票,对投票结果进行计算决定是否通过该提案,如果通过执行该提案,Leader和Followers更新数据,否则什么也不做。

在zookeeper集群中,一个新Server加入ZooKeeper服务中,它会在恢复模式下启动,发现Leader,并和Leader进行状态同步。待到同步结束,它也参与消息广播。ZooKeeper服务一直维持在广播状态,直到Leader崩溃了或者Leader失去了大部分的Followers支持,才再次进入恢复模式。

ZAB协议 VS RAFT协议

协议 leader选举 数据恢复 日志文件
ZAB协议 Zab基于Epoch和zxid选举leader Zk 则在新leader 选举之后,有一个 恢复阶段 做数据同步 Zab 是一种 fuzzy snapshot 机制,Zk snapshot 的时候,会记录下来当前的 zxid,恢复的时候,从该 snapshot 开始,replay 所有的 log 实现最终数据的正确性
RAFT协议 Raft 基于 term 选举leader Raft 没有固定在某个特定的阶段做时间恢复,通过每个节点的 AppendEntry RPC 分别做数据同步 Raft 的 snapshot 机制对应了某一个时刻完成的状态机的数据

ZAB协议如何基于Paxos协议演进

Paxos是解决分布式共识(分布式一致性)的协议,是一个达成分布式共识的通用算法。很少有人直接使用paxos算法,都会经过一定的简化、优化。

Paxos算法在出现竞争的情况下,其收敛速度很慢,甚至可能出现活锁问题:由于并不存在Leader角色, 当有三个及三个以上的提案发出请求请求后,出现新轮次可以不断抢占旧轮次,导致很难有一个提案收到半数以上的回复而不断地执行第一阶段的协议。

Paxos算法存在全序问题:如果消息a在消息b之前发送,则所有Server应该看到相同的顺序结果,但Paxos并不保证这一点。

为了避免竞争加快收敛的速度,在改进的Paxos算法引入了一个Leader角色,只有Leader可以提出议案,从而避免了竞争使得算法能够快速地收敛而趋于一致。同时为了保证Leader的健壮性,又引入了Leader选举,再考虑到同步的阶段,渐渐的改进的Paxos算法和ZAB协议很相似了。同时ZAB协议还引入了ZXID(全局单调递增的提案唯一ID)和利用TCP的FIFO,保证了提案的原子顺序性。

Zab协议两个重要的要求是从崩溃中快速恢复的能力和提供处理大量的客户端请求。Zab能处理大量并发请求且按FIFO顺序队列的提案。

Zookeeper系统架构

架构层级模型:
zookeeper架构
zk生命流程模型:
zookeeper架构
Zookeeper集群模式下的系统模型,其中主要分为三个阶段。

1、当Zookeeper集群在启动时,或者当leader节点出现网络中断、崩溃等情况时,Zookeeper就会进入恢复模式并选举产生新的 Leader。各个节点把自己的选票发送给集群中的所有节点,当集群中某个节点收到半数以上投个某个角色的选票后,确认自己的角色。
2、在zookeeper选举成功后,准Leader和其他节点之间需要进行数据同步,以准Leader节点是数据为基准进行同步,完成这些后才对外提供服务。
3、在Zookeeper选举成功,并且完成数据恢复后,准Leader变成为了正式Leader,便可对外提供服务了。客户端请求分为查询请求和事务请求;对于事务请求客户端可能发送给Leader,Follower或Observer。Follower或Observer接收到事务请求后会将请求交给Leader处理。Leader进行2阶段的提案(Proposal)及提交(Commit),完成一轮事务请求,并返回客户端结果。
与此同时,
4、上述模型中,每一个client与zk节点建立链接后,zk节点会为每一个节点维护会一个全局唯一的会话。
5、在client与节点建立会话后,client便可以发起事务及非事务请求,每发生一次一个节点的请求后,client及zk节点之间便会建立一次Watcher机制,类似分布式数据的发布/订阅功能。

基于上述5个步骤,下文核心功能拆解中,将做详细的讲解。
上述工作模型中,Zookeeper中的角色主要有三类,领导者(Leader)、学习者(Learner)、客户端(Client),以下各个角色的工作职责。

角色 工作职责
领导者(Leader) Leader 服务器是整个 ZooKeeper 集群工作机制中的核心,其主要工作有以下两个:1、事务请求的唯一调度和处理者,保证集群事务处理的顺序性。2、集群内部各服务器的调度者。
学习者(Learner) 跟随者(Follower) 1、处理客户端非事务请求,转发事务请求给 Leader 服务器。2、参与事务请求 Proposal 的投票。3、参与 Leader 选举投票。
学习者(Learner) 观察者(ObServer) ObServer接收客户端连接,将写请求转发给leader节点,但ObServer不参与投票,只填报leader的状态。ObServer目的是为了扩展系统,提高读取速度。
客户端(client) 请求发起方
Locking 节点启动后未明确自己的角色时的状态

Zookeeper系统模型

数据节点

Zookeeper中的数据存储是按照树性结构进行存储的,数据节点信息我们称之为ZNode,ZNode是Zookeeper中数据的最小单元,每个ZNode都可以保存数据,同时还可以挂载子节点,因此构成了一个层次化的命名空间,称为树。每一个znode默认能够存储1MB的数据,每个ZNode都可以通过其路径唯一标识。
zookeeper架构
node中包含有:数据(data),访问权限(acl),子节点引用(clild),元数据(stat)。

  • data:Znode存储的数据信息
  • acl:记录Znode的访问权限,即哪些人或哪些IP可以访问本节点
  • child:当前节点的子节点引用,类似于二叉树的左孩子右孩子
  • stat:包含Znode的各种元数据,比如事务ID、版本号、时间戳、大小等等

节点特性

在Zookeeper中,每个数据节点都是由生命周期的,类型不同则会不同的生命周期,节点类型可以分为持久节点(PERSISTENT)、临时节点(EPHEMERAL)、顺序节点(SEQUENTIAL)三大类,可以通过组合生成如下四种类型节点

  1. 持久节点(PERSISTENT): 节点创建后便一直存在于Zookeeper服务器上,直到有删除操作来主动清楚该节点。
  2. 持久顺序节点(PERSISTENT_SEQUENTIAL): 相比持久节点,其新增了顺序特性,每个父节点都会为它的第一级子节点维护一份顺序,用于记录每个子节点创建的先后顺序。在创建节点时,会自动添加一个数字后缀,作为新的节点名,该数字后缀的上限是整形的最大值。
  3. 临时节点(EPEMERAL): 临时节点的生命周期与客户端会话绑定,客户端失效,节点会被自动清理。同时,Zookeeper规定不能基于临时节点来创建子节点,即临时节点只能作为叶子节点。
  4. 临时顺序节点(EPEMERAL_SEQUENTIAL): 在临时节点的基础添加了顺序特性。
    每个节点除了存储数据外,还存储了节点本身的一些状态信息,可通过get命令获取。

版本–保证分布式数据原子性操作

每个数据节点都具有三种类型的版本信息,对数据节点的任何更新操作都会引起版本号的变化。

  • version-- 当前数据节点数据内容的版本号
  • cversion-- 当前数据子节点的版本号
  • aversion-- 当前数据节点ACL变更版本号
    上述各版本号都是表示修改次数,如version为1表示对数据节点的内容变更了一次。即使前后两次变更并没有改变数据内容,version的值仍然会改变。version可以用于写入验证,类似于CAS。

ACL–保障数据的安全

Zookeeper内部存储了分布式系统运行时状态的元数据,这些元数据会直接影响基于Zookeeper进行构造的分布式系统的运行状态,如何保障系统中数据的安全,从而避免因误操作而带来的数据随意变更而导致的数据库异常十分重要,Zookeeper提供了一套完善的ACL权限控制机制来保障数据的安全。

Zookeeper源码模块

zookeeper架构
zookeeper源码模块主要包括,Client、Server、Common模块。Client模块为客户端连接相关配置。Common模块包括 Util 类和通用模型。

Server是Zookeeper的主要实现业务实现,包括以下功能:

  • quorum模块:集群选举流程、数据同步、角色管理、会话管理
  • RequestProcessor责任链:处理事务请求及查询请求责任链
  • watch模块:维护客户端watch信息
  • Auth权限:集群调用权限管理
  • persistence持久化:负责事务日志、快照数据的维护及存储

二、核心功能拆解

1、服务器Leader选举

当Zookeeper集群在启动时,或者当leader节点出现网络中断、崩溃等情况时,Zookeeper就会进入恢复模式并选举产生新的 Leader。

服务器选举相关术语:

术语 描述
SID Server ID,用来唯一标识一台Zookeeper集群中的机器,全局唯一,与myid一致
ZXID 事务ID,用来唯一标识一次服务器状态的变更,在同一时刻,集群中每一台机器的ZXID不一定完全一致
Vote 当集群中的机器发现自己无法检测到Leader机器的时候,就会尝试开始投票
Quorum 半数以上机器,quorum >= (n/2+1)

服务器选举流程

zookeeper架构
Zookeeper选举主要涉及到如下几个步骤:

1、初始化选票:第一次选举前,每台服务器自增选举轮次,每台服务器都会将自己推举为leader

2、发送初始化选票:服务器会对所有参与选举的server端发送自己的选票

3、接收外部选票Notification

如果服务器没有接收到外部投票,则重新发送自己的投票,否则检查连接,没有连接的话重新和其他服务器创建连接,如果已经建立则重新发送投票

如果服务器接收到外部投票(外部接口投票是Locking、leader、follower),则会判断选举轮次

  • 如果外部投票的轮次大于内部投票,则立即更新自己的选举轮次;并清空所有已经收到的投票,然后使用初始化的投票来进行pk,并把内部投票发送出去
  • 外部投票的轮次小于内部投票,服务器会直接忽略掉该外部投票,返回步骤2
  • 外部投票的选举轮次和内部投票一致,开始pk选票
    4、服务器选票pk: 投票PK因素考虑优先级:1.选举轮次 2.ZXID 3.SID,谁越大选谁

5、服务器根据投票匹配结果,变更投票,并将变更发送出去

6、选票归档:服务器按照SID来区分,记录当前服务器在本轮次的leader选举中收到的所有外部投票。

7、根据过半原则统计投票,更新服务器状态:如果确定有过半的服务器认可了SID一组下的投票,则更新当前服务器的状态,确定是自身是leader还是follower;否则终止投票,继续接收和发送投票

2、数据恢复

在zookeeper选举成功后,Leader和其他节点之间需要进行数据恢复,主要涉及到的功能包括是确定选举周期轮次及同步事务日志,完成这些后才对外提供服务。
zookeeper架构
Leader及Leaner服务器同步需要处理的逻辑如下:

Leader服务器同步流程
1、重新加载快照和事务日志数据。
2、启动Follower接收器LearnerCnxAcceptor:接收所有非Leader服务器的连接请求,用于集群间非选举通信,事务等请求。
3、Leader解析Learner消息,通过Learner消息消息,计算新的epoch轮次,以确定集群的epoch轮次。
4、Leader向其他服务器发送leader状态(epoch轮次消息),Follower变更本地轮次后,发送ACKEPOCH回复消息,回复消息包括事务编号Zxid。
5、Leader接收过半Leaner的ACKEPOCH回复消息,开始进行主从数据同步。
5、每个Learner完成了和Leader服务器的同步后,Leader会发送一个NEWLEADER消息给Learner服务器,Learner服务器会响应一个ACK消息给Leader,Leader收到一半参与选举的服务器回复ACK消息之后,Leader服务器会启动请求链,同时Leader会发送一个UPTODATE消息给同步好的Leaner服务器,表示同步完成,可对外提供服务了。

Leaner服务器同步流程
1、Leader首先会主动连接注册到Leader服务器,并发送FOLLOWERINFO消息
2、一旦tcp连接上了,会接收到leader服务器发送的LEADERINFO消息,更新本地节点的epoch轮次,并回复ACKEPOCH消息,回复消息包括follower本地事务编号Zxid。
3.、leader会与Leader进行数据同步
4、数据同步完成,启动Leaner服务,初始化请求链,对外提供服务。

选举轮次
由于ZooKeeper的事务ID(zxId),需要有唯一性保证,同时要保证集群整体顺序性。ZXID的计算公式如下:
ZXID是一个长度64位的数字,其中低32位是按照数字递增,即每次客户端发起一个proposal,低32位的数字简单加1。高32位是leader周期轮次epoch编号。
故在数据恢复工程中,需要确定集群的轮次epoch编号,并且该编号需要不断递增。
轮次epoch计算逻辑为:leaner会等待过半的Follower发送自己的epoch信息,如果Follower的epoch比Leader的epoch大,则epoch_of_leader(leader周期轮次) = epoch_of_learner(learner周期的最大轮次) + 1,这样Leader便可以确定集群 的epoch了。

同步方式
上述过程中,数据同步主要以Leader的数据为基准进行同步。在数据同步过程中,由于Follwer本地的事务ID有可能超前、也有可能一直落后,故涉及到不同的同步方式。同步方式如下:

1.强制快照同步
可设置forceSnapSync为true,用于测试使用,默认为false
2.不需要同步
主从最大zxid一致,不需要同步,仅需要发送一个DIFF消息即可
3.回滚同步
learner服务器zxid(peerLastZxid)大于leader服务器zxid(lastProcessedZxid),并且peerLastZxid>0,此时需要从服务器丢弃大于lastProcessedZxid的事务日志,会发送TRUNC消息给learner服务器进行本地事务回滚;

4.差异化同步
learner服务器peerLastZxid可能在leader服务器事务日志最大的zxid(maxCommittedLog)及最小的zxid(minCommittedLog)之间,则需要进行差异化同步。

  • peerLastZxid位于minCommittedLog和 maxCommittedLog之间,但peerLastZxid找不到这个范围内的值,则先回滚到离peerLastZxid最近的前一条消息prevProposalZxid,然后再进行(prevProposalZxid, maxZxid]之间的zxid同步
  • peerLastZxid位于minCommittedLog和 maxCommittedLog之间,且peerLastZxid真实存在,则只需要进行(peerLaxtZxid, maxZxid]之间的zxid同步
    5.全量同步
    如果peerLastZxid小于以上情况,则进行全量同步,该方法返回true,回到LearnerHandler.run,会发送SNAP消息,并将整个ZKDatabase序列化,发送出去之后会开启线程异步发送queuedPackets队列消息,等待learner服务器的同步完成ACK消息。

3、请求处理链

在Zookeeper选举成功,并且完成数据恢复后,便可对外提供服务了。
客户端请求处理分为查询请求和事务请求;其中事务请求主要可分为请求接收,会话创建,预处理,事务处理,事务应用和会话响应6个阶段。
事务请求客户端选择连接的服务端可能是Leader,Follower或Observer。Follower或Observer接收到事务请求后会将请求交给Leader处理。
Leader进行2阶段的提案(Proposal)及提交(Commit),完成一轮事务请求。
Leader及Follower的请求处理链
Leader的请求处理链:
zookeeper架构
Leader在集群中主要完成

第一条 RequestProcessor 链:主要处理事务请求的提案阶段逻辑 PrepRequestProcessor --> ProposalRequestProcessor --> SyncRequestProcessor --> AckRequestProcessor
PreRequestProcessor : 创建和修改客户端请求Request,并转变成提案Proposal
ProposalRequestProcessor : 提交Proposal给各个 Follower包括Leader自己,同时转给SyncRequestProcessor及CommitProcessor
SynRequestProcessor: 主要是将 Request 提案持久化到事务日志里面,并不间断的生成快照文件
AckRequestProcessor: 主要完成针对Request提案的 ACK 回复,对于Leader是自己回复自己ACK。
LearnerHandler:处理来自集群的ACK消息,ACK消息过半后,进行Commit阶段,发送给集群节点进行commit提交

第二条 RequestProcessor 链:主要处理事务请求的提交阶段逻辑 CommitProcessor --> ToBeAppliedRequestProcessor --> FinalRequestProcessor
CommitProcessor : 对于非事务请求直接交由下个FinalRequestProcessor ;提案请求经过集群中的过半同意后,commit阶段提交给ToBeAppliedRequestProcessor
ToBeAppliedRequestProcessor: 这个处理链是事务请求处理时经过的最后一个RequestProcessor,其维护了toBeApplied列表(存储过半ACK同意的提案), 只有经过 FinalRequestProcessor 处理过的 Request 才会在 toBeApplied 中进行删除
FinalRequestProcessor: 普通请求直接查询内存数据库并返回结果,事务请求是在经过 SyncRequestProcessor 持久化到事务日志后, commit阶段将数据提交到内存数据库里面

Follower的请求处理链
zookeeper架构
Follower在集群中主要完成
第一条 RequestProcessor链:主要处理来自Leader的提案请求逻辑 FollowerRequestProcessor --> SyncRequestProcessor --> SendAckRequestProcesso
FollowerRequestProcessor: 若是普通请求交由下个 RequestProcessor;而若涉及事务的操作, 则交由 Follower 提交给 leader
SynRequestProcessor: Follwer接收来自Leader的事务提案请求,将提案持久化到事务日志里面,并不间断的生成快照文件
SendAckRequestProcessor: 完成针对Leader的提案的 ACK 回复,发送ACK消息给Leader

第二条 RequestProcessor链:主要处理来自Leader提交阶段逻辑 CommitProcessor --> FinalRequestProcessor
Follower接收来自Leader的事务提交(commit)请求,交由CommitProcessor
CommitProcessor: 若是普通请求直接交给FinalRequestProcessor ,提案请求经过集群中的过半同意后,commit阶段提交给FinalRequestProcessor
FinalRequestProcessor: 普通请求直接查询内存数据库,事务请求是在经过 SyncRequestProcessor 持久化到事务日志后, commit阶段将数据提交到内存数据库里面

4、会话管理

客户端与服务端之间任何交互操作都与会话息息相关,如临时节点的生命周期、客户端请求的顺序执行、Watcher通知机制等。Zookeeper的连接与会话就是客户端通过实例化Zookeeper对象来实现客户端与服务端创建并保持TCP连接的过程。
会话状态
zookeeper架构
在Zookeeper客户端与服务端成功完成连接创建后,就创建了一个会话,Zookeeper会话在整个运行期间的生命周期中,会在不同的会话状态中之间进行切换,这些状态可以分为CONNECTING、CONNECTED、RECONNECTING、RECONNECTED、CLOSE等。

一旦客户端开始创建Zookeeper对象,那么客户端状态就会变成CONNECTING状态,同时客户端开始尝试连接服务端,连接成功后,客户端状态变为CONNECTED,通常情况下,由于断网或其他原因,客户端与服务端之间会出现断开情况,一旦碰到这种情况,Zookeeper客户端会自动进行重连服务,同时客户端状态再次变成CONNCTING,直到重新连上服务端后,状态又变为CONNECTED,在通常情况下,客户端的状态总是介于CONNECTING和CONNECTED之间。但是,如果出现诸如会话超时、权限检查或是客户端主动退出程序等情况,客户端的状态就会直接变更为CLOSE状态。

会话创建
在每个服务器启动时,都会初始化一个会话管理器,同时也会初始化当前服务器的sessionId(基准sessionId),以后每创建一个客户端连接,它的sessionId只需要在基准sessionId的基础上递增就可以。
由于sessionId是zookeeper会话的重要标识,必须保持全局唯一。
会话管理-分桶策略
Zookeeper的会话管理主要是通过leader节点的SessionTracker来负责,其采用了分桶策略(将类似的会话放在同一区块中进行管理)进行管理,以便Zookeeper对会话进行不同区块的隔离处理以及同一区块的统一处理。
zookeeper架构
为了保持客户端会话的有效性,客户端在会话超时时间内需要向服务端发送PING请求来保持有效性。服务端接收到PING请求后会重新计算当前会话的过期时间,**会话。

5、服务端处理 Watcher 流程

ZooKeeper是用来协调(同步)分布式进程的服务,多个分布式进程通过ZooKeeper提供的API来操作共享的ZooKeeper内存数据对象ZNode来达成某种一致的行为或结果,这种模式本质上是基于状态共享的并发模型。ZooKeeper实现这些分布式进程的状态(ZNode的Data、Children)共享时,基于性能的考虑采用了类似的异步非阻塞的主动通知模式即Watch机制,使得分布式进程之间的“共享状态通信”更加实时高效。

Zookeeper的Watcher机制主要包括客户端线程、客户端Watcher管理器、Zookeeper服务器三部分。客户端在向Zookeeper服务器注册之后,会将Watcher对象存储在客户端的Watcher管理器当中。当Zookeeper服务器触发Watcher事件后,会向客户端发送通知,客户端线程从Watcher管理器中取出对应的Watcher对象来执行回调逻辑。
zookeeper架构
客户端注册Watcher
zookeeper架构
在 ZooKeeper 中,Packet 是一个最小的通信协议单元,即数据包。Pakcet 用于进行客户端与服务端之间的网络传输,任何需要传输的对象都需要包装成一个 Packet 对象。
在客户端ClientCnxn 中 Watch注册信息会被封装到 Pakcet中,然后将Packet中的Header 和 request 发送到服务端。
随后,ClientCnxn接收来自服务端的响应Response,根据请求编号在内存中找到对应的Packet,并取出对应的 Watcher 并注册到 Watcher管理器中去。
服务端处理 Watcher 流程
zookeeper架构
对于客户端事务请求,会有Leader产生事务提案,最终交由FinalRequestProcessor责任链更新DataTree树节点。在更新完成DataTree后调用Watch管理中节点对于的Watch客户端,将给监听当前节点的客户端发送节点ZNODE变更消息,自此客户端进入到本地进行回调。

Zookeeper Watcher如何确保注册只能确保一次消费

无论是服务端还是客户端,一旦一个 Watcher 被触发,ZooKeeper 都会将其从相应的存储中移除。因此,开发人员在 Watcher 的使用上要记住的一点是需要反复注册。这样的设计有效地减轻了服务端的压力。如果注册一个 Watcher 之后一直有效,那么针对那些更新非常频繁的节点,服务端会不断地向客户端发送事件通知,这无论对于网络还是服务端性能的影响都非常大。

watchr机制的轻量级设计

WatchedEvent 是 ZooKeeper 整个 Watcher 通知机制的最小通知单元,这个数据结构中只包含三部分的内容:通知状态、事件类型和节点路径。也就是说,Watcher 通知非常简单,只会告诉客户端发生了事件,而不会说明事件的具体内容。例如针对 NodeDataChanged 事件,ZooKeeper 的 Watcher 只会通知客户指定数据节点的数据内容发生了变更,而对于原始数据以及变更后的新数据都无法从这个事件中直接获取到,而是需要客户端主动重新去获取数据,这也是 ZooKeeper 的 Watcher 机制的一个非常重要的特性。另外,客户端向服务端注册 Watcher 的时候,并不会把客户端真实的 Watcher 对象传递到服务端,仅仅只是在客户端请求中使用 boolean 类型属性进行了标记,同时服务端也仅仅只是保存了当前连接的 ServerCnxn 对象。这样轻量级的 Watcher 机制设计,在网络开销和服务端内存开销上都是非常廉价的。

6、数据模型

在Zookeeper中数据分为两部分:内存数据(ZKDatabase)和磁盘数据(事务日志及快照文件)。
上述事务请求过程中,提案阶段(Proposal),数据会持久化到事务日志,并不定期生成快照文件;提交阶段(Commit),会将数据更新到内存数据。

内存数据
Zookeeper的内存数据库为ZKDatabase,负责管理Zookeeper的所有会话,DataTree存储和事务日志。它会定时向磁盘dump快照数据。
DataTree:Zookeeper的数据模型是一棵树,DataTree是内存数据存储的核心,代表了内存中最新的一份完整数据,包括所有的节点路径,节点数据和ACL信息,对应watches等。
DataNode:数据存储的最小单元,即为树的节点ZNode。包含节点的数据内容,节点状态,子节点列表,ACL权限,以及对子节点的操作接口等。

事务日志
ZooKeeper集群中的每个服务器节点每次接收到事务请求时,都会先将这次请求发送给leader,leader会将事务请求转换为带有唯一ZXID的事务提案,然后leader会对这次提案广播出去以便进行协调。当节点收到提案请求后,会将提案数据记录日志中去,这便是事务日志。
事务日志包括信息:事务头(包含checksum)、事务体(会话创建事务、节点创建事务、节点删除事务、节点数据更新事务)。

快照日志
zookeeper会定时会将内存数据全量dump到磁盘,形成快照数据,记录某个时刻所有节点的快照信息。

7、序列化及通讯协议

Jute
在网络传输时,传输的是二进制数据,所以发送端需要将序列化对象转变为二进制数据,也就是序列化过程。接收端需要将二进制数据转化为序列化对象,也就是反序列化过程。在序列化和反序列化过程中,需要定义一种对数据相互转变的一致性协议,也就是序列化协议。zookeeper使用Jute作为序列化组件。
Jute用于Zookeeper进行网络数据传输和本地磁盘数据存储的序列化和反序列化工作。大体可以分为 4 步:

  • 实体类需要实现 Record 接口的 serialize 和 deserialize 方法。
    构建一个序列化器 ByteOutputArchive。
  • 调用实体类的 serialize 方法,将对象序列化到指定 tag 中去。
  • 调用实体类的 deserialize 方法,从指定的 tag 中反序列化出数据内容。

通信协议
基于 TCP/IP 协议,ZooKeeper 实现了自己的通信协议来完成客户端与服务端(单独端口)、服务端与服务端(单独端口)之间的网络通信。ZooKeeper 通信协议整体上的设计非常简单,对于请求,主要包含请求头和请求体,而对于响应,则主要包含响应头和响应体。

  • len + 请求头 + 请求体
  • len + 响应头 + 响应体

8、其他

zookeeper的常用客户端:zookeeper原生client、Apache Curator;这块也需要在源码解析中进行详细解析。

三、 源码解析思路

鉴于上述模块介绍,想必大家对zookeeper已经有了一个初步的认识,zookeeper生命周期流程:1、选举流程,2、数据恢复流程,3、事务请求流程、4、以及客户端与服务器建立客户端会话和watch机制;基础技术:zk树型结构、存储模型、序列化、通信协议。

相关文章:

  • 2021-06-05
  • 2021-08-18
  • 2021-11-15
  • 2021-09-07
  • 2021-09-10
猜你喜欢
  • 2022-12-23
  • 2021-09-08
  • 2021-10-14
  • 2021-10-14
  • 2021-06-10
相关资源
相似解决方案