最近加入了部门的技术兴趣小组,被分配了Zookeeper的研究任务。在研究过程中,发现Zookeeper因为其开源的特性和其卓越的性能特色,在业界使用普遍,有不少的应用场景,而这些不一样的应用场景实际上底层的原理都是差很少的,只要你真正理解了Zookeeper的一些基础概念和机制,就可以举一反三。html
因而乎,在第一次和项目小组内成员分享过Zookeeper做为服务注册中心的原理和客户端demo演示以后,我萌生出了整理一个专题的想法,以此为起点,慢慢捡起本身的博客分享之路。java
本篇的内容主要介绍如下几点:node
我最先接触Zookeeper是由于咱们项目使用的微服务治理架构是Dubbo,Dubbo推荐使用的服务注册中心就是Zookeeper。从本质上来讲,Zookeeper就是一种分布式协调服务,在分布式环境中协调和管理服务是一个复杂的过程。ZooKeeper经过其简单的架构和API解决了这个问题。 ZooKeeper容许开发人员专一于核心应用程序逻辑,而没必要担忧应用程序的分布式特性。Zookeeper最先的应用是在Hadoop生态中,Apache HBase使用ZooKeeper跟踪分布式数据的状态。shell
实际上从它的名字上就很好理解,Zoo - 动物园,Keeper - 管理员,动物园中有不少种动物,这里的动物就能够比做分布式环境下多种多样的服务,而Zookeeper作的就是管理这些服务。数据库
ZooKeeper 的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用。apache
原语: 操做系统或计算机网络用语范畴。是由若干条指令组成的,用于完成必定功能的一个过程。具备不可分割性·即原语的执行必须是连续的,在执行过程当中不容许被中断。
Zookeeper提供服务主要就是经过:数据结构 + 原语集 + watch机制达到的。api
分布式应用程序结合Zookeeper能够实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master选举、分布式锁和分布式队列等功能。服务器
从上图能够看到,Zookeeper的数据模型和Unix的文件系统目录树很相似,拥有一个层次的命名空间。这里面的每个节点都被称为 - ZNode, 节点能够拥有子节点,同时也容许少许数据节点存储在该节点之下。(能够理解成一个容许一个文件也能够是一个目录的文件系统)网络
ZNode经过路径引用,如同Unix中的文件路径。路径必须是绝对的,所以他们必须有斜杠字符/
来开头,除此以外,路径名必须是惟一的,且不能更改。session
这个特性在Dubbo的服务注册上也有体现,Dubbo源码中有个贯穿全局的类URL
,dubbo是以总线模式来时刻传递和保存配置信息的,也就是配置信息都被放在URL
上进行传递,随时能够取得相关配置信息。Dubbo在向注册中心注册时写下的节点名就是由URL
中的URI
和配置信息编码后组成的。以下图。
这属于这部分知识的扩展内容,在以后服务注册中心的章节会更具体的说明。
前面提到过,ZNode兼具文件和目录两种特色,既像文件同样维护着数据、元信息、ACL、时间戳等数据结构,又像目录同样能够做为路径标识的一部分。
ZNode由如下几部分组成:
Stat数据结构
操做控制列表(ACL) - 每一个节点都有一个ACL来作节点的操做控制,这个列表规定了用户的权限,限定了特定用户对目标节点的操做
版本 - ZNode有三个数据版本
Zxid
ZooKeeper的每一个节点维护者三个Zxid值,分别为:cZxid、mZxid、pZxid。
下面有几个须要注意的知识点着重讲一下:
下图是我在服务器上使用zkClient,用get命令获取到的某个Dubbo微服务接口节点的状态信息,来做为示例,
[zk: localhost:2181(CONNECTED) 0] get /dubbo/com.***.microservice.ucs.api.UniqueControlApi 127.0.0.1 // 节点数据Data域 cZxid = 0xdd59 //Created ZXID,表示该ZNode被建立时的事务ID ctime = Thu Apr 18 15:17:11 CST 2019 //Created Time,表示该ZNode被建立的时间 mZxid = 0xdd59 //Modified ZXID,表示该ZNode最后一次被更新时的事务ID mtime = Thu Apr 18 15:17:11 CST 2019 //Modified Time,表示该节点最后一次被更新的时间 pZxid = 0xdd62 //表示该节点的子节点列表最后一次被修改时的事务ID。注意,只有子节点列表变动了才会变动pZxid,子节点内容变动不会影响pZxid。 cversion = 4 //子节点的版本号 dataVersion = 0 //数据节点版本号 aclVersion = 0 //ACL版本号 ephemeralOwner = 0x0 //建立该节点的会话的sessionID。若是该节点是持久节点,那么这个属性值为0。 dataLength = 9 // Data域内容长度 numChildren = 4 // 子节点个数 众所周知,Dubbo接口子节点分为providers/configurators/routers/consumers
关于Data域,Zookeeper中每一个节点存储的数据要被原子性的操做,也就是说读操做将获取与节点相关的全部数据,写操做也将替换掉节点的全部数据。
值得注意的是,Zookeeper虽然能够存储数据,可是从设计目的上,并非为了作数据库或者大数据存储,相反,它是用来管理调度数据,好比分布式应用中的配置文件信息、状态信息、聚集位置等,这些数据一般是很小的数据,KB为大小单位。ZNode对数据大小也有限制,至多1M。实际上从这里,就能够推导出Zookeeper用于分布式配置中心的可行性。
在ZooKeeper中,能改变ZooKeeper服务器状态的操做称为事务操做。通常包括数据节点建立与删除、数据内容更新和客户端会话建立与失效等操做。对应每个事务请求,ZooKeeper都会为其分配一个全局惟一的事务ID,用Zxid表示。
由上图的示例能够看出,Zxid是一个64位的数字。前32位叫作epoch,用来标识Zookeeper 集群中的Leader
节点,当Leader
节点更换时,就会有一个新的epoch。后32位则为递增序列。从这些Zxid中能够间接地识别出ZooKeeper处理这些事务操做请求的全局顺序。
ZNode节点类型严格来讲有四种:持久节点、临时节点、持久顺序节点、临时顺序节点
/myapp
的znode建立为顺序节点,则ZooKeeper会将路径更改成 /myapp0000000001
,并将下一个序列号设置为0000000002
。若是两个顺序节点是同时建立的,那么ZooKeeper不会对每一个znode使用相同的数字。顺序节点在锁定和同步中起重要做用。 如上图,标明了Zookeeper服务的九种基本操做,进入ZkClient.sh
,使用help
,能够看到这几种操做。
[zk: localhost:2181(CONNECTED) 1] help ZooKeeper -server host:port cmd args stat path [watch] // 获取指定节点的状态信息 set path data [version] // setData操做 ls path [watch] // 查看某个节点下的全部子节点信息 delquota [-n|-b] path // 删除节点配额 ls2 path [watch] // ls + stat 两个命令结合 setAcl path acl // 设置ACL setquota -n|-b val path // 设置节点配额,-n 是限制子节点个数 -b是限制节点数据长度 history // 历史命令 redo cmdno // 执行历史命令 printwatches on|off delete path [version] // 删除指定路径节点,有子节点须要先删除子节点 sync path // 同步视图 listquota path // 查看节点配额信息 rmr path // 删除节点及其子节点 get path [watch] // 获取当前节点数据内容 create [-s] [-e] path data acl // 建立节点 addauth scheme auth quit getAcl path // 获取ACL close connect host:port
从命令中能够看到,更新ZooKeeper操做是有限制的。delete或setData必须明确要更新的Znode的版本号,咱们能够调用exists找到。若是版本号不匹配,更新将会失败。
更新ZooKeeper操做是非阻塞式的。所以客户端若是失去了一个更新(因为另外一个进程在同时更新这个Znode),他能够在不阻塞其余进程执行的状况下,选择从新尝试或进行其余操做。
在 ZooKeeper 中,一个客户端链接是指客户端和服务器之间的一个 TCP 长链接。客户端启动的时候,首先会与服务器创建一个 TCP 链接,从第一次链接创建开始,客户端会话的生命周期也开始了。经过这个链接,客户端可以经过心跳检测与服务器保持有效的会话,也可以向Zookeeper服务器发送请求并接受响应,同时还可以经过该链接接收来自服务器的Watch事件通知。
客户端以特定的时间间隔发送心跳以保持会话有效。若是ZooKeeper Server Ensembles在超过服务器开启时指定的期间(会话超时)都没有从客户端接收到心跳,则它会断定客户端死机。
会话超时一般以毫秒为单位。当会话因为任何缘由结束时,在该会话期间建立的临时节点也会被删除。
在我看来,Watches - 监听事件,是Zookeeper中一个很重要的特性,也是实现Zookeeper大多数功能的核心特性之一。简单来讲, Zookeeper容许Client端在指定节点上注册Watches,在某些特定事件触发的时候,Zookeeper服务端会将事件异步通知到感兴趣(即注册了Watches)的客户端上去。能够理解成一个订阅/发布系统,是否是。
Znode更改是与znode相关的数据的修改或znode的子项中的更改。只触发一次watches。若是客户端想要再次通知,则必须经过另外一个读取操做来完成。当链接会话过时时,客户端将与服务器断开链接,相关的watches也将被删除。
下面说完简单的,来讲点复杂的部分。
几个特性先了解下:
Zookeeper的Watches 分为两种,数据监听器(Data Watches)和子节点监听器(Children Watches)。即你能够对某个节点的Data设置watches,也能够对某个子节点设置watches。
能够看下Zookeeper Java 客户端 Zkclient
中的设置watches的代码:
// listener 监听器 // path 节点路径 // 子节点监听器 private List<String> addTargetChildListener(String path, IZkChildListener listener) { return client.subscribeChildChanges(path, listener); } // 节点数据的监听器 public void addChildDataListener(String path, IZkDataListener listener) { try { // 递归建立节点 client.subscribeDataChanges(path, listener); } catch (ZkNodeExistsException e) { } }
做为开发者,须要知道监控节点的什么操做会触发你设置的watches。
再看下ZkClient中的数据监听器接口IZkDataListener
public interface IZkDataListener { // 监控节点数据更新的时候会触发 这段逻辑 public void handleDataChange(String dataPath, Object data) throws Exception; // 监控节点被删除的时候会触发 这段逻辑 public void handleDataDeleted(String dataPath) throws Exception; }
再看下ZkClient中的子节点监听器接口IZkChildListener
public interface IZkChildListener { /** * Called when the children of the given path changed. * 监控节点的子节点列表改变时会触发这段逻辑 * * @param parentPath * The parent path * @param currentChilds * The children or null if the root node (parent path) was deleted. * @throws Exception */ public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception; }
实际上看到这就能联想到,Zookeeper是能够当作分布式配置中心来使用的,只不过你须要本身扩展他异步通知节点数据变化以后的逻辑,更新你的配置。在后面的章节会更新相关demo。
关于Watches 详细介绍能够参考官网的介绍:
ZooKeeper Watches
本章内容算是Zookeeper系列的开篇,介绍了Zookeeper的几个基础概念,而且给出了相关实例,助于理解。
如今咱们再回过头来看看Zookeeper的特性:
① 顺序一致性
从同一个客户端发起的事务请求,最终将会严格按照其发起顺序被应用到ZooKeeper中。② 原子性
全部事务请求的结果在集群中全部机器上的应用状况是一致的,也就是说要么整个集群全部集群都成功应用了某一个事务,要么都没有应用,必定不会出现集群中部分机器应用了该事务,而另一部分没有应用的状况。③ 单一视图
不管客户端链接的是哪一个ZooKeeper服务器,其看到的服务端数据模型都是一致的。④ 可靠性
一旦服务端成功地应用了一个事务,并完成对客户端的响应,那么该事务所引发的服务端状态变动将会被一直保留下来,除非有另外一个事务又对其进行了变动。⑤ 实时性
一般人们看到实时性的第一反应是,一旦一个事务被成功应用,那么客户端可以当即从服务端上读取到这个事务变动后的最新数据状态。这里须要注意的是,ZooKeeper仅仅保证在必定的时间段内,客户端最终必定可以从服务端上读取到最新的数据状态。
今天的内容中,顺序一致性是经过ZXid来实现的,全局惟一,顺序递增,同一个session中请求是FIFO的;可靠性的描述也能够经过今天的知识进行理解,一次事务的应用,服务端状态的变动会以Zxid、Znode数据版本、数据、节点路径的形式保存下来。剩下的几种特性是怎么实现的,在学习完Zookeeper集群相关的内容以后应该就能理解。
本篇文章中借鉴了网上几篇优秀的文章,而且结合了我本人一些思考和实践。但愿能对你学习了解Zookeeper起到一些帮助。
下一章,我会介绍Zookeeper集群方面的知识,CAP
理论在Zookeeper中的实践,以及如何搭建Zookeeper的集群。
[1] https://zookeeper.apache.org/... 官方文档(强烈推荐)
[2] https://www.cnblogs.com/sundd... 做者应该是对官方文档有比较深的了解,我发现他的文章的脉络和官网有很类似的地方。写的很是好
[3] https://www.jianshu.com/p/a17... 做者对Zookeeper作了一个易懂的整体介绍
[4] https://www.w3cschool.cn/zook... w3cSchool tutorial