进去zookeeper安装目录,在bin目录下有客户端的启动脚本
./bin/zkCli.sh
这说明客户端启动成功了!
二. zk的节点类型zookeeper节点结构是一个树形结构,一个节点有两个维度:临时/永久、有序/无序。一共四种节点类型:
-
PERSISTENT:持久化节点
-
PERSISTENT_SEQUENTLAT:持久化排序节点
-
EPHEMERAL:临时节点
-
EPHEMERAL_SEQUENTLAL:临时排序节点
永久节点:在节点创建之后就会被持久化,只有主动调用delete方法的时候才可以删除节点;
临时节点:节点创建后自动创建超时连接或者失去连接的时候,节点会被删除,临时节点不能存在子节点;
排序节点:创建节点名称后会自动添加序号,例如:节点名称为node-,将会自动添加node-1,顺序的话就是node-2
三. 操作命令在控制台输入 help或者tab键,就可以看到zookeeper客户端支持的命令了:
下面介绍一写常用的命令,用来操作zookeeper集群
3.1. ls和get命令ls命令:查看当前节点信息,有三个参数:-w(监听子节点变化)、-s(详细信息)和-R(递归现实节点)
[zk: localhost:2181(CONNECTED) 8] ls / [zookeeper] [zk: localhost:2181(CONNECTED) 9] ls -s / [zookeeper]cZxid = 0x0 ctime = Thu Jan 01 08:00:00 CST 1970 mZxid = 0x0 mtime = Thu Jan 01 08:00:00 CST 1970 pZxid = 0x0 cversion = -1 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 0 numChildren = 1
简单介绍下这些信息:
| 参数 | 描述 |
|---|---|
| cZxid | 每次修改zk状态都会产生一个zk事务ID,事务ID是zk中所有修改的总次序。每次修改都有唯一的zxid,zxid有先后顺序。 |
| ctime | znode被创建的毫秒数(1970年开始) |
| mZxid | znode最后更新的事务zxid |
| mtime | znode最后修改的毫秒数(1970年开始) |
| pZxid | znode最后更新的子节点zxid |
| cversion | znode子节点变化号,znode子节点修改次数 |
| dataVersion | znode数据变化号 |
| aclVersion | znode访问访问控制表的变化号 |
| ephemeralOwner | 如果是临时节点,这个是znode拥有者的session id。如果不是临时节点则是0 |
| dataLength | znode的数据长度 |
| numChildren | znode的子节点数量 |
get命令可以获取节点内容:
有两个参数:-s(获取节点数据以及节点信息)、-w(监听节点数据变化)
[zk: localhost:2181(CONNECTED) 13] create /root "11" Created /root [zk: localhost:2181(CONNECTED) 15] get /root 113.2. create命令
创建节点命令格式:create [-s] [-e] [-c] [-t ttl] path [data] [acl]
-s参数:顺序节点
-e参数:临时节点
acl参数:用来控制权限
创建节点如果不赋值的话,节点的值就是null。
[zk: localhost:2181(CONNECTED) 20] create /root Created /root [zk: localhost:2181(CONNECTED) 21] get /root null [zk: localhost:2181(CONNECTED) 22] create /node "hello world" Created /node [zk: localhost:2181(CONNECTED) 23] get /node hello world [zk: localhost:2181(CONNECTED) 24]
创建带序号的节点(永久节点+序号):
[zk: localhost:2181(CONNECTED) 39] create /root # 需要先创建普通节点 Created /root [zk: localhost:2181(CONNECTED) 40] create -s /root/node- "aaaaa" # 创建带序号的节点 Created /root/node-0000000000 [zk: localhost:2181(CONNECTED) 41] create -s /root/node- "bbbbb" Created /root/node-0000000001 [zk: localhost:2181(CONNECTED) 42] create -s /root/node- "ccccc" Created /root/node-0000000002 [zk: localhost:2181(CONNECTED) 43]
创建临时节点(带序号/不带序号):
[zk: localhost:2181(CONNECTED) 58] create /root Created /root [zk: localhost:2181(CONNECTED) 61] create -e /root/node "11" # 临时无序节点不能多次设置值 Created /root/node [zk: localhost:2181(CONNECTED) 62] create -e /root/node "22" Node already exists: /root/node [zk: localhost:2181(CONNECTED) 63] create -e -s /root/node "22" # 临时有序节点 Created /root/node0000000002 [zk: localhost:2181(CONNECTED) 64] create -e -s /root/node "22" Created /root/node0000000003 [zk: localhost:2181(CONNECTED) 65] create -e -s /root/node "22" Created /root/node0000000004 [zk: localhost:2181(CONNECTED) 66] create -e -s /root/node "33" Created /root/node0000000005 [zk: localhost:2181(CONNECTED) 67] ls /root # 查看所有节点 [node, node0000000002, node0000000003, node0000000004, node0000000005]3.3. delete和deleteall命令
删除有两个命令:delete和deleteall
[zk: localhost:2181(CONNECTED) 8] ls /root [node-0000000006, node-0000000007, node-0000000008] [zk: localhost:2181(CONNECTED) 10] delete /root/node-0000000006 [zk: localhost:2181(CONNECTED) 11] deleteall /root [zk: localhost:2181(CONNECTED) 12] ls /root Node does not exist: /root3.4. set命令
修改节点数据值,有两个参数:-s(设置并显示节点状态)、-v(使用CAS设置数据,可以使用stat从dataVersion找到版本)
[zk: localhost:2181(CONNECTED) 13] create /root "11" Created /root [zk: localhost:2181(CONNECTED) 15] get /root 11 [zk: localhost:2181(CONNECTED) 16] set /root "22" [zk: localhost:2181(CONNECTED) 17] get /root 223.5. 其他命令
查看节点状态信息:stat
[zk: localhost:2181(CONNECTED) 18] stat /root cZxid = 0x3b ctime = Tue Sep 21 21:47:04 CST 2021 mZxid = 0x3c mtime = Tue Sep 21 21:47:26 CST 2021 pZxid = 0x3b cversion = 0 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 2 numChildren = 0
查看操作历史:history
[zk: localhost:2181(CONNECTED) 19] history 9 - delete /root/node- 10 - delete /root/node-0000000006 11 - deleteall /root 12 - ls /root 13 - create /root "11" 14 - ls /root 15 - get /root 16 - set /root "22" 17 - get /root 18 - stat /root 19 - history
推出操作:quit
[zk: localhost:2181(CONNECTED) 31] quit WATCHER:: WatchedEvent state:Closed type:None path:null 2021-09-21 23:31:15,799 [myid:] - INFO [main:ZooKeeper@1422] - Session: 0x1000abff6690001 closed 2021-09-21 23:31:15,799 [myid:] - INFO [main-EventThread:ClientCnxn$EventThread@524] - EventThread shut down for session: 0x1000abff6690001四. 权限控制
zk类似于unix文件系统,节点类比文件,客户端可以删除节点,创建节点,修改节点。zk可以使用ACL(access control list)访问控制列表来对节点的权限进行控制。
4.1. ACL概述ACL权限控制包含三个方面:
-
权限模式(scheme):授权的策略
-
权限对象(id):权限的对象
-
权限(permission):授予的权限
ACL授权注意点:
-
zk的权限控制是基于znode节点的,需要对每个节点设置权限
-
每个znode支持设置多种权限控制方案和多个权限
-
子节点不会继承父节点权限,客户端无法访问某一个节点,但是可以访问他的子节点
权限模式的种类:
-
world:授权对象是anyone,表示登录到服务器的所有客户端都能对该节点执行某种权限
-
ip:对客户端进行IP认证
-
auth:用已经认证后的用户进行认证
-
digest:账号密码认证
权限类型:
-
read:简写r,读取节点及显示子节点列表的权限
-
write:简写w,写权限
-
create:简写c,创建权限
-
delete:简写d,删除权限
-
admin:简写a,设置ACL的权限
查看节点ACL权限信息:
[zk: localhost:2181(CONNECTED) 1] create /root "admin" Created /root [zk: localhost:2181(CONNECTED) 2] getAcl /root 'world,'anyone : cdrwa [zk: localhost:2181(CONNECTED) 3]
可以看到默认节点的ACL是:world:anyone:cdrwa
设置节点的ACL权限:
设置world权限:取消节点读取权限,当通过get获取数据时会发生异常
[zk: localhost:2181(CONNECTED) 1] create /root "admin" Created /root [zk: localhost:2181(CONNECTED) 2] getAcl /root 'world,'anyone : cdrwa [zk: localhost:2181(CONNECTED) 4] setAcl /root world:anyone:cdwa [zk: localhost:2181(CONNECTED) 5] get /root org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /root [zk: localhost:2181(CONNECTED) 6]
设置IP权限:设置之后只能由这个IP访问
[zk: localhost:2181(CONNECTED) 6] create /node "1" Created /node [zk: localhost:2181(CONNECTED) 7] setAcl /node ip:192.168.0.117:adc [zk: localhost:2181(CONNECTED) 8] get /node org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /node
设置digest权限:基于账号密码的授权模式,这里的密码只能使用加密之后的密码
# 先创建一个账号密码 ➜ ~ echo -n root:123456 | openssl dgst -binary -sha1 | openssl base64 u53OoA8hprX59uwFsvQBS3QuI00= [zk: localhost:2181(CONNECTED) 3] create /lala "1" Created /lala [zk: localhost:2181(CONNECTED) 4] setAcl /lala digest:root:u53OoA8hprX59uwFsvQBS3QuI00=:adcwr [zk: localhost:2181(CONNECTED) 5] getAcl /lala Insufficient permission : /lala [zk: localhost:2181(CONNECTED) 6] addauth digest root:123456 [zk: localhost:2181(CONNECTED) 7] get /lala 1 [zk: localhost:2181(CONNECTED) 8] getAcl /lala 'digest,'root:u53OoA8hprX59uwFsvQBS3QuI00= : cdrwa [zk: localhost:2181(CONNECTED) 9]
设置auth授权模式:
[zk: localhost:2181(CONNECTED) 0] create /aaa "1" Created /aaa [zk: localhost:2181(CONNECTED) 1] addauth digest admin:123456 [zk: localhost:2181(CONNECTED) 2] setAcl /aaa auth:admin:adcwr [zk: localhost:2181(CONNECTED) 3] get /aaa 1 [zk: localhost:2181(CONNECTED) 4] getAcl /aaa 'digest,'admin:0uek/hZ/V9fgiM35b0Z2226acMQ= : cdrwa [zk: localhost:2181(CONNECTED) 5]
退出之后重新登录:
[zk: localhost:2181(CONNECTED) 0] get /aaa org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /aaa [zk: localhost:2181(CONNECTED) 1] addauth digest admin:123456 [zk: localhost:2181(CONNECTED) 2] get /aaa 1 [zk: localhost:2181(CONNECTED) 3]
删除节点ACL权限:
取消的话,这是默认权限就可以了。
[zk: localhost:2181(CONNECTED) 7] get /lala 1 [zk: localhost:2181(CONNECTED) 8] getAcl /lala 'digest,'root:u53OoA8hprX59uwFsvQBS3QuI00= : cdrwa [zk: localhost:2181(CONNECTED) 9] setAcl /lala world:anyone:cdwar [zk: localhost:2181(CONNECTED) 10] get /lala 1 [zk: localhost:2181(CONNECTED) 11]五. 监听机制
zookeeper提供了数据的发布/订阅功能,多个订阅者可以同时监听某一特定数据节点,当数据节点自身发生变化,会主动通知所有订阅者。当前通知时异步通知,客户端不必再watcher注册后轮询阻塞,从而减轻了客户端压力,可以看做是观察者模型的实现。
5.1. 特点-
一次性:监听是一次性的,一旦触发就会移除,再次使用时需要重新注册
-
顺序回调:回调是顺序串行执行
-
轻量级:结构上
-
时效性:监听只有在当前session失效时才会无效,如果在seesion有效期内快速重新连接成功,仍可以接收到通知
-
当客户端启动之后会创建两个线程,一个负责网络连接通信(connet),另一个负责监听(listener)。
-
通过connect线程将注册的监听事件发生给zookeeper
-
在zookeeper的注册监听器列表中将注册的监听事件添加到列表中
-
zookeeper监听到有数据或路径变化,就会将这个消息发送给listener线程
-
listener线程内部调用了process()方法。
一次性监听,触发后会被删除,无法再次触发。
| 命令 | 描述 |
|---|---|
| ls -w path | 监听子节点的变化(增、删)[监听目录] |
| get -w path | 监听节点数据的变化 |
| stat -w path | 监听节点属性的变化 |
# 写数据节点 [zk: localhost:2181(CONNECTED) 5] create /root Created /root [zk: localhost:2181(CONNECTED) 14] set /root "77" # 在另一个监听节点 [zk: localhost:2181(CONNECTED) 8] get -w /root null [zk: localhost:2181(CONNECTED) 9] WATCHER:: WatchedEvent state:SyncConnected type:NodeDataChanged path:/root5.3.2. 监听子节点变化
# 写数据节点 [zk: localhost:2181(CONNECTED) 16] create /root Created /root [zk: localhost:2181(CONNECTED) 17] create /root/node1 "123" Created /root/node1 # 在另一个节点监听 [zk: localhost:2181(CONNECTED) 10] ls -w /root [node1, node2] [zk: localhost:2181(CONNECTED) 11] WATCHER:: WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/root5.3.3. 永久监听
在Zookeeper 3.6.0版本之后,客户端可以在节点上创建永久监听,永久监听在被触发后不会被删除。
六. 客户端API操作下面演示一个Java对zk的CRUD操作,并简单介绍一下API参数。
6.1. 准备工作搭建一个简单的maven项目,添加下面依赖:
org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-devtools runtime true org.springframework.boot spring-boot-configuration-processor true org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test org.apache.zookeeper zookeeper 3.7.0 org.slf4j slf4j-log4j12
增加配置项:
zookeeper.address=192.168.0.117:2181 zookeeper.timeout=4000
增加获取连接的Bean:
@Slf4j
@Data
@Configuration
@ConfigurationProperties(prefix = "zookeeper")
public class ZkConfig {
private String address;
private Integer timeout;
@Bean(name = "zookeeperConn")
public ZooKeeper zookeeperClient() {
ZooKeeper zooKeeper = null;
try {
CountDownLatch countDownLatch = new CountDownLatch(1);
zooKeeper = new ZooKeeper(address, timeout, (event) -> {
if (Watcher.Event.KeeperState.SyncConnected == event.getState()) {
countDownLatch.countDown();
}
});
countDownLatch.await();
log.info("初始化zk连接:{}", zooKeeper.getState());
} catch (IOException | InterruptedException e) {
log.info("获取ZK连接失败: {}", e.getMessage());
}
return zooKeeper;
}
}
这样准备工作就做好了,下面开始CRUD大法!
6.2. 创建节点创建方法多种大体分页两类:同步创建和异步创建。
// 同步创建 String create(String path, byte[] data, Listacl, CreateMode createMode); String create(String path, byte[] data, List acl, CreateMode createMode, Stat stat); String create(String path, byte[] data, List acl, CreateMode createMode, Stat stat, long ttl); // 异步创建 void create(String path, byte[] data, List acl, CreateMode createMode, AsyncCallback.Create2Callback cb, Object ctx); void create(String path, byte[] data, List acl, CreateMode createMode, AsyncCallback.StringCallback cb, Object ctx); void create(String path, byte[] data, List acl, CreateMode createMode, AsyncCallback.Create2Callback cb, Object ctx, long ttl);
这里需要看一下CreateMode这个枚举类,定义了节点类型:
public enum CreateMode {
PERSISTENT(0, false, false, false, false),
PERSISTENT_SEQUENTIAL(2, false, true, false, false),
EPHEMERAL(1, true, false, false, false),
EPHEMERAL_SEQUENTIAL(3, true, true, false, false),
CONTAINER(4, false, false, true, false),
PERSISTENT_WITH_TTL(5, false, false, false, true),
PERSISTENT_SEQUENTIAL_WITH_TTL(6, false, true, false, true);
}
这里一共有七种类型:
-
PERSISTENT:持久节点(也有叫永久节点的),不会随着会话的结束而自动删除
-
PERSISTENT_SEQUENTIAL:带有递增序号的持久节点,不会随着会话的结束而自动删除。
-
EPHEMERAL:临时节点,会随着会话的结束而自动删除
-
EPHEMERAL_SEQUENTIAL:带有递增序号的临时节点,会随着会话的结束而自动删除。
-
CONTAINER:容器节点,用于Leader、Lock等特殊用途,当容器节点不存在任何子节点时,容器将成为服务器在将来某个时候删除的候选节点。
-
PERSISTENT_WITH_TTL:带TTL(time-to-live,存活时间)的持久节点,节点在TTL时间之内没有得到更新并且没有子节点,就会被自动删除。
-
PERSISTENT_SEQUENTIAL_WITH_TTL:带TTL(time-to-live,存活时间)和递增序号的持久节点,节点在TTL时间之内没有得到更新并且没有子节点,就会被自动删除。
创建节点时候需要注意:
-
如果指令路径和版本的节点已经存在,则会抛出一个KeeperException异常
-
临时节点不能有子节点,如果给临时节点创建子节点会抛KeeperException异常。
-
临时节点的生命周期与客户端会话绑定。一旦客户端会话失效(客户端与 Zookeeper连接断开不一定会话失效),那么这个客户端创建的所有临时节点都会被移除。
-
byte[] data允许的最大数据量为1MB(1,048,576 bytes),如果超过,会抛KeeperExecption。
@GetMapping("create")
public String create(@RequestParam("path") String path, @RequestParam("data") String data) {
try {
return "创建成功";
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
return "创建失败";
}
}
其他的操作看zk的api接口就可以了!
6.6. 监听器监听主要是针对节点而言,前面在判断节点是否存在、修改数据时都可以设置监听器,但是他们是一次性的,如果我们希望长久有效,则可以使用下面的addWatch
@Slf4j
@Data
@Configuration
@ConfigurationProperties(prefix = "zookeeper")
public class ZkConfig {
private String address;
private Integer timeout;
@Bean(name = "zookeeperConn")
public ZooKeeper zookeeperClient() {
ZooKeeper zooKeeper = null;
try {
CountDownLatch countDownLatch = new CountDownLatch(1);
zooKeeper = new ZooKeeper(address, timeout, (event) -> {
if (Watcher.Event.KeeperState.SyncConnected == event.getState()) {
countDownLatch.countDown();
}
});
countDownLatch.await();
// 增加部分
zooKeeper.addWatch("/six",
event -> log.info("监听到节点变化: {}", event.getState()),
AddWatchMode.PERSISTENT_RECURSIVE);
log.info("初始化zk连接:{}", zooKeeper.getState());
} catch (IOException | InterruptedException | KeeperException e) {
log.info("获取ZK连接失败: {}", e.getMessage());
}
return zooKeeper;
}
}
监听模式有两中种:
-
AddWatchMode.PERSISTENT: 表示只关心当前节点的删除、数据变更,创建,一级子节点的创建、删除;无法感知子节点的子节点创建、删除,无法感知子节点的数据变更
-
AddWatchMode.PERSISTENT_RECURSIVE: 相当于递归监听,改节点及其子节点的所有变更都监听
另外在zk还可以在exists中增加监听器,但是是一次性的:
public Stat exists(final String path, Watcher watcher)七. 写数据的流程
第一步:客户端发出写入数据请求给集群中的任意一个server,如果当前server是follower,follower会将写请求转发给leader;
第二步:leader会将数据广播follower中,leader采用两个阶段提交方式,先发送Propose(事务)广播给follower,follower接收到Propose消息,写入日志成功之后,返回ack消息给leader;
第三步:当leader收到半数以上ack消息,返回成功返回给客户端,并广播commit请求给follower
第四步:zk会进一步通知客户端写成功了。
下面推荐几个常用的zookeeper客户端可视化工具:
| 客户端 | 地址 |
|---|---|
| PrettyZoo | https://github.com/vran-dev/PrettyZoo |
| zk-view-tool | https://github.com/yangyuscript/zk-view-tool |
| zookeeper-visualizer | https://github.com/yuanhongxiang/zookeeper-visualizer |
| idea插件 | zookeeper tools |
推荐PrettyZoo,JavaFx使用的,挺好看的,颜值党,哈哈!



