万字长文带你入门Zookeeper!!!
程序员文章站
2022-06-22 08:10:44
导读 文章首发于微信公众号【码猿技术专栏】,原创不易,谢谢支持。 Zookeeper 相信大家都听说过,最典型的使用就是作为服务注册中心。今天陈某带大家从零基础入门 Zookeeper,看了本文,你将会对 Zookeeper 有了初步的了解和认识。 注意:本文基于 Zookeeper 的版本是 3. ......
导读
- 文章首发于微信公众号【码猿技术专栏】,原创不易,谢谢支持。
- zookeeper 相信大家都听说过,最典型的使用就是作为服务注册中心。今天陈某带大家从零基础入门 zookeeper,看了本文,你将会对 zookeeper 有了初步的了解和认识。
- 注意:本文基于 zookeeper 的版本是 3.4.14,最新版本的在使用上会有一些出入,但是企业现在使用的大部分都是 3.4x 版本的。
zookeeper 概述
- zookeeper 是一个分布式协调服务的开源框架。主要用来解决分布式集群中应用系统的一致性问题,例如怎样避免同时操作同一数据造成脏读的问题。
- zookeeper 本质上是一个分布式的小文件存储系统。提供基于类似于文件系 统的目录树方式的数据存储,并且可以对树中的节点进行有效管理。从而用来维护和监控你存储的数据的状态变化。通过监控这些数据状态的变化,从而可以达 到基于数据的集群管理。诸如:
统一命名服务
、分布式配置管理
、分布式消息队列
、分布式锁
、分布式协调
等功能。
zookeeper 特性
-
全局数据一致
:每个 server 保存一份相同的数据副本,client 无论连 接到哪个 server,展示的数据都是一致的,这是最重要的特征; -
可靠性
:如果消息被其中一台服务器接受,那么将被所有的服务器接受。 -
顺序性
:包括全局有序和偏序两种:全局有序是指如果在一台服务器上 消息 a 在消息 b 前发布,则在所有 server 上消息 a 都将在消息 b 前被 发布;偏序是指如果一个消息 b 在消息 a 后被同一个发送者发布,a 必将排在 b 前面。 -
数据更新原子性
:一次数据更新要么成功(半数以上节点成功),要么失 败,不存在中间状态; -
实时性
: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
- 创建持久化有序节点:
## 完整的节点名称是a0000000001 create /a a created /a0000000001 ## 完整的节点名称是b0000000002 create /b b created /b0000000002
- 创建临时节点:
create -e /a a
- 创建临时有序节点:
## 完整的节点名称是a0000000001 create -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 = 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
查看节点列表
- 查看节点列表有
ls path
和ls2 path
两个命令。后者是前者的增强,不仅会返回节点列表还会返回当前节点的状态信息。 -
ls path
:
ls / ## 仅仅返回节点列表 [zookeeper, node1]
-
ls2 path
:
ls2 / ## 返回节点列表和当前节点的状态信息 [zookeeper, node1] czxid = 0x0 ctime = thu jan 01 08:00:00 cst 1970 mzxid = 0x0 mtime = thu jan 01 08:00:00 cst 1970 pzxid = 0x6 cversion = 2 dataversion = 0 aclversion = 0 ephemeralowner = 0x0 datalength = 0 numchildren = 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 ## 删除节点node2 delete /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 <scheme> <auth>
:添加认证用户,和 auth,digest 授权模式相关。
world 授权模式案例
- zookeeper 中默认的授权模式,针对登录 zookeeper 的任何用户授予指定的权限。命令如下:
setacl [path] world:anyone:[permission]
-
path
:节点 -
permission
:授予的权限,比如cdrwa
- 去掉不能读取节点数据的权限:
## 获取权限列表(默认的) getacl /node2 'world,'anyone : cdrwa ## 去掉读取节点数据的的权限,去掉r setacl /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 客户端的开发量。
添加依赖
<dependency> <groupid>org.apache.curator</groupid> <artifactid>curator-recipes</artifactid> <version>4.0.0</version> <exclusions> <exclusion> <groupid>org.apache.zookeeper</groupid> <artifactid>zookeeper</artifactid> </exclusion> </exclusions> </dependency> <dependency> <groupid>org.apache.zookeeper</groupid> <artifactid>zookeeper</artifactid> <version>3.4.10</version> </dependency>
建立连接
- 客户端建立与 zookeeper 的连接,这里仅仅演示单机版本的连接,如下:
//创建curatorframework,用来操作api curatorframework 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<acl> acls=new arraylist<>(); //指定授权模式和授权对象 arg1:授权模式,arg2授权对象 id id=new id("ip","127.0.0.1"); //指定授予的权限,zoodefs.perms.all:crdwa acls.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 事件对象 */ @override public 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() { //回调方法 @override public 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() { @override public 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<string> strs = client.getchildren().forpath("/"); for (string str:strs) { system.out.println(str); }
- 异步获取全部子节点
client.getchildren() //异步获取 .inbackground((curatorframework, curatorevent) -> { list<string> strs = curatorevent.getchildren(); for (string str:strs) { system.out.println(str); } }) .forpath("/");
查看节点是否存在
- 同步查看
//如果节点不存在,stat为null stat stat = client.checkexists().forpath("/node");
- 异步查看
//如果节点不存在,stat为null client.checkexists() .inbackground((curatorframework, curatorevent) -> { //如果为null则不存在 system.out.println(curatorevent.getstat()); }) .forpath("/node");
watcher api
- curator 提供了两种 watcher 来监听节点的变化
-
nodecache
:监听一个特定的节点,监听新增和修改 -
pathchildrencache
:监听一个节点的子节点,当一个子节点增加、删除、更新时,path cache 会改变他的状态,会包含最新的子节点的数据和状态。
-
- nodecache 演示:
//arg1:连接对象 arg2:监听的节点路径,/namespace/path final 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入门指南
。
上一篇: python基础总结
下一篇: iphoneX设计网站适配