深入学习zookeeper(一)

ZooKeeper的下载与安装(Windows)

下载地址:https://zookeeper.apache.org/releases.html#download

在这里插入图片描述
在这里插入图片描述
解压到常用目录下面即可;

在这里插入图片描述
修改bin目录下的zkEnv.cmd文件配置(也可以不修改此文件,直接将conf目录下的zoo_sample.cfg文件,复制一份,重命名为zoo.cfg就行):

//修改前
set ZOOCFG=%ZOOCFGDIR%\zoo.cfg

//修改后
set ZOOCFG=%ZOOCFGDIR%\zoo_sample.cfg

安装目录下新建一个空的data文件夹和log文件夹,然后再修改zoo_sample.cfg配置文件,添加如下配置:

//修改前
dataDir=/tmp/zookeeper

//修改后
dataDir=E:/apache-zookeeper-3.6.2-bin/data
dataLogDir=E:/apache-zookeeper-3.6.2-bin/log

双击bin目录下zkServer.cmd启动,如果启动闪退,则在zkServer.cmd文件末尾添加pause 。这样运行出错就不会退出,会提示错误信息,方便找到原因;
在这里插入图片描述
比如我的报错是环境变量JAVA_HOME没有配置,那么只需加上变量配置即可!

在这里插入图片描述
如果启动又报ZooKeeper audit is disabled的话,只需要在zoo_sample.cfg文件末尾加上audit.enable=true配置即可!

audit.enable=true表示开启审查日志,在zookeeper启动的时候会在目录生成logs文件夹(zkEnv.cmd里面有配置logs生成路径),里面会生成一个zookeeper_audit.log文件日志,该日志会记录所有相关改的操作日志!

如果不开启的话就不会记录对应日志,不影响其它操作使用!
在这里插入图片描述
重新启动服务:

在这里插入图片描述
检查服务是否启动,可以通过 netstat -ano 命令查看是否有你配置的 clientPort 端口号在监听服务,也可以通过双击执行zkCli.cmd来判定是否启动成功;
在这里插入图片描述
在这里插入图片描述

什么是ZooKeeper

zooKeeper是一个经典的分布式数据一致性解决方案,致力于为分布式应用提供一个高性能、高可用,且具有严格顺序访问控制能力的分布式协调存储服务:

  • 维护配置信息;
  • 分布式锁服务;
  • 集群管理;
  • 生成分布式唯一ID;
  • 服务注册中心

1、维护配置信息

java编程经常会遇到配置项,比如数据库的url、schema、user和password等。通常这些配置项我们会放置在配置文件中,再将配置文件放置在服务器上,当需要更改配置项时,需要去服务器上修改对应的配置文件。

但是随着分布式系统的兴起,通常会将配置文件部署在一个集群上,然而一个集群动辄上千台服务器,此时如果再一台台服务器逐个修改配置文件那将是非常繁琐且危险的的操作,因此就需要一种服务,能够高效快速且可靠地完成配置项的更改等操作,并能够保证各配置项在每台服务器上的数据一致性。

zookeeper就可以提供这样一种服务!
在这里插入图片描述

2、分布式锁服务

一个集群是一个分布式系统,由多台服务器组成。为了提高并发度和可靠性,多台服务器上运行着同一种服务。当多个服务在运行时就需要协调各服务的进度,有时候需要保证当某个服务在进行某个操作时,其他的服务都不能进行该操作,即对该操作进行加锁,如果当前机器挂掉后,释放锁并fail over 到其他的机器继续执行该服务。
在这里插入图片描述

3、集群管理

一个集群有时会因为各种软硬件故障或者网络故障,出现某些服务器挂掉而被移除集群,而某些服务器加入到集群中的情况,zookeeper会将这些服务器加入/移出的情况通知给集群中的其他正常工作的服务器,以及时调整存储和计算等任务的分配和执行等。此外zookeeper还会对故障的服务器做出诊断并尝试修复。
在这里插入图片描述

4、生成分布式唯一ID

在过去的单库单表型系统中,通常可以使用数据库字段自带的auto_increment属性来自动为每条记录生成一个唯一的ID。但是分库分表后,就无法在依靠数据库的auto_increment属性来唯一标识一条记录了。

此时我们就可以用zookeeper在分布式环境下生成全局唯一ID。做法如下:每次要生成一个新Id时,创建一个持久顺序节点,创建操作返回的节点序号,即为新Id,然后把比自己节点小的删除即可。

5、服务注册中心

Zookeeper 可以充当一个服务注册表(Service Registry),让多个服务提供者形成一个集群,让服务消费者通过服务注册表获取具体的服务访问地址(IP+端口)去访问具体的服务提供者;

ZooKeeper的优点

  1. 高性能:zookeeper将全量数据存储在内存中,并直接服务于客户端的所有非事务请求,尤其适用于以读为主的应用场景;
  2. 高可用:zookeeper一般以集群的方式对外提供服务,一般3 ~ 5台机器就可以组成一个可用的zookeeper集群了,每台机器都会在内存中维护当前的服务器状态,并且每台机器之间都相互保持着通信。只要集群中超过一半的机器都能够正常工作,那么整个集群就能够正常对外服务;
  3. 严格顺序访问:对于来自客户端的每个更新请求,zookeeper都会分配一个全局唯一的递增编号,这个编号反映了所有事务操作的先后顺序;

ZooKeeper的数据模型

zookeeper的数据节点可以视为树状结构(或者目录),树中的各节点被称为znode(即zookeeper node),一个znode可以有多个子节点。可以使用路径path来定位某个znode。

在这里插入图片描述
一个znode大体上分为3各部分:

  1. 节点的数据:即znode data(节点path, 节点data)的关系就像是java map中(key,value)的关系
  2. 节点的子节点children
  3. 节点的状态stat:用来描述当前节点的创建、修改记录,包括cZxid、ctime等

节点stat各个属性如下表:

状态属性说明
cZxid数据节点创建时的事务 ID
ctime数据节点创建时的时间
mZxid数据节点最后一次更新时的事务 ID
mtime数据节点最后一次更新时的时间
pZxid数据节点的子节点最后一次被修改时的事务 ID
cversion子节点的更改次数
dataVersion节点数据的更改次数
aclVersion节点的 ACL 的更改次数
ephemeralOwner如果节点是临时节点,则表示创建该节点的会话的SessionID;如果节点是持久节点,则该属性值为 0
dataLength数据内容的长度
numChildren数据节点当前的子节点个数

zookeeper中的节点有两种,分别为临时节点和永久节点。节点的类型在创建时即被确定,并且不能改变:

  1. 临时节点:该节点的生命周期依赖于创建它们的会话。一旦会话(Session)结束,临时节点将被自动删除,当然可以也可以手动删除。另外,ZooKeeper的临时节点不允许拥有子节点
  2. 持久化节点:该节点的生命周期不依赖于会话,并且只有在客户端显示执行删除操作的时候,他们才能被删除

zookeeper常用操作

1、新增节点

创建持久化节点并写入数据:

create [-s] [-e] path data #其中-s 为有序节点,-e 临时节点

#示例
create /node "node"	 

创建持久化有序节点,此时创建的节点名为指定节点名 + 自增序号:

[zk: localhost:2181(CONNECTED) 2] create -s /a "aaa"
Created /a0000000000
[zk: localhost:2181(CONNECTED) 3] create -s /b "bbb"
Created /b0000000001
[zk: localhost:2181(CONNECTED) 4] create -s /c "ccc"
Created /c0000000002

创建临时节点,临时节点会在会话过期后被删除:

[zk: localhost:2181(CONNECTED) 5] create -e /tmp "tmp"
Created /tmp

创建临时有序节点,临时节点会在会话过期后被删除:

[zk: localhost:2181(CONNECTED) 6] create -s -e /aa 'aaa'
Created /aa0000000004
[zk: localhost:2181(CONNECTED) 7] create -s -e /bb 'bbb'
Created /bb0000000005
[zk: localhost:2181(CONNECTED) 8] create -s -e /cc 'ccc'
Created /cc0000000006

2、更新节点

更新节点的命令是 set ,可以直接进行修改,如下:

[zk: localhost:2181(CONNECTED) 12] create /node2 "node2"
Created /node2
[zk: localhost:2181(CONNECTED) 13] set /node2 "node22"
[zk: localhost:2181(CONNECTED) 14] get /node2
node22

也可以基于版本号进行更改,当你传入的数据版本号(dataVersion) 和当前节点的数据版本号不符合时,zookeeper 会拒绝本次修改,指定版本号更新需要加上 -v 选项:

[zk: localhost:2181(CONNECTED) 27] set /node2 "node2" -v 1
version No is not valid : /node2

注意:在老版本的zookeeper中,使用命令:set path data version 可以直接根据版本号更新。但在新版本的zookeeper中,指定版本号更新需要加上 -v 选项;

3、删除节点

删除节点的语法如下:

delete path [version]

和更新节点数据一样,也可以传入版本号,当你传入的数据版本号 (dataVersion)和当前节点的数据版本号不符合时,zookeeper 不会执行删除操作;

[zk: localhost:2181(CONNECTED) 32] delete /node2 -v 1
version No is not valid : /node2
[zk: localhost:2181(CONNECTED) 33] delete /node2
[zk: localhost:2181(CONNECTED) 34]

要想删除某个节点及其所有后代节点,可以使用递归删除,命令为:deleteall path;

[zk: localhost:2181(CONNECTED) 42] deleteall /node2
[zk: localhost:2181(CONNECTED) 43] get /node2
org.apache.zookeeper.KeeperException$NoNodeException: KeeperErrorCode = NoNode for /node2

注意:有的文章或者教程,递归删除的命令是:rmr path,但我本地执行报命令未找到。输入help 查看帮助发现一个命令 deleteall 可以删除。估计是新版本已经作废rmr命令转而改为deleteall 了吧!

4、查看节点

get path

[zk: localhost:2181(CONNECTED) 47] get /node3
node3
[zk: localhost:2181(CONNECTED) 6] create -s /node90 "node90"
Created /node900000000105
[zk: localhost:2181(CONNECTED) 9] get /node900000000105
node90

如果是查看有序节点,则path参数为创建有序节点返回的值!

5、查看节点状态

可以使用 stat 命令查看节点状态:

[zk: localhost:2181(CONNECTED) 48] stat /node3
cZxid = 0x1a
ctime = Mon Mar 15 21:56:10 CST 2021
mZxid = 0x1b
mtime = Mon Mar 15 21:56:26 CST 2021
pZxid = 0x1a
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0

具体的属性释义可以在上面数据模型小节里面查看具体对应的意思!

6、查看节点列表

可以使用 ls path 命令查看节点列表:

[zk: localhost:2181(CONNECTED) 49] create /node1 "node1"
Created /node1
[zk: localhost:2181(CONNECTED) 50] create /node1/node11 "node11"
Created /node1/node11
[zk: localhost:2181(CONNECTED) 51] ls /node1
[node11]

7、监听器get -w path

使用 get -w path 注册的监听器能够在节点内容发生改变的时候,向客户端发出通知。需要注意的是 zookeeper 的触发器是一次性的 (One-time trigger),即触发一次后就会立即失效;

[zk: localhost:2181(CONNECTED) 1] get /node1 watch
'get path [watch]' has been deprecated. Please use 'get [-s] [-w] path' instead.
node1
[zk: localhost:2181(CONNECTED) 2] get -w /node1
node1
[zk: localhost:2181(CONNECTED) 3] set /node1 "node2"

WATCHER::

WatchedEvent state:SyncConnected type:NodeDataChanged path:/node1

可以发现老版本的命令:get path [watch],已经遗弃了。后续watch相关操作命令都改为-w!

8、监听器stat -w path

[zk: localhost:2181(CONNECTED) 5] stat -w /node1
cZxid = 0x1c
ctime = Mon Mar 15 22:00:03 CST 2021
mZxid = 0x20
mtime = Tue Mar 16 15:32:19 CST 2021
pZxid = 0x1d
cversion = 1
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 1
[zk: localhost:2181(CONNECTED) 6] set /node1 "node3"

WATCHER::

WatchedEvent state:SyncConnected type:NodeDataChanged path:/node1

可以发现老版本的命令:stat path [watch],已经遗弃了。

9、监听器ls -w path

使用 ls -w path 注册的监听器能够监听该节点下所有子节点的增加和删除操作

[zk: localhost:2181(CONNECTED) 9] ls -w /node1
[node11]
[zk: localhost:2181(CONNECTED) 10] create /node1/node5 "node5"

WATCHER::
Created /node1/node5
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/node1

zookeeper的acl权限控制

acl 权限控制,使用scheme:id:permission 来标识,主要涵盖 3 个方面:

  1. 权限模式(scheme):授权的策略
  2. 授权对象(id):授权的对象
  3. 权限(permission):授予的权限

zooKeeper的权限控制是基于每个znode节点的,需要对每个节点设置权限。每个znode支持设置多种权限控制方案和多个权限。

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

1、权限模式

采用何种方式授权,主要有以下4种方案:

方案描述
world只有一个用户:anyone,代表登录zokeeper所有人(默认)
ip对客户端使用IP地址认证
auth使用已添加认证的用户认证
digest使用“用户名:密码”方式认证

2、授权对象

权限赋予的实体,例如:IP 地址或用户;

3、权限

create、delete、read、writer、admin也就是 增、删、改、查、管理权限,这5种权限简写为cdrwa。

注意:这5种权限中,delete是指对子节点的删除权限,其它4种权限指对自身节点的操作权限;

权限ACL简写描述
createc可以创建子节点
deleted可以删除子节点(仅下一级节点)
readr可以读取节点数据及显示子节点列表
writew可以设置节点数据
admina可以设置节点访问控制列表权限

4、授权的相关命令

命令使用方式描述
getAclgetAcl读取ACL权限
setAclsetAcl设置ACL权限
addauthaddauth添加认证用户

5、案例

world授权模式:

setAcl <path> world:anyone:<acl>

[zk: localhost:2181(CONNECTED) 11] setAcl /node1 world:anyone:cdrwa
[zk: localhost:2181(CONNECTED) 12] getAcl /node1
'world,'anyone
: cdrwa

IP授权模式:

setAcl <path> ip:<ip>:<acl>

[zk: localhost:2181(CONNECTED) 18] create /node2 "node2"
Created /node2
[zk: localhost:2181(CONNECTED) 23] setAcl /node2 ip:192.168.60.129:cdrwa
[zk: localhost:2181(CONNECTED) 25] getAcl /node2
'ip,'192.168.60.129
: cdrwa
#使用IP非 192.168.60.129 的机器
[zk: localhost:2181(CONNECTED) 0] get /node2
Authentication is not valid : /node2 #没有权限

Auth授权模式:

addauth digest <user>:<password> #添加认证用户
setAcl <path> auth:<user>:<acl>

[zk: localhost:2181(CONNECTED) 22] addauth digest yd:123456
[zk: localhost:2181(CONNECTED) 24] create /node9 "node9"
Created /node9
[zk: localhost:2181(CONNECTED) 25] getAcl /node9
'world,'anyone
: cdrwa
[zk: localhost:2181(CONNECTED) 26] setAcl /node9 auth:yd:cdra
[zk: localhost:2181(CONNECTED) 27] getAcl /node9
'digest,'yd:77OhL6kqaNVF3TfPfa7dMB1d96s=
: cdra
[zk: localhost:2181(CONNECTED) 28] set /node9 "node99"
Authentication is not valid : /node9

Digest授权模式:

setAcl <path> digest:<user>:<password>:<acl>

这里的密码是经过SHA1及BASE64处理的密文,在SHELL中可以通过以下命令计算:

echo -n <user>:<password> | openssl dgst -binary -sha1 | openssl base64

先来计算一个密文(我本机没有装linux,用的git Bash代替)

在这里插入图片描述
示例:

[zk: localhost:2181(CONNECTED) 29] create /node100 "node100"
Created /node100
[zk: localhost:2181(CONNECTED) 30] setAcl /node100 digest:yd:77OhL6kqaNVF3TfPfa7dMB1d96s=:cdrwa
[zk: localhost:2181(CONNECTED) 31] getAcl /node100
'digest,'yd:77OhL6kqaNVF3TfPfa7dMB1d96s=
: cdrwa

多种模式授权:

[zk: localhost:2181(CONNECTED) 32] create /node200 "node200"
Created /node200
[zk: localhost:2181(CONNECTED) 33] addauth digest yd:123456
[zk: localhost:2181(CONNECTED) 39] setAcl /node200 ip:192.168.1.128:cdra,auth:yd:cdrwa,digest:yd:77OhL6kqaNVF3TfPfa7dMB1
d96s=:cdrwa
[zk: localhost:2181(CONNECTED) 40] getAcl /node200
'ip,'192.168.1.128
: cdra
'digest,'yd:77OhL6kqaNVF3TfPfa7dMB1d96s=
: cdrwa
'digest,'yd:77OhL6kqaNVF3TfPfa7dMB1d96s=
: cdrwa

6、acl 超级管理员

zookeeper的权限管理模式有一种叫做super,该模式提供一个超管可以方便的访问任何权限的节点

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

echo -n super:admin | openssl dgst -binary -sha1 | openssl base64

那么打开zookeeper目录下的/bin/zkServer.sh服务器脚本文件,找到如下一行:

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

这就是脚本中启动zookeeper的命令,默认只有以上两个配置项,我们需要加一个超管的配置项

"-
Dzookeeper.DigestAuthenticationProvider.superDigest=super:xQJmxLMiHGwaqBv
st5y6rkB6HQs="

那么修改以后这条完整命令变成了

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

之后启动zookeeper,输入如下命令添加权限

addauth digest super:admin #添加认证用户

zookeeper javaAPI

znode是zooKeeper集合的核心组件,zookeeper API提供了一小组方法使用zookeeper集合来操纵znode的所有细节;

1、连接到ZooKeeper

ZooKeeper(String connectionString, int sessionTimeout, Watcher watcher)

对应参数释义:

  • connectionString:zookeeper主机
  • sessionTimeout:会话超时(以毫秒为单位)
  • watcher:实现“监视器”对象。zookeeper集合通过监视器对象返回连接状态
public class ZookeeperConnection {
    public static void main(String[] args) {
        try {
            /**
             * 计数器对象,由于zk客户端与服务器连接是异步操作,当zk服务端响应连接完成,客户端和服务器才算真正的连接上。连接完成时
             * zk服务会通过Watcher机制,通知到zk的客户端
             */
            CountDownLatch countDownLatch=new CountDownLatch(1);
            // arg1:服务器的ip和端口,arg2:客户端与服务器之间的会话超时时间(以毫秒为单位的),arg3:监视器对象
            ZooKeeper zooKeeper=new ZooKeeper("127.0.0.1:2181", 5000, event -> {
                if(event.getState()== Watcher.Event.KeeperState.SyncConnected) {
                    System.out.println("连接创建成功!");
                    countDownLatch.countDown();
                }
            });
            // 主线程阻塞等待连接对象的创建成功,如果不阻塞,等zk连上通知客户端的时候,客户端已经走完了
            countDownLatch.await();
            // 会话编号
            System.out.println(zooKeeper.getSessionId());
            zooKeeper.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

2、新增节点

// 同步方式
create(String path, byte[] data, List<ACL> acl, CreateMode createMode)
// 异步方式
create(String path, byte[] data, List<ACL> acl, CreateMode createMode,
AsyncCallback.StringCallback callBack,Object ctx)

对应参数释义:

  • path:znode路径。例如,/node1 /node1/node11
  • data:要存储在指定znode路径中的数据
  • acl:要创建的节点的访问控制列表。zookeeper API提供了一个静态接口ZooDefs.Ids 来获取一些基本的acl列表
  • createMode:节点的类型,这是一个枚举
  • callBack:异步回调接口
  • ctx:传递上下文参数

示例(连接部分代码省略):

    @Test
    public void create1()throws Exception{
        // arg1:节点的路径
        // arg2:节点的数据
        // arg3:权限列表  Ids.OPEN_ACL_UNSAFE--world:anyone:cdrwa
        // arg4:节点类型  PERSISTENT--持久化节点,PERSISTENT_SEQUENTIAL--持久化顺序节点,EPHEMERAL--临时节点,EPHEMERAL_SEQUENTIAL--临时顺序节点
        zooKeeper.create("/create/node1","node1".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    }

    @Test
    public void create2() throws Exception {
        // 权限列表
        List<ACL> acls = new ArrayList<ACL>();
        // world授权模式和授权对象
        Id id = new Id("world", "anyone");
        // ip授权模式授权模式和授权对象
        //Id id = new Id("ip", "127.0.0.1");
        // 权限设置
        acls.add(new ACL(ZooDefs.Perms.READ, id));
        acls.add(new ACL(ZooDefs.Perms.WRITE, id));
        zooKeeper.create("/create/node2", "node2".getBytes(), acls, CreateMode.PERSISTENT);
    }

    @Test
    public void create3() throws Exception {
        // 添加授权用户
        zooKeeper.addAuthInfo("digest", "yd:123456".getBytes());
        // 权限列表
        List<ACL> acls = new ArrayList<ACL>();
        // auth授权模式和授权对象
        Id id = new Id("auth", "yd");
        // digest授权模式和授权对象
        //Id id = new Id("digest", "yd:qlzQzCLKhBROghkooLvb+Mlwv4A=");
        // 权限设置
        acls.add(new ACL(ZooDefs.Perms.READ, id));
        zooKeeper.create("/create/node3", "node3".getBytes(), acls, CreateMode.PERSISTENT);
    }

    @Test
    public void create4() throws Exception {
        // 异步方式创建节点
        zooKeeper.create("/create/node4", "node4".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT, (rc, path, ctx, name) -> {
            // 0 代表创建成功
            System.out.println(rc);
            // 节点的路径
            System.out.println(path);
            // 节点的路径
            System.out.println(name);
            // 上下文参数
            System.out.println(ctx);

        }, "I am context");
        Thread.sleep(10000);
    }

3、更新节点

// 同步方式
setData(String path, byte[] data, int version)
// 异步方式
setData(String path, byte[] data, int version,AsyncCallback.StatCallback callBack, Object ctx)

其它参数释义都是一样的,可以见新增节点部分!

version:znode的当前版本。每当数据更改时,ZooKeeper会更新znode的版本号。

示例:

    @Test
    public void set1() throws Exception {
		// arg1:节点的路径,arg2:修改的数据,arg3:数据版本号 -1代表版本号不参与更新
        Stat stat = zooKeeper.setData("/set/node1", "node13".getBytes(), -1);
        // 当前节点的版本号
        System.out.println(stat.getVersion());

    }

    @Test
    public void set2() throws Exception {
        zooKeeper.setData("/set/node1", "node14".getBytes(), -1, (rc, path, ctx, stat) -> {
            // 0代表修改成功
            System.out.println(rc);
            // 节点的路径
            System.out.println(path);
            // 属性描述对象
            System.out.println(stat.getVersion());
        }, "I am Context");
        Thread.sleep(10000);
        System.out.println("结束");
    }

4、删除节点

// 同步方式
delete(String path, int version)
// 异步方式
delete(String path, int version, AsyncCallback.VoidCallback callBack,Object ctx)

参数说明见上面部分!

示例:

    @Test
    public void delete1() throws Exception {
		// arg1:删除节点的节点路径,arg2:数据版本信息 -1代表删除节点时不考虑版本信息
        zooKeeper.delete("/delete/node1",-1);
    }

    @Test
    public void delete2() throws Exception {
        // 异步使用方式
        zooKeeper.delete("/delete/node2", -1, (rc, path, ctx) -> {
            // 0代表删除成功
            System.out.println(rc);
            // 节点的路径
            System.out.println(path);
        },"I am Context");
        Thread.sleep(10000);
        System.out.println("结束");
    }

5、查看节点

// 同步方式
getData(String path, boolean b, Stat stat)
// 异步方式
getData(String path, boolean b,AsyncCallback.DataCallback callBack,Object ctx)

参数说明:

  • b:是否使用连接对象中注册的监视器
  • stat:返回znode的元数据

示例:

    @Test
    public void get1() throws Exception {
        // arg1:节点的路径
        // arg3:读取节点属性的对象
        Stat stat=new Stat();
        byte [] bys=zooKeeper.getData("/get/node1",false,stat);
        // 打印数据
        System.out.println(new String(bys));
    }

    @Test
    public void get2() throws Exception {
        //异步方式
        zooKeeper.getData("/get/node1", false, (rc, path, ctx, data, stat) -> {
            // 0代表读取成功
            System.out.println(rc);
            // 节点的路径
            System.out.println(path);
            // 数据
            System.out.println(new String(data));
            // 属性对象
            System.out.println(stat.getVersion());
        },"I am Context");
        Thread.sleep(10000);
    }

6、查看子节点

// 同步方式
getChildren(String path, boolean b)
// 异步方式
getChildren(String path, boolean b,AsyncCallback.ChildrenCallback callBack,Object ctx)

示例:

    @Test
    public void get1() throws Exception {
        // arg1:节点的路径
        List<String> list = zooKeeper.getChildren("/get", false);
        for (String str : list) {
            System.out.println(str);
        }
    }

    @Test
    public void get2() throws Exception {
        // 异步用法
        zooKeeper.getChildren("/get", false, (rc, path, ctx, children) -> {
            // 0代表读取成功
            System.out.println(rc);
            // 节点的路径
            System.out.println(path);
            // 子节点信息
            for (String str : children) {
                System.out.println(str);
            }
        },"I am Context");
        Thread.sleep(10000);
    }

7、检查节点是否存在

// 同步方法
exists(String path, boolean b)
// 异步方法
exists(String path, boolean b,AsyncCallback.StatCallback callBack,Object ctx)

示例:

    @Test
    public void exists1() throws Exception {
        // arg1:节点的路径
        Stat stat=zooKeeper.exists("/exists1",false);
        // 节点的版本信息
        System.out.println(stat.getVersion());
    }

    @Test
    public void exists2() throws Exception {
        // 异步方式
        zooKeeper.exists("/exists1", false, (rc, path, ctx, stat) -> {
            // 0 代表方式执行成功
            System.out.println(rc);
            // 节点的路径
            System.out.println(path);
            // 节点的版本信息
            System.out.println(stat.getVersion());
        },"I am Context");
        Thread.sleep(10000);
    }

zookeeper 事件监听机制

Watcher机制简介

zookeeper采用了Watcher机制实现数据的监听。该机制在被监听对象发生变化时会异步通知客户端,因此客户端不必在Watcher注册后轮询阻塞,从而减轻了客户端压力;

Watcher实现由三个部分组成:

  1. Zookeeper服务端
  2. Zookeeper客户端
  3. 客户端的ZKWatchManager对象

客户端首先将Watcher注册到服务端,同时将Watcher对象保存到客户端的Watch管理器中。当ZooKeeper服务端监听的数据状态发生变化时,服务端会主动通知客户端,接着客户端的Watch管理器会触发相关Watcher来回调相应处理逻辑,从而完成整体的数
据发布/订阅流程;

在这里插入图片描述
watcher特性:

  • watcher是一次性的,一旦被触发就会移除,再次使用时需要重新注册
  • watcher只有在当前session彻底失效时才会无效,若在session有效期内快速重连成功,则watcher依然存在,仍可接收到通知;
  • watcher回调是顺序串行化执行的,只有回调后客户端才能看到最新的数据状态

watcher接口设计

Watcher是一个接口,任何实现了Watcher接口的类就是一个新的Watcher。Watcher内部包含了两个枚举类:

  1. KeeperState
  2. EventType

在这里插入图片描述

Watcher通知状态(KeeperState)

KeeperState是客户端与服务端连接状态发生变化时对应的通知类型,是一个枚举类,其枚举属性如下:

枚举属性说明
SyncConnected客户端与服务器正常连接时
Disconnected客户端与服务器断开连接时
Expired会话session失效时
AuthFailed身份认证失败时

Watcher事件类型(EventType)

EventType是数据节点(znode)发生变化时对应的通知类型。EventType变化时KeeperState永远处于SyncConnected通知状态下;当KeeperState发生变化时,EventType永远为None,也是一个枚举类,枚举属性如下:

枚举属性说明
None
NodeCreatedWatcher监听的数据节点被创建时
NodeDeletedWatcher监听的数据节点被删除时
NodeDataChangedWatcher监听的数据节点内容发生变更时(无论内容数据是否变化)
NodeChildrenChangedWatcher监听的数据节点的子节点列表发生变更时

注:客户端接收到的相关事件通知中只包含状态及类型等信息,不包括节点变化前后的具体内容,变化前的数据需业务自身存储,变化后的数据需调用get等方法重新获取;

捕获相应的事件

在zookeeper中采用zk.getChildren(path, watch)、zk.exists(path, watch)、zk.getData(path, watcher, stat)这样的方式为某个znode注册监听

注册方式CreatedChildrenChangedChangedDeleted
zk.exists(“/node-x”,watcher)可监控可监控可监控
zk.getData(“/node-x”,watcher)可监控可监控
zk.getChildren(“/node-x”,watcher)可监控可监控

注册watcher

1、客服端与服务器的连接状态

public class ZKConnectionWatcher implements Watcher {

    // 计数器对象
    static CountDownLatch countDownLatch = new CountDownLatch(1);
    // 连接对象
    static ZooKeeper zooKeeper;

    @Override
    public void process(WatchedEvent event) {
        // 事件类型
        if (event.getType() == Event.EventType.None) {
            if (event.getState() == Event.KeeperState.SyncConnected) {
                System.out.println("连接创建成功!");
                countDownLatch.countDown();
            } else if (event.getState() == Event.KeeperState.Disconnected) {
                System.out.println("断开连接!");
            } else if (event.getState() == Event.KeeperState.Expired) {
                System.out.println("会话超时!");
            } else if (event.getState() == Event.KeeperState.AuthFailed) {
                System.out.println("认证失败!");
            }
        }
    }
    
    public static void main(String[] args) {
        try {
            zooKeeper = new ZooKeeper("127.0.0.1:2181", 5000, new ZKConnectionWatcher());
            // 阻塞线程等待连接的创建
            countDownLatch.await();
            // 添加授权用户,测试认证失败的监听
            zooKeeper.addAuthInfo("digest1","yd:1234561".getBytes());
            byte [] bs=zooKeeper.getData("/node1",false,null);
            // 休眠50s,可手动断开服务器测试断开连接、创建连接的监听
            Thread.sleep(50000);
            zooKeeper.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

2、检查节点是否存在

// 使用连接对象的监视器
exists(String path, boolean b)
// 自定义监视器
exists(String path, Watcher w)
// NodeCreated:节点创建
// NodeDeleted:节点删除
// NodeDataChanged:节点内容发生变化
public class ZKWatcherExists {

    String IP = "127.0.0.1:2181";
    ZooKeeper zooKeeper = null;

    @Before
    public void before() throws IOException, InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        // 连接zookeeper客户端
        zooKeeper = new ZooKeeper(IP, 6000, event -> {
            // 连接成功
            if (event.getState() == Watcher.Event.KeeperState.SyncConnected) {
                countDownLatch.countDown();
            }
            System.out.println("eventType=" + event.getType());
        });
        countDownLatch.await();
    }

    @After
    public void after() throws InterruptedException {
        zooKeeper.close();
    }

    @Test
    public void watcherExists1() throws KeeperException, InterruptedException {
        // 1、使用连接对象中的watcher
        zooKeeper.exists("/watcher1", true);

        // 2、自定义watcher对象
        zooKeeper.exists("/watcher1", event -> {
            System.out.println("自定义watcher");
            System.out.println("eventType=" + event.getType());
        });

        // 3、注册多个监听器对象
        zooKeeper.exists("/watcher1", event -> {
            System.out.println("自定义watcher2");
            System.out.println("eventType=" + event.getType());
        });
        Thread.sleep(50000);
    }
    
}

3、查看节点(避免重复代码,创建连接、关闭连接可用上面小节的代码!)

// 使用连接对象的监视器
getData(String path, boolean b, Stat stat)
// 自定义监视器
getData(String path, Watcher w, Stat stat)
// NodeDeleted:节点删除
// NodeDataChanged:节点内容发生变化
public class ZKWatcherGetData {
    @Test
    public void watcherGetData1() throws KeeperException, InterruptedException {
        // 1、使用连接对象中的watcher
        zooKeeper.getData("/watcher2", true, null);
        
        //2、使用自定义watcher对象
        zooKeeper.getData("/watcher2", event -> {
            System.out.println("自定义watcher");
            System.out.println("eventType=" + event.getType());
        }, null);
        
        //3、注册多个监听器对象
        zooKeeper.getData("/watcher2", event -> {
            System.out.println("自定义watcher2");
            System.out.println("eventType=" + event.getType());
        },null);
        Thread.sleep(500000);
    }

4、查看子节点(避免重复代码,创建连接、关闭连接可用上面小节的代码!)

// 使用连接对象的监视器
getChildren(String path, boolean b)
// 自定义监视器
getChildren(String path, Watcher w)
// NodeChildrenChanged:子节点发生变化
// NodeDeleted:节点删除
public class ZKWatcherGetChild {
    @Test
    public void watcherGetChild1() throws KeeperException, InterruptedException {
        // 1、使用连接对象中的watcher
        zooKeeper.getChildren("/watcher3", true);

        // 2、使用自定义watcher
        zooKeeper.getChildren("/watcher3", event -> {
            System.out.println("自定义watcher");
            System.out.println("eventType=" + event.getType());
        });

        // 3、多个监视器对象
        zooKeeper.getChildren("/watcher3", event -> {
            System.out.println("自定义watcher2");
            System.out.println("eventType=" + event.getType());

        });
        Thread.sleep(50000);
    }
}

总结

深入学习zookeeper(二)

本文仅总结笔记作后续复习理解使用,如果有侵犯到原作者,请第一时间通知我,我会进行处理,感谢!!!

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值