下述各zookeeper机制的java客户端实践参考zookeeper java客户端之curator详解。html
官方文档http://zookeeper.apache.org/doc/current/zookeeperOver.html、http://zookeeper.apache.org/doc/current/zookeeperInternals.html描述了部分关于zk的内部工做机制,可是并不够友好和详细。java
据官网介绍,ZooKeeper是一个用于提供配置信息、命名服务、分布式协调以及分组服务的中心化服务,它们是分布式应用所必需的。从实际应用来看,zookeeper是最普遍使用的分布式协调服务,包括dubbo、kafka、hadoop、es-job等都依赖于zookeeper提供的分布式协调和注册服务。其余用于提供注册服务的中间件还包括consul以及etcd、eureka,但都不及zookeeper普遍。其余适用场景可见https://xie.infoq.cn/article/1dcd3f8b100645e0da782a279。node
官网:https://zookeeper.apache.org/,https://zookeeper.apache.org/doc/r3.4.14/。mysql
zookeeper配置文件详解:https://zookeeper.apache.org/doc/r3.4.14/zookeeperAdmin.html#sc_configuration,也能够参考http://www.javashuo.com/article/p-seepotrk-z.htmlgit
在zookeeper中,节点分为下列几种角色:github
在一个zookeeper集群,各节点之间的交互以下所示:web
注:几乎全部现代基于分布式架构的中间件都是采用相似作法,例如kafka、es等。redis
从上可知,全部请求均由客户端发起,它多是本地zkCli或java客户端。 各角色详细职责以下。算法
leader的职责包括:sql
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硬盘。日志(WAL)在单独的硬盘,快照(zk内存数据快照)写在OS系统盘,读写分别为1K大小,且客户端不直连leader。且从上可知,节点越多、写越慢、读越快,因此通常节点不会不少,可是为了作扩展性和异地,会使用observer节点。
dataDir=/data
dataLogDir=/datalog
dataLogDir若是没提供的话使用的则是dataDir。zookeeper的持久化都存储在这两个目录里。dataLogDir里是放到的顺序日志(WAL)。而dataDir里放的是内存数据结构的snapshot,便于快速恢复(目前基本上全部带持久化特性的中间件如redis 4.x(kafka采用磁盘append,是个另类)都是借鉴数据库(oracle/mysql也支持buffer_pool/sga的dump)的作法,按期快照+WAL重放,而不是重启后清空来尽量提高性能)。为了达到性能最大化,通常建议把dataDir和dataLogDir分到不一样的磁盘上,这样就能够充分利用磁盘顺序写的特性。以下:
zookeeper快照文件的命名规则为snapshot.**,其中**表示zookeeper触发快照的那个瞬间,提交的最后一个事务的ID。其默认不会清理,从3.4.0开始,zookeeper提供了自动清理snapshot和事务日志的功能,经过配置 autopurge.snapRetainCount 和 autopurge.purgeInterval 这两个参数可以实现定时清理了。这两个参数都是在zoo.cfg中配置的:autopurge.purgeInterval 这个参数指定了清理频率,单位是小时,须要填写一个1或更大的整数,默认是0,表示不开启本身清理功能。autopurge.snapRetainCount 这个参数和上面的参数搭配使用,这个参数指定了须要保留的文件数目。默认是保留3个。
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一致时才会进行修改。
ZooKeeper默认状况下对数据字段的传输限制为1MB(全部分布式应用几乎默认都这个大小,如kafka、dubbo),该限制为任何节点数据字段的最大可存储字节数,同时也限制了任何父节点能够拥有的子节点数。
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是采用推送机制实现的,而不是客户端轮训(有些中间件采用拉的模式,例如kafka消费者,这主要取决于设计者认为的合理性,通常来讲流量很大的适合于拉的模式,这样更好作控制,不然客户端容易失控;反之推的模式)。watch有两种类型的事件可以监听:znode相关的及客户端实例相关的。分别为:
总结起来,zk watch的特性为:
客户端设置的每一个监视点与会话关联,若是会话过时,等待中的监视点将会被删除。不过监视点能够跨越不一样服务端的链接而保持,例如,当一个ZooKeeper客户端与一个ZooKeeper服务端的链接断开后链接到集合中的另外一个服务端,客户端会发送未触发的监视点列表,在注册监视点时,服务端将要检查已监视的znode节点在以前注册监视点以后是否已经变化,若是znode节点已经发生变化,一个监视点的事件就会被发送给客户端,不然在新的服务端上注册监视点。这一机制使得咱们能够关心逻辑层会话,而非底层链接自己。
两种状况下会发生Leader节点的选举,集群初始构建的时候;其次,不管如何,leader老是有可能发生宕机可能的。zookeeper中leader的选举过程为:
集群中的服务器会向其余全部的Follower服务器发送消息,这个消息能够形象化的称之为选票,选票主要由两个信息组成,所推举的Leader服务器的ID(即配置在myid文件中的数字),以及该服务器的事务ID,事务表示对服务器状态变动的操做,一个服务器的事务ID越大,则其数据越新。整个过程以下所述:
这样通过多轮投票后,若是某一台服务器获得了超过半数的选票,则其将当前选为Leader。由以上分析可知,Zookeeper集群对Leader服务器的选择具备偏向性,偏向于那些ZXID更大,即数据更新的机器。
整个过程以下图所示:
因此这里实际上简化了,有一个最后达成一致的细节过程须要进一步阐述(后续补充)。
Zookeeper经过事务日志和数据快照来避免由于服务器故障致使的数据丢失。这一点上全部采用事务机制的存储实现都同样,采用WAL+重放机制实现。
zookeeper的链接状态机以下:
从上可知,共有5种主要状态。实际上还有NOT_CONNECTED、CONNECTEDREADONLY、ASSOCIATING、RECONNECTED。
https://curator.apache.org/apidocs/org/apache/curator/framework/state/ConnectionState.html
未完待续。。。
未完待续。。。
关于zookeeper的acl认证机制,及相关集成,可参考zookeeper acl认证机制及dubbo、kafka集成、zooviewer/idea zk插件配置。
分析zookeeper的事务日志
可参见http://www.pianshen.com/article/6006190069/。
https://blog.imaginea.com/monitoring-zookeeper-with-exhibitor/
http://www.javashuo.com/article/p-qenrfpus-nc.html
https://github.com/soabase/exhibitor/wiki/Running-Exhibitor
http://www.javashuo.com/article/p-wlhbcppf-gz.html
zk etcd consul性能测试对比:https://coreos.com/blog/performance-of-etcd.html