万字长文带你入门Zookeeper!!!



点击上方码农沉思录,选择“设为星标”javascript

优质文章,及时送达css

前言 java

Zookeeper 相信你们都据说过,最典型的使用就是做为服务注册中心。今天陈某带你们从零基础入门 Zookeeper,看了本文,你将会对 Zookeeper 有了初步的了解和认识。node

注意:本文基于 Zookeeper 的版本是 3.4.14,最新版本的在使用上会有一些出入,可是企业如今使用的大部分都是 3.4x 版本的。nginx

Zookeeper 概述web

Zookeeper 是一个分布式协调服务的开源框架。主要用来解决分布式集群中应用系统的一致性问题,例如怎样避免同时操做同一数据形成脏读的问题。sql

ZooKeeper 本质上是一个分布式的小文件存储系统。提供基于相似于文件系 统的目录树方式的数据存储,而且能够对树中的节点进行有效管理。从而用来维护和监控你存储的数据的状态变化。经过监控这些数据状态的变化,从而能够达 到基于数据的集群管理。诸如:统一命名服务、分布式配置管理、分布式消息队列、分布式锁、分布式协调等功能。shell

Zookeeper 特性apache

  1. 全局数据一致:每一个 server 保存一份相同的数据副本,client 不管连 接到哪一个 server,展现的数据都是一致的,这是最重要的特征。api

  2. 可靠性:若是消息被其中一台服务器接受,那么将被全部的服务器接受。

  3. 顺序性:包括全局有序和偏序两种:全局有序是指若是在一台服务器上 消息 a 在消息 b 前发布,则在全部 Server 上消息 a 都将在消息 b 前被 发布;偏序是指若是一个消息 b 在消息 a 后被同一个发送者发布,a 必将排在 b 前面。

  4. 数据更新原子性:一次数据更新要么成功(半数以上节点成功),要么失 败,不存在中间状态。

  5. 实时性:Zookeeper 保证客户端将在一个时间间隔范围内得到服务器的更新信息,或者服务器失效的信息。

Zookeeper 节点类型

Znode 有两种,分别为临时节点和永久节点。

临时节点:该节点的生命周期依赖于建立它们的会话。一旦会话结束,临时节点将被自动删除,固然能够也能够手动删除。临时节点不容许拥有子节点。

永久节点:该节点的生命周期不依赖于会话,而且只有在客户端显示执行删除操做的时候,他们才能被删除。

节点的类型在建立时即被肯定,而且不能改变。

Znode 还有一个序列化的特性,若是建立的时候指定的话,该 Znode 的名字后面会自动追加一个不断增长的序列号。序列号对于此节点的父节点来讲是惟一的,这样便会记录每一个子节点建立的前后顺序。它的格式为"%10d"(10 位数字,没有数值的数位用 0 补充,例如“0000000001”)。

这样便会存在四种类型的 Znode 节点,分类以下:

PERSISTENT:永久节点

EPHEMERAL:临时节点

PERSISTENT_SEQUENTIAL:永久节点、序列化

EPHEMERAL_SEQUENTIAL:临时节点、序列化

ZooKeeper Watcher

ZooKeeper 提供了分布式数据发布/订阅功能,一个典型的发布/订阅模型系统定义了一种一对多的订阅关系,能让多个订阅者同时监听某一个主题对象,当这个主题对象自身状态变化时,会通知全部订阅者,使他们可以作出相应的处理。

触发事件种类不少,如:节点建立,节点删除,节点改变,子节点改变等。

总的来讲能够归纳 Watcher 为如下三个过程:客户端向服务端注册 Watcher、服务端事件发生触发 Watcher、客户端回调 Watcher 获得触发事件状况。

Watcher 机制特色

一次性触发 :事件发生触发监听,一个 watcher event 就会被发送到设置监听的客户端,这种效果是一次性的,后续再次发生一样的事件,不会再次触发。

事件封装 :ZooKeeper 使用 WatchedEvent 对象来封装服务端事件并传递。WatchedEvent 包含了每个事件的三个基本属性:通知状态(keeperState),事件类型(EventType)和节点路径(path)。

event 异步发送 :watcher 的通知事件从服务端发送到客户端是异步的。

先注册再触发 :Zookeeper 中的 watch 机制,必须客户端先去服务端注册监听,这样事件发送才会触发监听,通知给客户端。

经常使用 Shell 命令 新增节点create [-s] [-e] path data

-s:表示建立有序节点

-e:表示建立临时节点

建立持久化节点:

create /test 1234

## 子节点create /test/node1 node1

建立持久化有序节点:

完整的节点名称是a0000000001create /a a
Created /a0000000001
## 完整的节点名称是b0000000002create /b b
Created /b0000000002


建立临时节点:

create -e /a a

建立临时有序节点:

完整的节点名称是a0000000001create -e -s /a a
Created /a0000000001
更新节点set [path] [data] [version]


path:节点路径

data:数据

version:版本号

修改节点数据:

set /test aaa

## 修改子节点set /test/node1 bbb

基于数据版本号修改,若是修改的节点的版本号(dataVersion)不正确,拒绝修改

set /test aaa 1

删除节点delete [path] [version]

path:节点路径

version:版本号,版本号不正确拒绝删除

删除节点

delete /test

## 版本号删除delete /test 2

递归删除,删除某个节点及后代

rmr /test

查看节点数据和状态

命令格式以下:

get path

获取节点详情:

获取节点详情get /node1
## 节点内容aaa
cZxid = 0x6
ctime = Sun Apr 05 14:50:10 CST 2020
mZxid = 0x6
mtime = Sun Apr 05 14:50:10 CST 2020
pZxid = 0x7
cversion = 1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 3
numChildren = 1


节点各个属性对应的含义以下:

cZxid:数据节点建立时的事务 ID。

ctime:数据节点建立时间。

mZxid:数据节点最后一次更新时的事务 ID。

mtime:数据节点最后一次更新的时间。

pZxid:数据节点的子节点最后一次被修改时的事务 ID。

cversion:子节点的更改次数。

dataVersion:节点数据的更改次数。

aclVersion :节点 ACL 的更改次数。

ephemeralOwner:若是节点是临时节点,则表示建立该节点的会话的 SessionID。若是节点是持久化节点,值为 0。

dataLength :节点数据内容的长度。

numChildren:数据节点当前的子节点的个数。

查看节点状态stat path

stat命令和get命令类似,不过这个命令不会返回节点的数据,只返回节点的状态属性。

stat /node1
## 节点状态信息,没有节点数据cZxid = 0x6ctime = Sun Apr 05 14:50:10 CST 2020mZxid = 0x6mtime = Sun Apr 05 14:50:10 CST 2020pZxid = 0x7cversion = 1dataVersion = 0aclVersion = 0ephemeralOwner = 0x0dataLength = 3numChildren = 1


查看节点列表

查看节点列表有ls path和ls2 path两个命令。后者是前者的加强,不只会返回节点列表还会返回当前节点的状态信息。

ls path:ls /
## 仅仅返回节点列表[zookeeper, node1]
ls2 path:ls2 /
## 返回节点列表和当前节点的状态信息[zookeeper, node1]cZxid = 0x0ctime = Thu Jan 01 08:00:00 CST 1970mZxid = 0x0mtime = Thu Jan 01 08:00:00 CST 1970pZxid = 0x6cversion = 2dataVersion = 0aclVersion = 0ephemeralOwner = 0x0dataLength = 0numChildren = 2

监听器 get path watch

使用get path watch注册的监听器在节点内容发生改变时,向客户端发送通知,注意 Zookeeper 的触发器是一次性的,触发一次后会当即生效。

get /node1 watch
## 改变节点数据set /node1 bbb
## 监听到节点内容改变了WATCHER::WatchedEvent state:SyncConnected type:NodeDataChanged path:/node1

监听器 stat path watch

stat path watch注册的监听器可以在节点状态发生改变时向客户端发出通知。好比节点数据改变、节点被删除等。

stat /node2 watch

## 删除节点node2delete /node2

## 监听器监听到了节点删除WATCHER::

WatchedEvent state:SyncConnected type:NodeDeleted path:/node2

监听器 ls/ls2 path watch

使用ls path watch或者ls2 path watch注册的监听器,可以监听到该节点下的子节点的增长和删除操做。

ls /node1 watch

## 建立子节点create /node1/b b

## 监听到了子节点的新增WATCHER::

WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/node1

Zookeeper 的 ACL 权限控制

zookeeper 相似文件控制系统,client 能够建立,删除,修改,查看节点,那么如何作到权限控制的呢?zookeeper 的access control list 访问控制列表能够作到这一点。

ACL 权限控制,使用scheme:id:permission来标识。

权限模式(scheme):受权的策略

受权对象(id):受权的对象

权限(permission):授予的权限

权限控制是基于每一个节点的,须要对每一个节点设置权限。

每一个节点支持设置多种权限控制方案和多个权限。

子节点不会继承父节点的权限,客户端无权访问某节点,但可能能够访问它的子节点。

例如:根据 IP 地址进行受权,命令以下:

setACl /node1 ip:192.168.10.1:crdwa
权限模式

权限模式便是采用何种方式受权。

world:只有一个用户,anyone,表示登陆 zookeeper 全部人(默认的模式)。

ip:对客户端使用 IP 地址认证。

auth:使用已添加认证的用户认证。

digest:使用用户名:密码方式认证。

受权对象

给谁受权,受权对象的 ID 指的是权限赋予的实体,例如 IP 地址或用户。

授予的权限

授予的权限包括create、delete、read、writer、admin。也就是增、删、改、查、管理的权限,简写cdrwa。

注意:以上 5 种权限中,delete是指对子节点的删除权限,其余 4 种权限是对自身节点的操做权限。

create:简写c,能够建立子节点。

delete:简写d,能够删除子节点(仅下一级节点)。

read:简写r,能够读取节点数据以及显示子节点列表。

write:简写w,能够更改节点数据。

admin:简写a,能够设置节点访问控制列表权限。

受权相关命令

getAcl [path]:读取指定节点的 ACL 权限。

setAcl [path] [acl]:设置 ACL

addauth :添加认证用户,和 auth,digest 受权模式相关。

world 受权模式案例

zookeeper 中默认的受权模式,针对登陆 zookeeper 的任何用户授予指定的权限。命令以下:

setAcl [path] world:anyone:[permission]

path:节点

permission:授予的权限,好比cdrwa

去掉不能读取节点数据的权限:

获取权限列表(默认的)getAcl /node2
'world,'anyone
: cdrwa
## 去掉读取节点数据的的权限,去掉rsetAcl /node2 world:anyone:cdwa
## 再次获取权限列表getAcl /node2
'world,'anyone
: cdwa
## 获取节点数据,没有权限,失败get /node2
Authentication is not valid : /node2
IP 受权模式案例


针对登陆用户的 ip 进行权限限制。命令以下:

setAcl [path] ip:[ip]:[acl]

远程登陆 zookeeper 的命令以下:

./zkCli.sh -server ip

设置192.168.10.1这个 ip 的增删改查管理的权限。

setAcl /node2 ip:192.168.10.1:crdwa

Auth 受权模式案例

auth 受权模式须要有一个认证用户,添加命令以下:

addauth digest [username]:[password]

设置 auth 受权模式命令以下:

setAcl [path] auth:[user]:[acl]

为chenmou这个帐户添加 cdrwa 权限:

添加一个认证帐户addauth digest chenmou:123456
## 添加权限setAcl /node2 auth:chenmou:crdwa
多种模式受权


zookeeper 中同一个节点可使用多种受权模式,多种受权模式用,分隔。

建立节点create /node3
## 添加认证用户addauth chenmou:123456
## 添加多种受权模式setAcl /node3 ip:192.178.10.1:crdwa,auth:chenmou:crdwa
ACL 超级管理员


zookeeper 的权限管理模式有一种叫作super,该模式提供一个超管能够方便的访问任何权限的节点。

假设这个超管是super:admin,须要先为超管生成密码的密文:

echo -n super:admin | openssl dgst -binary -sha1 |openssl base64## 执行完生成了秘钥xQJmxLMiHGwaqBvst5y6rkB6HQs=

打开zookeeper目录下/bin/zkServer.sh,找到以下一行:

nohup JAVA"−Dzookeeper.log.dir=JAVA"−Dzookeeper.log.dir={ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}"

在后面添加一行脚本,以下:

"-Dzookeeper.DigestAuthenticationProvider.superDigest=super:xQJmxLMiHGwaqBvst5y6rkB6HQs="

此时完整的脚本以下:

nohup "$JAVA" "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" "-Dzookeeper.DigestAuthenticationProvider.superDigest=super:xQJmxLMiHGwaqBvst5y6rkB6HQs=" \ -cp "$CLASSPATH" $JVMFLAGS $ZOOMAIN "$ZOOCFG" > "$_ZOO_DAEMON_OUT" 2>&1 < /dev/null &

重启 zookeeper

重启完成以后此时超管即配置完成,若是须要使用,则使用以下命令:

 addauth digest super:admin
Curator 客户端

Curator 是 Netflix 公司开源的一个 Zookeeper 客户端,与 Zookeeper 提供的原生客户端相比,Curator 的抽象层次更高,简化了 Zookeeper 客户端的开发量。

添加依赖 

org.apache.curatorcurator-recipes            4.0.0org.apache.zookeeper           zookeeper        org.apache.zookeeperzookeeper 3.4.10

创建链接

客户端创建与 Zookeeper 的链接,这里仅仅演示单机版本的链接,以下:

//建立CuratorFramework,用来操做apiCuratorFramework client = CuratorFrameworkFactory.builder()//ip地址+端口号,若是是集群,逗号分隔 .connectString("120.26.101.207:2181")//会话超时时间 .sessionTimeoutMs(5000)//超时重试策略,RetryOneTime:超时重连仅仅一次 .retryPolicy(new RetryOneTime(3000))//命名空间,父节点,若是不指定是在根节点下 .namespace("node4") .build();//启动client.start();

重连策略

会话链接策略,便是当客户端与 Zookeeper 断开链接以后,客户端从新链接 Zookeeper 时使用的策略,好比从新链接一次。

RetryOneTime:N 秒后重连一次,仅仅一次,演示以下:

.retryPolicy(new RetryOneTime(3000))
RetryNTimes:每 n 秒重连一次,重连 m 次。演示以下://每三秒重连一次,重连3次。arg1:多长时间后重连,单位毫秒,arg2:总共重连几回.retryPolicy(new RetryNTimes(3000,3))
RetryUntilElapsed:设置了最大等待时间,若是超过这个最大等待时间将会再也不链接。//每三秒重连一次,等待时间超过10秒再也不重连。arg1:总等待时间,arg2:多长时间重连,单位毫秒.retryPolicy(new RetryUntilElapsed(10000,3000))

新增节点

新增节点

client.create()//指定节点的类型。PERSISTENT:持久化节点,PERSISTENT_SEQUENTIAL:持久化有序节点,EPHEMERAL:临时节点,EPHEMERAL_SEQUENTIAL临时有序节点 .withMode(CreateMode.PERSISTENT)//指定权限列表,OPEN_ACL_UNSAFE:world:anyone:crdwa .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)//写入节点数据,arg1:节点名称 arg2:节点数据 .forPath("/a", "a".getBytes());
自定义权限列表:withACL(acls)方法中能够设置自定义的权限列表,代码以下://自定义权限列表List acls=new ArrayList<>();//指定受权模式和受权对象 arg1:受权模式,arg2受权对象Id id=new Id("ip","127.0.0.1");//指定授予的权限,ZooDefs.Perms.ALL:crdwaacls.add(new ACL(ZooDefs.Perms.ALL,id));client.create() .withMode(CreateMode.PERSISTENT)//指定自定义权限列表 .withACL(acls) .forPath("/b", "b".getBytes());
递归建立节点:creatingParentsIfNeeded()方法用于建立多层节点,若是其中一个节点不存在则会自动建立//递归建立节点client.create()//递归方法,若是节点不存在,那么建立该节点 .creatingParentsIfNeeded() .withMode(CreateMode.PERSISTENT) .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)//test节点和b节点不存在,递归建立出来 .forPath("/test/a", "a".getBytes());
异步建立节点:inBackground()方法能够异步回调建立节点,建立完成后会自动回调实现的方法//异步建立节点client.create() .withMode(CreateMode.PERSISTENT) .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)//异步建立 .inBackground(new BackgroundCallback() {/** * @param curatorFramework 客户端对象 * @param curatorEvent 事件对象 */@Overridepublic void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {//打印事件类型 System.out.println(curatorEvent.getType()); } }) .forPath("/test1", "a".getBytes());

更新节点数据

更新节点,当节点不存在会报错,代码以下:

client.setData() .forPath("/a","a".getBytes());

携带版本号更新节点,当版本错误拒绝更新

client.setData()//指定版本号更新,若是版本号错误则拒绝更新 .withVersion(1) .forPath("/a","a".getBytes());

异步更新节点数据:

client.setData()//异步更新 .inBackground(new BackgroundCallback() {//回调方法@Overridepublic void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception { } }) .forPath("/a","a".getBytes());

删除节点

删除当前节点,若是有子节点则拒绝删除client.delete()//删除节点,若是是该节点包含子节点,那么不能删除 .forPath("/a");
指定版本号删除,若是版本错误则拒绝删除client.delete()//指定版本号删除 .withVersion(1)//删除节点,若是是该节点包含子节点,那么不能删除 .forPath("/a");
若是当前节点包含子节点则一并删除,使用deletingChildrenIfNeeded()方法client.delete()//若是删除的节点包含子节点则一块儿删除 .deletingChildrenIfNeeded()//删除节点,若是是该节点包含子节点,那么不能删除 .forPath("/a");
异步删除节点,使用inBackground()client.delete() .deletingChildrenIfNeeded()//异步删除节点 .inBackground(new BackgroundCallback() { @Overridepublic void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {//回调监听 } })//删除节点,若是是该节点包含子节点,那么不能删除 .forPath("/a");

获取节点数据

同步获取节点数据byte[] bytes = client.getData().forPath("/node1");System.out.println(new String(bytes));
获取节点状态和数据//保存节点状态Stat stat=new Stat();byte[] bytes = client.getData()//获取节点状态存储在stat对象中 .storingStatIn(stat) .forPath("/node1");System.out.println(new String(bytes));//获取节点数据的长度System.out.println(stat.getDataLength());

异步获取节点数据

client.getData()//异步获取节点数据,回调监听 .inBackground((curatorFramework, curatorEvent) -> {//节点数据 System.out.println(new String(curatorEvent.getData())); }) .forPath("/node1");

获取子节点

同步获取所有子节点 List strs = client.getChildren().forPath("/");for (String str:strs) { System.out.println(str); }
异步获取所有子节点client.getChildren()//异步获取.inBackground((curatorFramework, curatorEvent) -> { List strs = curatorEvent.getChildren();for (String str:strs) { System.out.println(str); } }).forPath("/");查看节点是否存在


同步查看

//若是节点不存在,stat为nullStat stat = client.checkExists().forPath("/node");
异步查看//若是节点不存在,stat为nullclient.checkExists() .inBackground((curatorFramework, curatorEvent) -> {//若是为null则不存在 System.out.println(curatorEvent.getStat()); }) .forPath("/node");

Watcher API

curator 提供了两种 watcher 来监听节点的变化

NodeCache:监听一个特定的节点,监听新增和修改

PathChildrenCache:监听一个节点的子节点,当一个子节点增长、删除、更新时,path Cache 会改变他的状态,会包含最新的子节点的数据和状态。

NodeCache 演示:

//arg1:链接对象 arg2:监听的节点路径,/namespace/pathfinal NodeCache nodeCache = new NodeCache(client, "/w1");//启动监听nodeCache.start();//添加监听器nodeCache.getListenable().addListener(() -> {//节点路径 System.out.println(nodeCache.getCurrentData().getPath());//节点数据 System.out.println(new String(nodeCache.getCurrentData().getData()));});//睡眠100秒Thread.sleep(1000000);//关闭监听nodeCache.close();
PathChildrenCache演示://arg1:链接对象 arg2:节点路径 arg3:是否可以获取节点数据PathChildrenCache cache=new PathChildrenCache(client,"/w1", true);cache.start();cache.getListenable().addListener((curatorFramework, pathChildrenCacheEvent) -> {//节点路径 System.out.println(pathChildrenCacheEvent.getData().getPath());//节点状态 System.out.println(pathChildrenCacheEvent.getData().getStat());//节点数据 System.out.println(new String(pathChildrenCacheEvent.getData().getData()));});cache.close();

小福利

是否是以为文章太长看得头晕脑胀,为此陈某特意将本篇文章制做成 PDF 文本,须要回去仔细研究的朋友,老规矩,回复关键词ZK入门指南。

本文分享自微信公众号 - 码农沉思录(code-thinker)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索