下述各zookeeper机制的java客户端实践参考zookeeper java客户端之curator详解。
官方文档http://zookeeper.apache.org/doc/current/zookeeperOver.html、http://zookeeper.apache.org/doc/current/zookeeperInternals.html描述了部分关于zk的内部工作机制,但是并不够友好和详细。
据官网介绍,ZooKeeper是一个用于提供配置信息、命名服务、分布式协调以及分组服务的中心化服务,它们是分布式应用所必需的的。从实际应用来看,zookeeper是最广泛使用的分布式协调服务,包括dubbo、kafka、hadoop、es-job等都依赖于zookeeper提供的分布式协调和注册服务。其他用于提供注册服务的中间件还包括consul以及etcd、eureka,但都不及zookeeper广泛。
官网:https://zookeeper.apache.org/,https://zookeeper.apache.org/doc/r3.4.14/
在zookeeper中,节点分为下列几种角色:
在一个zookeeper集群,各节点之间的交互如下所示:

注:几乎所有现代基于分布式架构的中间件都是采用类似做法,例如kafka、es等。
从上可知,所有请求均由客户端发起,它可能是本地zkCli或java客户端。 各角色详细职责如下。
leader的职责包括:

follower的主要职责为:
向Leader发送请求;接收Leader的消息并进行处理;接收Zookeeper Client的请求,如果为写清求,转发给Leader进行处理Follower的工作流程简图如下所示,在实际实现中,Follower是通过5个线程来实现功能的。

各种消息的含义如下:
PING:心跳消息。
PROPOSAL:Leader发起的提案,要求Follower投票。
COMMIT:服务器端最新一次提案的信息。
UPTODATE:表明同步完成。
REVALIDATE:根据Leader的REVALIDATE结果,关闭待revalidate的session还是允许其接受消息。
SYNC:返回SYNC结果到客户端,这个消息最初由客户端发起,用来强制得到最新的更新。
虽然zookeeper采用的是文件系统存储机制,但是所有数据数据都存储于内存中。其对外提供的视图类似于Unix文件系统。树的根Znode节点相当于Unix文件系统的根路径。

zk中的节点称之为znode(也叫data register,也就是存储数据的文件夹),按其生命周期的长短可以分为持久结点(PERSISTENT)和临时结点(EPHEMERAL);在创建时还可选择是否由Zookeeper服务端在其路径后添加一串序号用来区分同一个父结点下多个结点创建的先后顺序。
经过组合就有以下4种Znode结点类型:

zookeeper通过下列机制实现一致性保证:
» 所有更新请求顺序进行,来自同一个client的更新请求按其发送顺序依次执行
» 数据更新原子性,一次数据更新要么成功,要么失败
» 全局唯一数据视图,client无论连接到哪个server,数据视图都是一致的,基于所有写请求全部由leader完成,然后同步实现
» 实时性,在一定事件范围内,client能读到最新数据
» Zookeeper是一个由多个server组成的集群
» 一个leader,多个follower
» 每个server保存一份数据副本
» 全局数据一致
» 分布式读写
» 更新请求全部转发由leader完成,并在成功后同步给follower
客户端写请求的过程如下:

其过程为:
由于每次的请求都需要转发给leader并进行投票处理,所以zookeeper并不适合于写密集型的场景,例如序列产生器、分布式锁,不同节点数量、不同读写比例下zk的tps如下:

来源于官方测试。上述测试基于3.2,2Ghz Xeon, 2块SATA 15K RPM硬盘。日志在单独的硬盘,快照写在OS系统盘,读写分别为1K大小,且客户端不直连leader。且从上可知,节点越多、写越慢、读越快。
znode节点的状态信息中包含czxid, 那么什么是zxid呢? 在zk中,状态的每一次改变, 都对应着一个递增的Transaction id, 该id称为zxid. 由于zxid的递增性质, 如果zxid1小于zxid2, 那么zxid1肯定先于zxid2发生. 创建任意节点, 或者更新任意节点的数据, 或者删除任意节点, 都会导致Zookeeper状态发生改变, 从而导致zxid的值增加.
Znode结构主要由存储于其中的数据信息和状态信息两部分构成,通过get 命令获取一个Znode结点的信息如下:

第一行存储的是ZNode的数据信息,从cZxid开始就是Znode的状态信息。Znode的状态信息比较多,几个主要的为:
czxid:
即Created ZXID,表示创建该Znode结点的事务ID
mzxid:
即Modified ZXID,表示最后一次更新该结点的事务ID
version
该Znode结点的版本号。每个Znode结点被创建时版本号都为0,每更新一次都会导致版本号加1,即使更新前后Znode存储的值没有变化版本号也会加1。version值可以形象的理解为Znode结点被更新的次数。Znode状态信息中的版本号信息,使得服务端可以对多个客户端对同一个Znode的更新操作做并发控制。整个过程和java中的CAS有点像,是一种乐观锁的并发控制策略,而version值起到了冲突检测的功能。客户端拿到Znode的version信息,并在更新时附上这个version信息,服务端在更新Znode时必须必须比较客户端的version和Znode的实际version,只有这两个version一致时才会进行修改。
paxos算法基于这样的原理:
• 在一个分布式数据库系统中,如果各节点的初始状态一致,每个节点都执行相同的操作序列,那么他们最后能得到一个一致的状态。
• Paxos算法解决的什么问题呢,解决的就是保证每个节点执行相同的操作序列。好吧,这还不简单,master维护一个
全局写队列,所有写操作都必须 放入这个队列编号,那么无论我们写多少个节点,只要写操作是按编号来的,就能保证一
致性。没错,就是这样,可是如果master挂了呢。
• Paxos算法通过投票来对写操作进行全局编号,同一时刻,只有一个写操作被批准,同时并发的写操作要去争取选票,
只有获得过半数选票的写操作才会被 批准(所以永远只会有一个写操作得到批准),其他的写操作竞争失败只好再发起一
轮投票,就这样,在日复一日年复一年的投票中,所有写操作都被严格编号排 序。编号严格递增,当一个节点接受了一个
编号为100的写操作,之后又接受到编号为99的写操作(因为网络延迟等很多不可预见原因),它马上能意识到自己 数据
不一致了,自动停止对外服务并重启同步过程。任何一个节点挂掉都不会影响整个集群的数据一致性(总2n+1台,除非挂掉大于n台)
因此在生产中,要求zookeeper部署3(单机房)或5(单机房)或7(跨机房)个节点的集群。
由于zookeeper的java官方客户端太不友好,因此实际中一般使用三方客户端Curator。故不对zookeeper客户端进行详细分析,参见本文首部对curator的详解。
watch是zookeeper针对节点的一次性观察者机制(即一次触发后就失效,需要手工重新创建watch),行为上类似于数据库的触发器。
当watch监视的数据发生时,通知设置了该watch的client,客户端即watcher。watcher的机制是监听数据发生了某些变化,所以一定会有对应的事件类型和状态类型,一个客户端可以监控多个节点,在代码中体现在new了几个就产生几个watcher,只要节点变化都会执行一遍process。其示意图如下:

在zookeeper中,watch有两种类型的事件能够监听:znode相关的及客户端实例相关的。分别为:
总结起来,zk watch的特性为:
两种情况下会发生Leader节点的选举,集群初始构建的时候;其次,无论如何,leader总是有可能发生宕机可能的。zookeeper中leader的选举过程为:
集群中的服务器会向其他所有的Follower服务器发送消息,这个消息可以形象化的称之为选票,选票主要由两个信息组成,所推举的Leader服务器的ID(即配置在myid文件中的数字),以及该服务器的事务ID,事务表示对服务器状态变更的操作,一个服务器的事务ID越大,则其数据越新。整个过程如下所述:
这样经过多轮投票后,如果某一台服务器得到了超过半数的选票,则其将当前选为Leader。由以上分析可知,Zookeeper集群对Leader服务器的选择具有偏向性,偏向于那些ZXID更大,即数据更新的机器。
整个过程如下图所示:

所以这里实际上简化了,有一个最后达成一致的细节过程需要进一步阐述(后续补充)。
Zookeeper通过事务日志和数据快照来避免因为服务器故障导致的数据丢失。这一点上所有采用事务机制的存储实现都一样,采用WAL+重放机制实现。
关于zookeeper的acl认证机制,及相关集成,可参考zookeeper acl认证机制及dubbo、kafka集成、zooviewer/idea zk插件配置。
原文:https://www.cnblogs.com/zhjh256/p/11033043.html