一. 客户端启动

进去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有先后顺序。
ctimeznode被创建的毫秒数(1970年开始)
mZxidznode最后更新的事务zxid
mtimeznode最后修改的毫秒数(1970年开始)
pZxidznode最后更新的子节点zxid
cversionznode子节点变化号,znode子节点修改次数
dataVersionznode数据变化号
aclVersionznode访问访问控制表的变化号
ephemeralOwner如果是临时节点,这个是znode拥有者的session id。如果不是临时节点则是0
dataLengthznode的数据长度
numChildrenznode的子节点数量

get命令可以获取节点内容:

有两个参数:-s(获取节点数据以及节点信息)、-w(监听节点数据变化)

[zk: localhost:2181(CONNECTED) 13] create /root "11"
Created /root
[zk: localhost:2181(CONNECTED) 15] get /root
11

3.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命令

删除有两个命令:deletedeleteall

[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: /root

3.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
22

3.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的权限

4.2. 命令操作

查看节点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有效期内快速重新连接成功,仍可以接收到通知

5.2. 监听流程

  • 当客户端启动之后会创建两个线程,一个负责网络连接通信(connet),另一个负责监听(listener)。

  • 通过connect线程将注册的监听事件发生给zookeeper

  • zookeeper的注册监听器列表中将注册的监听事件添加到列表中

  • zookeeper监听到有数据或路径变化,就会将这个消息发送给listener线程

  • listener线程内部调用了process()方法。

5.3. 客户端演示

一次性监听,触发后会被删除,无法再次触发。

命令描述
ls -w path监听子节点的变化(增、删)[监听目录]
get -w path监听节点数据的变化
stat -w path监听节点属性的变化
5.3.1. 监听节点变化
# 写数据节点
[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:/root

5.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:/root

5.3.3. 永久监听

在Zookeeper 3.6.0版本之后,客户端可以在节点上创建永久监听,永久监听在被触发后不会被删除。

六. 客户端API操作

下面演示一个Java对zk的CRUD操作,并简单介绍一下API参数。

6.1. 准备工作

搭建一个简单的maven项目,添加下面依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.apache.zookeeper</groupId>
        <artifactId>zookeeper</artifactId>
        <version>3.7.0</version>
        <exclusions>
            <!-- 去掉日志避免和springboot中的产生冲突 -->
            <exclusion>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-log4j12</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

增加配置项:

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, List<ACL> acl, CreateMode createMode);
String create(String path, byte[] data, List<ACL> acl, CreateMode createMode, Stat stat);
String   create(String path, byte[] data, List<ACL> acl, CreateMode createMode, Stat stat, long ttl);

// 异步创建
void create(String path, byte[] data, List<ACL> acl, CreateMode createMode, AsyncCallback.Create2Callback cb, Object ctx);
void create(String path, byte[] data, List<ACL> acl, CreateMode createMode, AsyncCallback.StringCallback cb, Object ctx);
void create(String path, byte[] data, List<ACL> 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客户端可视化工具:

客户端地址
PrettyZoohttps://github.com/vran-dev/PrettyZoo
zk-view-toolhttps://github.com/yangyuscript/zk-view-tool
zookeeper-visualizerhttps://github.com/yuanhongxiang/zookeeper-visualizer
idea插件zookeeper tools

推荐PrettyZoo,JavaFx使用的,挺好看的,颜值党,哈哈!

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐