Zookeeper之快速入门篇(个人笔记)

简介

ZooKeeper是一个集中的服务,用于维护配置信息、命名、提供分布式同步和提供组服务。所有这些类型的服务都以某种形式被分布式应用程序使用。每次它们被实现时,都会有大量的工作来修复不可避免的错误和竞争条件。由于实现这些服务的困难,应用程序最初通常会略过这些服务,这使得它们在出现更改时变得脆弱,并且难以管理。即使正确地执行了这些服务,在部署应用程序时,这些服务的不同实现也会导致管理复杂性。

zookeeper由雅虎研究院开发,是Google Chubby的开源实现,后来托管到 Apache,于2010年11月正式成为(分布式应用提供协调服务)apache的顶级项目

zookeeper = 文件系统 + 监听通知机制。

在分布式应用中把Zookeeper当作“配置中心”,而且当“配置信息”发生变化后,Zookeeper还能提供相应数据的“发布/订阅”机制。因为Zookeeper提供了一种监听机制叫watch

大数据生态系统里由很多组件的命名都是某些动物或者昆虫,比如hadoop就是🐘大象,hive就是🐝蜂巢,zookeeper即动物园管理员,顾名思义就是管理大数据生态系统各组件的管理员,如下所示:
在这里插入图片描述

应用场景

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

应用场景有:

  • 维护配置信息
  • 分布式锁服务
  • 集群管理
  • 生成分布式唯一ID
  1. 维护配置信息

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

    但是随着分布式系统的兴起,由于许多服务都需要使用到该配置文件,因此有必须保证该配置服务的高可用性(highavailability)和各台服务器上配置数据的一致性

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

    zookeeper就可以提供这样一种服务,其使用Zab这种一致性协议来保证一致性。现在有很多开源项目使用zookeeper来维护配置,如在 hbase中,客户端就是连接一个 zookeeper,获得必要的 hbase集群的配置信息,然后才可以进一步操作。还有在开源的消息队列 kafka中,也便用zookeeper来维护 brokers的信息。在 alibaba开源的soa框架dubbo中也广泛的使用zookeeper管理一些配置来实现服务治理。

在这里插入图片描述

  1. 分布式锁服务

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

  2. 集群管理

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

在这里插入图片描述

  1. 生产分布式唯一ID

    在过去的单库单表型系统中,通常可以使用数据库字段自带的auto_ increment属性来自动为每条记录生成一个唯一的ID。但是分库分表后,就无法在依靠数据库的auto_ Increment属性来唯一标识一条记录了。此时我们就可以用zookeeper在分布式环境下生成全局唯一ID

    做法如下:每次要生成一个新id时,创建一个持久顺序节点,创建操作返回的节点序号,即为新id,然后把比自己节点小的删除即可

Zookeeper的设计目标

zooKeeper致力于为分布式应用提供一个高性能高可用,且具有严格顺序访问控制能力的分布式协调服务

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

数据模型

zookeeper的数据结点可以视为树状结构(或目录),树中的各个结点被称为znode(即zookeeper node),一个znode可以由多个子结点。zookeeper结点在结构上表现为树状;

使用路径path来定位某个znode,比如/ns-1/itcast/mysqml/schemal1/table1,此处ns-1,itcast、mysql、schemal1、table1分别是根节点、2级节点、3级节点以及4级节点;其中ns-1itcast的父节点,itcastns-1的子节点,itcastmysql的父节点…以此类推

znode,间距文件和目录两种特点,即像文件一样维护着数据、元信息、ACL、时间戳等数据结构,又像目录一样可以作为路径标识的一部分

在这里插入图片描述

那么如何描述一个znode呢?一个znode大体上分为3个部分:

  • 节点的数据:即znode data(节点path,节点data)的关系就像是Java map中的 key value关系
  • 节点的子节点children
  • 节点的状态stat:用来描述当前节点的创建、修改记录,包括cZxidctime

节点状态stat的属性

zookeeper shell中使用 get命令查看指定路径节点的datastat信息

在这里插入图片描述
属性说明:

节点的各个属性如下。其中重要的概念是mZxid(Zookeeper Transaction ID)Zookeeper节点的每一次更改都具有唯一的mZxid,如果mZxid-1 小于mZxid-2 ,则mZxid-1 的更改发生在 mZxid-2更改之前

https://zookeeper.apache.org/doc/r3.4.14/zookeeperProgrammers.html#sc_zkDataModel_znodes

  • cZxid数据节点创建时的事务ID——针对于zookeeper数据节点的管理:我们对节点数据的一些写操作都会导致zookeeper自动地为我们去开启一个事务,并且自动地去为每一个事务维护一个事务ID
  • ctime数据节点创建时的时间
  • mZxid数据节点最后一次更新时的事务ID
  • mtime数据节点最后一次更新时的时间
  • pZxid数据节点最后一次修改此znode子节点更改的zxid
  • cversion子节点的更改次数
  • dataVersion节点数据的更改次数
  • aclVersion节点的ACL更改次数——类似linux的权限列表,维护的是当前节点的权限列表被修改的次数
  • ephemeralOwner如果节点是临时节点,则表示创建该节点的会话的SessionID;如果是持久节点,该属性值为0
  • dataLength数据内容的长度
  • numChildren数据节点当前的子节点个数

节点类型

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

  • 临时节点(依赖会话):
    该节点的生命周期依赖于创建它们的会话。一旦会话( Session)结束,临时节点将被自动删除,当然可以也可以手动删除。虽然每个临时的 Znode都会绑定到一个客户端会话,但他们对所有的客户端还是可见的。另外,Zookeeper临时节点不允许拥有子节点
  • 持久化节点(不依赖会话):
    该节点的生命周期不依赖于会话,并且只有在客户端显示执行删除操作的时候,它们才能被删除

单机安装

macOS环境下安装流程

测试系统环境centos7.3

zookeeper:zookeeper-3.4.10.tar.gz

jdk:jdk-8u131-linux-x64.tar.gz

http://archive.apache.org/dist/zookeeper/

  1. centos中使用 root用户创建 zookeeper用户,用户名:zookeeper密码:zookeeper

useradd zookeeper
passwd zookeeper
su zookeeper

  1. zookeeper底层依赖于jdk,zookeeper用户登录后,根目录下先进行jdk 的安装,jdk使用 jdk-8u131-linux-x64.tar.gz

tar -zxf tar.gz

  1. 配置jdk 环境变量

vi /etc/profile
JAVA_HOME=/home/zookeeper/jdk1.8.0_131
export JAVA_HOME
PATH= J A V A H O M E / b i n : JAVA_HOME/bin: JAVAHOME/bin:PATH
export PATH
souce /etc/profile

  1. 检测jdk安装

    java -version // 如果反馈了Java信息,则成功

  2. zookeeper 上传解压

    tar -zxf tar.gz

  3. zookeeper准备配置文件

#进入conf目录
cd /home/zookeeper/zookeeper-3.4.10/conf
# 复制配置文件
cp zoo_sampe.cfg zoo.cfg
# zookeeper 根目录下创建data目录
mkdir data
# vi 配置文件中的dataDir
# 此路径用于存储zookeeper中数据的内存快照、及事务日志文件,虽然zookeeper是使用内存的,但是需要持久化一些数据来保证数据的安全,和redis一样
dataDir=/home/zookeeper/zookeeper-3.4.10/data

  1. 启动zookeeper

#进入zookeeper的bin目录
cd /home/zookeeper/zookeeper-3.4.10/bin
# 启动zookeeper
./zkServer.sh start
# 启动: zkServer.sh start
# 停止: zkServer.sh stop
# 查看状态:zkServer.sh status
# 进入zookeeper 内部
./zkCli.sh

常用shell命令

zookeeper——shell命令官方入口

操作节点

创建节点

创建节点并写入数据:

create [-s] [-e] path data # 其中 -s 为有序节点,-e 临时节点(默认是持久节点)

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

# 此时,如果quit退出后再./ZkCient.sh 登入
# 再用输入 get /testZK 获取,节点依然存在(永久节点),默认就是永久节点
create /testZK "123456"

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

# 创建一个持久化有序节点,创建的时候可以观察到返回的数据带上了一个id			       
create -s /a "a"

# 返回的值,id递增了               
create -s /b "b"
         
# 依然还会返回自增的id,quit后再进来,继续创建,id依然是往后推的
create -s /aa "aa"

# 继续创建节点,可以看到pZxid变化了
create /aa/xx            

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

create -e /tmp "tmp"

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

create -s -e /tmp "tmp"

大家如果刷过 ZK 相关面试题的话,就一定会刷到过 “ZK 有几种节点类型?”,大家通常背书的答案的话是:4 种!但其实ZK (3.6.2)服务端支持 7 种节点类型,分别是:

  • 持久
  • 持久顺序
  • 临时
  • 临时顺序
  • 容器
  • 持久 TTL
  • 持久顺序 TTL

查看节点
更新节点的命令是get path

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

zxid的含义:
znode节点的状态信息中包含czxidmzxid, 那么什么是zxid呢? ZooKeeper状态的每一次改变, 都对应着一个递增的Transaction id, 该id称为zxid. 由于zxid的递增性质, 如果zxid1小于zxid2, 那么zxid1肯定先于zxid2发生. 创建任意节点, 或者更新任意节点的数据, 或者删除任意节点, 都会导致Zookeeper状态发生改变, 从而导致zxid的值增加。

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

# 修改节点值
set /a "a2"        

# 也可以基于版本号进行更改,类似于乐观锁,当传入版本号(dataVersion)
# 新创建的节点,版本号是从:‘0’作为起始值,每次通过'set'修改后都会进行加‘1’
# 和当前节点的数据版本号不一致时,zookeeper会拒绝本次修改
set /a0000000007 'a3' 1       

删除节点
删除节点的语法如下:
delete path [version]

# 删除节点
delete /a0000000007
# 乐观锁机制,与set 方法一样,也可以传入当前版本号          
delete /a0000000007 1         

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

[zk: localhost:2181(CONNECTED) 0] ls /a -s
[aNode, aNode2]
cZxid = 0x2c
ctime = Tue Dec 29 10:36:41 CST 2020
mZxid = 0x34
mtime = Tue Dec 29 11:13:08 CST 2020
pZxid = 0x38
cversion = 2
dataVersion = 3
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 12
numChildren = 2
[zk: localhost:2181(CONNECTED) 1] deleteall /a

查看节点状态
可以使用stat path命令查看节点状态,它的返回值和get path命令类似,但不会返回节点数据

[zk: localhost:2181(CONNECTED) 47] stat /a
cZxid = 0x20
ctime = Mon Dec 28 11:41:55 CST 2020
mZxid = 0x20
mtime = Mon Dec 28 11:41:55 CST 2020
pZxid = 0x21
cversion = 1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 11
numChildren = 1

查看节点列表
查看节点列表有ls pathls -s path两个命令,后者是前者的增强,不仅可以查看指定路径下的所有节点,还可以查看当前节点状态

# 可以查看节点的列表
[zk: localhost:2181(CONNECTED) 62] ls /a
[node01, node02]
# 可以查看节点的列表以及目标结点的信息
[zk: localhost:2181(CONNECTED) 64] ls -s /a
[node01, node02]
cZxid = 0x20
ctime = Mon Dec 28 11:41:55 CST 2020
mZxid = 0x20
mtime = Mon Dec 28 11:41:55 CST 2020
pZxid = 0x27
cversion = 4
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 11
numChildren = 2
 # 根节点
 ls /                    

Zookeeper对节点的watch

使用场景:
zookeeper可以作用为注册中心,将应用中心所依赖的一系列相关的配置信息放在zookeeper内部, 当这些配置信息一旦发生变化的时候,可以通过这种监听机制,获取到数据的变化,既而使应用程序读取最新的配置信息。

1、对节点的watch是永久的吗?不是,因为一个watch是一个事件,当设置了watch的数据发生了改变的时候,则服务器将这个改变发送给设置watch的客户端。
2、watch通知是一次性的,必须重复注册。
3、发生ConnectionLoss的时候,如果Session_timeout之内再次连接上(即不发生SessionExpired),那么这个连接注册的watch依旧存在。
4、对于某个节点注册了watch,如果该节点删除了,那么watch也会删除。
5、同一个zk客户端对某一节点注册相同的watch,只会收到一次通知。
6、watcher对象只会保存在客户端,不会传递到服务端。
7、创建临时节点,连接断开之后,zk不会马上移除临时数据,只有当SessionExpired之后,才会把这个会话建立的临时数据删除。

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

# 客户端1号
# get 的时候添加监听器,当值改变的时候,监听器返回消息
[zk: localhost:2181(CONNECTED) 2] get /a -s -w
aTestConten2
cZxid = 0x2c
ctime = Tue Dec 29 10:36:41 CST 2020
mZxid = 0x2d
mtime = Tue Dec 29 10:41:23 CST 2020
pZxid = 0x2c
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 12
numChildren = 0

# 测试
# 客户端2号
# 在另外创建一个新的客户端,修改‘/a’节点内容
[zk: localhost:2181(CONNECTED) 0] set /a 'aTestConten3'

# 客户端1号
# 此时会出现监听通知
[zk: localhost:2181(CONNECTED) 2] get /a -s -w
aTestConten2
cZxid = 0x2c
ctime = Tue Dec 29 10:36:41 CST 2020
mZxid = 0x2d
mtime = Tue Dec 29 10:41:23 CST 2020
pZxid = 0x2c
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 12
numChildren = 0
[zk: localhost:2181(CONNECTED) 3]
# 这个就是监听通知 
WATCHER::

WatchedEvent state:SyncConnected type:NodeDataChanged path:/a
ls /
[a, zookeeper]
[zk: localhost:2181(CONNECTED) 4] 

ls -w path \ ls -s -w path

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

# 添加监听器
# 客户端1号
[zk: localhost:2181(CONNECTED) 13] ls -s -w  /a
[aNode]
cZxid = 0x2c
ctime = Tue Dec 29 10:36:41 CST 2020
mZxid = 0x34
mtime = Tue Dec 29 11:13:08 CST 2020
pZxid = 0x35
cversion = 1
dataVersion = 3
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 12
numChildren = 1

# 测试
# 客户端2号
# 在另外创建一个新的客户端,在‘/a’节点下创建新的子节点
[zk: localhost:2181(CONNECTED) 0] create /a/aNode2 'aNode2TestConten'
Created /a/aNode2

# 客户端1号
# 此时会出现监听通知
[zk: localhost:2181(CONNECTED) 13] ls -s -w  /a
[aNode]
cZxid = 0x2c
ctime = Tue Dec 29 10:36:41 CST 2020
mZxid = 0x34
mtime = Tue Dec 29 11:13:08 CST 2020
pZxid = 0x35
cversion = 1
dataVersion = 3
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 12
numChildren = 1
[zk: localhost:2181(CONNECTED) 14] 
# 这个就是监听通知 
WATCHER::

WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/a
ls -s /a
[aNode, aNode2]
cZxid = 0x2c
ctime = Tue Dec 29 10:36:41 CST 2020
mZxid = 0x34
mtime = Tue Dec 29 11:13:08 CST 2020
pZxid = 0x38
cversion = 2
dataVersion = 3
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 12
numChildren = 2
[zk: localhost:2181(CONNECTED) 15] 

zookeeper的Acl权限控制

zookeeper——Acl权限控制官方入口

zookeeper类似文件系统,client可以创建节点、更新节点、删除节点,那么如何做到节点的权限控制呢?

zookeeperaccess control list 访问控制列表可以做到这一点。

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

  • 权限模式(scheme):授权的策略
  • 授权对象(id):授权的对象
  • 权限(permission):授予的权限

其特性如下:

  • zookeeper的权限控制是基于每个znode节点的,需要对每个节点设置权限

  • 每个znode支持多种权限控制方案和多个权限

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

例如:
setAcl /a ip:192.168.133.133:crwda // 将该节点权限设置为Ip:192.168.133.133 的客户端可以对节点进行增删改查和管理权限

权限模式

采用何种方式授权

方案描述
world只有一个用户:anyone,代表登录zookeeper所有人(默认模式)
ip对客户端使用IP地址认证
auth根据指定用户,使用已添加认证的用户认证 ,密码是明文的
digest根据指定用户,使用"用户名:密码"方式认证 ,密码是经过加密(密文)

授权对象

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

授权的权限

  • 授予什么权限

  • create、delete、read、writer、admin也就是 增、删、查、改、管理权限,这5种权限简写为 c d r w a,注意:这五种权限中,有的权限并不是对节点自身操作的例如:delete是指对子节点的删除权限

    可以试图删除父结点,但是子节点必须删除干净,所以delete的权限也是很有用的

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

授权的相关命令

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

Acl权限 案例

  • world权限模式
    命令

    setAcl <path> world:anyone:<acl> // 对指定的节点授权,指定路径,权限模式:world:anyone指定于当前登陆到zookeeper所有用户,权限列表

  • getAcl path // 读取权限信息

[zk: localhost:2181(CONNECTED) 0] ls /
[testNode, zookeeper]
[zk: localhost:2181(CONNECTED) 1] getAcl /testNode
'world,'anyone
: cdrwa
  • setAcl /node world:anyone:drwa // 设置权限(禁用创建子结点的权限)
[zk: localhost:2181(CONNECTED) 0] ls /testNode
[testNode01, testNode02]
[zk: localhost:2181(CONNECTED) 1] setAcl /testNode world:anyone:drwa
[zk: localhost:2181(CONNECTED) 2] getAcl /testNode
# world形式授权模式,anyone,代表登录zookeeper所有人(默认模式)
'world,'anyone
# 权限有:删除、读取、设置、管理
: drwa 
[zk: localhost:2181(CONNECTED) 3] create /testNode/testNode03 'testNode03Conten'
# 现在没有‘创建’权限
Authentication is not valid : /testNode/testNode03
[zk: localhost:2181(CONNECTED) 4] setAcl /testNode world:anyone:rwa
[zk: localhost:2181(CONNECTED) 5] getAcl /testNode
'world,'anyone
: rwa
[zk: localhost:2181(CONNECTED) 6] delete /testNode/testNode01
# 现在没有‘创建、删除’权限
Authentication is not valid : /testNode/testNode01
[zk: localhost:2181(CONNECTED) 7] get /testNode
testNodeConten
[zk: localhost:2181(CONNECTED) 8] setAcl /testNode world:anyone:wa
[zk: localhost:2181(CONNECTED) 9] getAcl /testNode
'world,'anyone
: wa
[zk: localhost:2181(CONNECTED) 10] get /testNode
org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /testNode
[zk: localhost:2181(CONNECTED) 11] setAcl /testNode world:anyone:rwa
[zk: localhost:2181(CONNECTED) 12] getAcl /testNode
'world,'anyone
: rwa
[zk: localhost:2181(CONNECTED) 13] get /testNode
testNodeConten
[zk: localhost:2181(CONNECTED) 14] get /testNode
testNodeConten
[zk: localhost:2181(CONNECTED) 15] setAcl /testNode world:anyone:a
[zk: localhost:2181(CONNECTED) 16] getAcl /testNode
'world,'anyone
: a
[zk: localhost:2181(CONNECTED) 17] set /testNode 'testNodeConten1'
# 现在没有‘删除、读取、设置’权限
Authentication is not valid : /testNode
[zk: localhost:2181(CONNECTED) 18] setAcl /testNode world:anyone:wa
[zk: localhost:2181(CONNECTED) 19] getAcl /testNode
'world,'anyone
: wa
[zk: localhost:2181(CONNECTED) 20] set /testNode 'testNodeConten1'
[zk: localhost:2181(CONNECTED) 21] setAcl /testNode world:anyone:rwa
[zk: localhost:2181(CONNECTED) 22] get /testNode
testNodeConten1
[zk: localhost:2181(CONNECTED) 23] 

ip模式

./zkServer.sh -server 192.168.133.133 可以远程登录

  • setAcl /testNode2 ip:127.0.0.1:drwa
[zk: localhost:2181(CONNECTED) 0] ls /
[testNode, testNode2, zookeeper]
[zk: localhost:2181(CONNECTED) 1] setAcl /testNode2 ip:127.0.0.1:drwa
[zk: localhost:2181(CONNECTED) 2] getAcl /testNode2
# ip形式授权模式,对客户端使用IP地址认证  
'ip,'127.0.0.1
: drwa
[zk: localhost:2181(CONNECTED) 3] 
  • 如果在两台不同的虚拟机中,另一台用远程连接的模式,进行上面这条命令,那么只会有一台被授权
  • 需要两台虚拟机一起授权的话需要用逗号将授权列表隔开:setAcl /testNode2 ip:192.168.133.133:cdrwa,ip:192.168.133.132:cdrwa

auth认证用户模式
使用auth认证用户模式之前,需要先添加认证用户,命令:addauth digest <user>:<password>
设置auth形式授权模式,命令:
setAcl <path> auth:<user>:<acl>

[zk: localhost:2181(CONNECTED) 0] ls /
[testNode, testNode2, testNode3, zookeeper]
[zk: localhost:2181(CONNECTED) 1] getAcl /testNode3
'world,'anyone
: cdrwa
# 添加认证用户
[zk: localhost:2181(CONNECTED) 2] addauth digest testUser:123456
# 设置auth-明文形式授权模式
[zk: localhost:2181(CONNECTED) 3] setAcl /testNode3 auth:testUser:cdrwa
[zk: localhost:2181(CONNECTED) 4] getAcl /testNode3
'digest,'testUser:PXJRKP8p5vYVvO2vknKVtymY3+w=
: cdrwa
# 退出客户端 
[zk: localhost:2181(CONNECTED) 5] quit

WATCHER::

WatchedEvent state:Closed type:None path:null
# 重新连接客户端
liangleide-MBP:zookeeper leiliang$ zkCli
Connecting to localhost:2181
Welcome to ZooKeeper!
JLine support is enabled

WATCHER::

WatchedEvent state:SyncConnected type:None path:null
# 获取‘testNode3’节点数据
[zk: localhost:2181(CONNECTED) 0] get /testNode3
# 显示没有权限操作了
org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /testNode3
# 添加认证用户之后就能正常操作了
[zk: localhost:2181(CONNECTED) 1] addauth digest testUser:123456
# 认证之后,再获取数据
[zk: localhost:2181(CONNECTED) 2] get /testNode3
testNode3TestConten
[zk: localhost:2181(CONNECTED) 3] 

Digest授权模式
命令:
setAcl <路径> digest:<用户>:<加密后密码>:<权限列表>

  • 这里的密码是经过SHA1以及BASE64处理的密文,在shell 中使用自带命令获取,可以通过以下命令计算:
echo -n <user>:<password> | openssl dgst -binary -sha1 | openssl base64
 # 计算密码
testUser-MBP:~ testUser$ echo -n testUser:123456 | openssl dgst -binary -sha1 | openssl base64
PXJRKP8p5vYVvO2vknKVtymY3+w=
 # 进入zookeeper客户端
[zk: localhost:2181(CONNECTED) 0] ls /
[testNode, testNode2, testNode3, testNode4, zookeeper]
[zk: localhost:2181(CONNECTED) 1] getAcl /testNode4
'world,'anyone
: cdrwa
 # 设置auth-密文形式授权模式
[zk: localhost:2181(CONNECTED) 3] setAcl /testNode4 digest:testUser:PXJRKP8p5vYVvO2vknKVtymY3+w=:cdrwa
#没有权限获取
[zk: localhost:2181(CONNECTED) 4] getAcl /testNode4
Authentication is not valid : /testNode4
[zk: localhost:2181(CONNECTED) 6] quit

WATCHER::

WatchedEvent state:Closed type:None path:null
liangleide-MBP:zookeeper leiliang$ zkCli
Connecting to localhost:2181
Welcome to ZooKeeper!
JLine support is enabled

WATCHER::

WatchedEvent state:SyncConnected type:None path:null
[zk: localhost:2181(CONNECTED) 0] getAcl /testNode4
Authentication is not valid : /testNode4
#添加用户认证
[zk: localhost:2181(CONNECTED) 1] addauth digest testUser:123456
[zk: localhost:2181(CONNECTED) 2] getAcl /testNode4
'digest,'testUser:PXJRKP8p5vYVvO2vknKVtymY3+w=
: cdrwa
[zk: localhost:2181(CONNECTED) 3] 

多种授权模式

仅需逗号隔开

# 在终端生成密文密码
testUser-MBP:~ testUser$ echo -n testUser:123456 | openssl dgst -binary -sha1 | openssl base64
PXJRKP8p5vYVvO2vknKVtymY3+w=
# 进入zookeeper客户端
# 添加用户认证
[zk: localhost:2181(CONNECTED) 0] addauth digest testUser:123456
# 多种授权模式,仅需逗号隔开
[zk: localhost:2181(CONNECTED) 1] setAcl /testNode4 ip:127.0.0.1:cdrwa,auth:testUser:cdrwa,digest:testUser:PXJRKP8p5vYVvO2vknKVtymY3+w=:cdrwa

Acl权限 超级管理员

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

    假设这个超管是supper:admin,需要为超管生产密码的密文

    echo -n super:admin | openssl dgst -binary -sha1 | openssl base64
    
  • 那么打开zookeeper目录下/bin/zkServer.sh服务器脚本文件,macOS的目录在/usr/local/Cellar/zookeeper/3.6.2_1/libexec/bin/zkServer.sh,找到如下一行:

     # 快速查找,可以看到如下
     nohup "$JAVA" "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}"
    
  • 这个就算脚本中启动zookeeper的命令,默认只有以上两个配置项,我们需要添加一个超管的配置项

    "-Dzookeeper.DigestAuthenticationProvider.superDigest=super:xQJmxLMiHGwaqBvst5y6rkB6HQs="
    
  • 修改后命令变成如下

    nohup "$JAVA" $ZOO_DATADIR_AUTOCREATE "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.DigestAuthenticationProvider.superDigest=super:xQJmxLMiHGwaqBvst5y6rkB6HQs=" \
    

然后重启zookeeper,全局查看一下zookeeper进程,ps -ef |grep zookeeper,杀死进程kill -9 13914

# 重起后,进入zookeeper客户端
[zk: localhost:2181(CONNECTED) 0] ls /
[testNode, testNode2, testNode3, testNode4, zookeeper]
# 获取当前节点权限数据
[zk: localhost:2181(CONNECTED) 1] getAcl /testNode2
'ip,'127.0.0.1
: drwa
# 通过IP模式设置权限
[zk: localhost:2181(CONNECTED) 2] setAcl /testNode2 ip:192.168.1.136:cdrwa
# 获取该节点权限数据
[zk: localhost:2181(CONNECTED) 3] getAcl /testNode2
# 因为当前本机的IP是:127.0.0.1,所有没有权限获取
Authentication is not valid : /testNode2
# 使用超级管理员的身份认证进行操作
[zk: localhost:2181(CONNECTED) 4] addauth digest super:admin
# 能获取节点数据了
[zk: localhost:2181(CONNECTED) 5] getAcl /testNode2
'ip,'192.168.1.136
: cdrwa
[zk: localhost:2181(CONNECTED) 6]

zookeeper的 JavaAPI

zondezookeeper集合的核心组件,zookeeper API 提供了一小组使用 zookeeper集群来操作znode的所有细节。

客户端应该遵循以下步骤,与zookeeper服务器进行清晰和干净的交互。

  • 连接到zookeeper服务器。zookeeper服务器为客户端分配会话ID
  • 定期向服务器发送心跳。否则,zookeeper服务器将过期会话ID,客户端需要重新连接
  • 只要会话Id处于活动状态,就可以获取/设置znode
  • 所有任务完成后,断开与zookeeper服务器连接,如果客户端长时间不活动,则zookeeper服务器将自动断开客户端

连接到Zookeeper

案例:
pom.xml依赖

<dependencies>
    <!-- 测试依赖 -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>compile</scope>
    </dependency>
    <!-- 日志依赖 -->
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-core</artifactId>
      <version>2.8.2</version>
    </dependency>
    <!-- zookeeper依赖 -->
    <dependency>
      <groupId>org.apache.zookeeper</groupId>
      <artifactId>zookeeper</artifactId>
      <version>3.6.2</version>
    </dependency>
</dependencies>>
public static void main(String[] args) {

        try{

            // 计数器对象
            final CountDownLatch countDownLatch = new CountDownLatch(1);

            // 创建一个zookeeper对象,通过构造器转入连接IP跟端口、客户端与服务器之间会话连接超时,毫秒单位,5秒、匿名内部类监视器对象
            ZooKeeper zooKeeper = new ZooKeeper("192.168.1.136:2181", 5000, new Watcher() {
                /**
                 * process方法是Watcher接口中的一个回调方法,当ZooKeeper向客户端发送一个Watcher事件通知时,客户端就会对相应的process方法进行回调
                 *
                 * WatchedEvent包含了每一个事件的三个基本属性:通知状态(keeperState),事件类型(EventType)和节点路径(path)
                 */
                @Override
                public void process(WatchedEvent watchedEvent) {

                    // 判断回调'watchedEvent(事件状态)'等于Event.KeeperState.SyncConnected常量时
                    if (watchedEvent.getState() == Event.KeeperState.SyncConnected){
                        System.out.println("连接成功");

                        // 告诉主线程不用再阻塞
                        countDownLatch.countDown();
                    }

                }
            });

            // 主线程阻塞等待连接对象的创建成功对象,经过19行代码连接,不一定连接成功,所有要进行阻塞等待
            countDownLatch.await();

            // 打印客户端连接服务端的会话ID
            System.out.println(zooKeeper.getSessionId());

            // 释放资源
            zooKeeper.close();
        } catch (Exception e){
            e.printStackTrace();
        }
    }

新增节点

  // 创建同步节点,路径、数据、权限列表、当前节点类型:临时节点还是持久化节点
  create(String path, byte[] data, List<ACL> acl, CreateMode createMode)
  // 创建异步节点,路径、数据、权限列表、当前节点类型:临时节点还是持久化节点、异步回调接口:因为是异步创建,成功与不成功就都会执行
  create(String path, byte[] data, List<ACL> acl, CreateMode createMode,
        AsynCallback.StringCallback callBack, Object ctx)
参数解释
pathznode路径
data要存储在指定znode路径中的数据
acl要创建的节点的访问控制列表。zookeeper API提供了一个静态接口 ZooDefs.Ids 来获取一些基本的acl列表。例如,ZooDefs.Ids.OPEN_ACL_UNSAFE返回打开znodeacl列表
createMode节点的类型,这是一个枚举
callBack异步回调接口
ctx传递上下文参数

案例:

package com.zookeeper;

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class ZNodeCreate {

    /**
     * 在使用Zookeeper JavaAPI,在连接zookeeper的时候有三个步骤:1、打开zookeeper连接 2、对相对的业务处理逻辑编写操作 3、最后释放资源
     *
     * 下面三个注解的执行顺序:1、@Before 2、@Test 3、@After
     */

    /**
     * 定义成员变量
     */
    String IP = "127.0.0.1:2181";
    ZooKeeper zooKeeper;


    @Before
    public void Before(){
        System.out.println("Before:获取连接对象");

        try {

            // 计数器对象
            final CountDownLatch countDownLatch = new CountDownLatch(1);

            // 创建一个zookeeper对象,通过构造器转入连接IP跟端口、客户端与服务器之间会话连接超时,毫秒单位,5秒、匿名内部类监视器对象
            zooKeeper = new ZooKeeper(IP, 5000, new Watcher() {
                /**
                 * process方法是Watcher接口中的一个回调方法,当ZooKeeper向客户端发送一个Watcher事件通知时,客户端就会对相应的process方法进行回调
                 *
                 * WatchedEvent包含了每一个事件的三个基本属性:通知状态(keeperState),事件类型(EventType)和节点路径(path)
                 */
                @Override
                public void process(WatchedEvent watchedEvent) {

                    // 判断回调'watchedEvent(事件状态)'等于Event.KeeperState.SyncConnected常量时
                    if (watchedEvent.getState() == Event.KeeperState.SyncConnected){
                        System.out.println("连接成功");

                        // 告诉主线程不用再阻塞
                        countDownLatch.countDown();
                    }

                }
            });

            // 主线程阻塞等待连接对象的创建成功对象,经过连接,不一定连接成功,所有要进行阻塞等待
            countDownLatch.await();

        } catch (Exception e){
            e.printStackTrace();
        }
    }

    @After
    public void After(){
        System.out.println("After:释放资源");

        try {

            // 释放资源
            zooKeeper.close();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 枚举的方式
    @Test
    public void create(){
        System.out.println("create:编写对应业务逻辑代码");

        try {
            /**
             * ZooDefs.Ids
             * OPEN_ACL_UNSAFE:完全开放的ACL,任何连接的客户端都可以操作该属性znode;world:anyone:cdrwa
             * CREATOR_ALL_ACL:只有创建者才有ACL权限;
             * READ_ACL_UNSAFE:只能读取ACL;world:anyone:r
             *
             * CreateMode
             * PERSISTENT:持久化目录节点,存储的数据不会丢失,然后返回给客户端已经成功创建的目录节点名。
             * PERSISTENT_SEQUENTIAL:顺序自动编号的持久化目录节点,存储的数据不会丢失,并且根据当前已近存在的节点数自动加 1,然后返回给客户端已经成功创建的目录节点名。
             * EPHEMERAL:临时目录节点,一旦创建这个节点的客户端与服务器端口也就是session 超时,这种节点会被自动删除。然后返回给客户端已经成功创建的目录节点名。
             * EPHEMERAL_SEQUENTIAL:临时自动编号节点,一旦创建这个节点的客户端与服务器端口也就是session 超时,这种节点会被自动删除,并且根据当前已近存在的节点数自动加 1,然后返回给客户端已经成功创建的目录节点名。
             */

            String str = "testNodeContent01";

            // 创建节点 参数1:节点路径,参数2:节点数据,参数3:world:anyone:cdrwa权限列表,参数4:持久化类型节点
            String path = zooKeeper.create("/testNode/testNode01",str.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            System.out.println("文件路径:" + path);

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void create2(){
        System.out.println("create:编写对应业务逻辑代码");

        try {
            // world授权模式
            // 权限列表
            List<ACL> acls = new ArrayList<ACL>();

            //授权模式和授权对象
            Id id = new Id("world","anyone");

            // 权限设置
            // 读取权限
            acls.add(new ACL(ZooDefs.Perms.READ, id));
            // 编辑权限
            acls.add(new ACL(ZooDefs.Perms.WRITE, id));

            // 创建节点 参数1:节点路径,参数2:节点数据,参数3:world:anyone:cdrwa权限列表,参数4:持久化类型节点
            String path = zooKeeper.create("/testNode/testNode02","testNodeContent02".getBytes(), acls, CreateMode.PERSISTENT);
            System.out.println("文件路径:" + path);

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void create3(){
        System.out.println("create:编写对应业务逻辑代码");

        try {
            // ip授权模式
            // 权限列表
            List<ACL> acls = new ArrayList<ACL>();

            // 授权模式和授权对象
            Id id = new Id("ip","127.0.0.1");

            // 权限设置
            // cdrwa所有权限
            acls.add(new ACL(ZooDefs.Perms.ALL, id));

            // 创建节点 参数1:节点路径,参数2:节点数据,参数3:world:anyone:cdrwa权限列表,参数4:持久化类型节点
            String path = zooKeeper.create("/testNode/testNode03","testNodeContent03".getBytes(), acls, CreateMode.PERSISTENT);
            System.out.println("文件路径:" + path);

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void create4(){
        System.out.println("create:编写对应业务逻辑代码");

        try {
            // auth模式
            // 添加授权用户
            zooKeeper.addAuthInfo("digest","testUser:123456".getBytes());

            // 创建节点 参数1:节点路径,参数2:节点数据,参数3:只有创建者才有ACL权限;'digest,'tesUser:tpdSmz+0KeRJnFKNjGHVt33TbR4=:cdrwa权限列表,参数4:持久化类型节点
            String path = zooKeeper.create("/testNode/testNode04","testNodeContent04".getBytes(), ZooDefs.Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT);
            System.out.println("文件路径:" + path);

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void create5(){
        System.out.println("create:编写对应业务逻辑代码");

        try {
            // auth模式
            // 添加授权用户
            zooKeeper.addAuthInfo("digest","testUser:123456".getBytes());

            // 权限列表
            List<ACL> acls = new ArrayList<ACL>();

            // 授权模式和授权对象
            Id id = new Id("auth","testUser");

            // 权限设置
            // 读取权限
            acls.add(new ACL(ZooDefs.Perms.READ, id));

            // 创建节点 参数1:节点路径,参数2:节点数据,参数3:'digest,'testUser:x权限列表,参数4:持久化类型节点
            String path = zooKeeper.create("/testNode/testNode05","testNodeContent05".getBytes(), acls, CreateMode.PERSISTENT);
            System.out.println("文件路径:" + path);

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void create6(){
        System.out.println("create:编写对应业务逻辑代码");

        try {
            // digest模式
            // 权限列表
            List<ACL> acls = new ArrayList<ACL>();

            // 授权模式和授权对象
            Id id = new Id("digest","testUser:PXJRKP8p5vYVvO2vknKVtymY3+w=" );

            // 权限设置
            // cdrwa所有权限
            acls.add(new ACL(ZooDefs.Perms.ALL, id));

            // 创建节点 参数1:节点路径,参数2:节点数据,参数3:'digest,'testUser:x权限列表,参数4:持久化类型节点
            String path = zooKeeper.create("/testNode/testNode06","testNodeContent06".getBytes(), acls, CreateMode.PERSISTENT);
            System.out.println("文件路径:" + path);

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void create7(){
        System.out.println("create:编写对应业务逻辑代码");

        // 异步方式创建节点
        try {
             zooKeeper.create("/testNode/testNode07", "testNodeContent07".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT, new AsyncCallback.StringCallback() {
                 @Override
                 public void processResult(int rc, String path, Object name, String ctx) {
                     // 0代表创建成功
                     System.out.println(rc);
                     // 节点路径
                     System.out.println(path);
                     // 节点路径
                     System.out.println(name);
                     // 上下文参数
                     System.out.println(ctx);
                 }
             }, "这里传上下文参数内容,就会传给ctx");

            Thread.sleep(10000);
            System.out.println("结束");

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

修改节点

同样也有两种修改方式(异步和同步)

  // 同步
  setData(String path, byte[] data, int version)
  // 异步
  setData(String path, byte[] data, int version, StatCallback callBack, Object ctx)
参数解释
path节点路径
data数据
version数据的版本号, -1代表不使用版本号,乐观锁机制
callBack异步回调 AsyncCallback.StatCallback,和之前的回调方法参数不同,这个可以获取节点状态
ctx传递上下文参数

案例:

package com.zookeeper;

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.concurrent.CountDownLatch;

public class ZNodeSet {

    /**
     * 定义成员变量
     */
    String IP = "127.0.0.1:2181";
    ZooKeeper zooKeeper;

    @Before
    public void Before(){
        System.out.println("Before:获取连接对象");

        try {

            // 计数器对象
            final CountDownLatch countDownLatch = new CountDownLatch(1);

            // 创建一个zookeeper对象,通过构造器转入连接IP跟端口、客户端与服务器之间会话连接超时,毫秒单位,6秒、匿名内部类监视器对象
            zooKeeper = new ZooKeeper(IP, 6000, new Watcher() {
                /**
                 * process方法是Watcher接口中的一个回调方法,当ZooKeeper向客户端发送一个Watcher事件通知时,客户端就会对相应的process方法进行回调
                 *
                 * WatchedEvent包含了每一个事件的三个基本属性:通知状态(keeperState),事件类型(EventType)和节点路径(path)
                 */
                @Override
                public void process(WatchedEvent watchedEvent) {

                    // 判断回调'watchedEvent(事件状态)'等于Event.KeeperState.SyncConnected常量时
                    if (watchedEvent.getState() == Event.KeeperState.SyncConnected){
                        System.out.println("连接成功");

                        // 告诉主线程不用再阻塞
                        countDownLatch.countDown();
                    }

                }
            });

            // 主线程阻塞等待连接对象的创建成功对象,经过连接,不一定连接成功,所有要进行阻塞等待
            countDownLatch.await();

        } catch (Exception e){
            e.printStackTrace();
        }
    }

    @After
    public void After(){
        System.out.println("After:释放资源");

        try {

            // 释放资源
            zooKeeper.close();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void set(){
        System.out.println("set:编写对应业务逻辑代码");

        try {

            // 修改节点 参数1:节点路径,参数2:节点数据,参数3:数据版本号 -1代表:不使用版本号,乐观锁机制
            zooKeeper.setData("/testNode/testNode01", "testNodeContent11".getBytes(), -1);

        }  catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Test
    public void set2(){
        System.out.println("set:编写对应业务逻辑代码");

        try {
            /**
             * testNodeContent11
             * cZxid = 0xba
             * ctime = Wed Jan 06 10:10:21 CST 2021
             * mZxid = 0xbc
             * mtime = Wed Jan 06 10:19:51 CST 2021
             * pZxid = 0xba
             * cversion = 0
             * dataVersion = 1
             * aclVersion = 0
             * ephemeralOwner = 0x0
             * dataLength = 17
             * numChildren = 0
             */

            // 返回stat属性结构对象 修改节点 参数1:节点路径,参数2:节点数据,参数3:数据版本号,这里版本号要与'dataVersion'对应一致,如果两者版本号不一致则报'KeeperException$BadVersionException'异常
            Stat stat =  zooKeeper.setData("/testNode/testNode01", "testNodeContent111".getBytes(), 1);

            System.out.println("当前版本号:" + stat.getVersion());

        }  catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Test
    public void set3(){
        System.out.println("set:编写对应业务逻辑代码");

        // 异步方式修改节点
        try {

            // 修改节点 参数1:节点路径,参数2:节点数据,参数3:数据版本号 -1代表:不使用版本号,乐观锁机制
            zooKeeper.setData("/testNode/testNode01", "testNodeContent1111".getBytes(), -1, new AsyncCallback.StatCallback() {
                @Override
                public void processResult(int i, String s, Object o, Stat stat) {
                    // 0代表:修改成功
                    System.out.println(i);
                    // 节点路径
                    System.out.println(s);
                    // 上下文参数
                    System.out.println(o);
                    // stat属性结构对象,这里通过stat属性结构对象获取当前版本号
                    System.out.println("当前版本号:" + stat.getVersion());
                }
            }, "这里传上下文参数内容,就会传给o");

            Thread.sleep(10000);
            System.out.println("结束");

        }  catch (Exception e) {
            e.printStackTrace();
        }
    }
}

删除节点

同样也有两种修改方式(异步和同步)

  // 同步
  delete(String path, int version)
  // 异步
  delete(String path, int version, AsyncCallback.VoidCallback callBack, Object ctx)
参数解释
path节点路径
version版本
callBack数据的版本号, -1代表不使用版本号,乐观锁机制
ctx传递上下文参数

案例:

package com.zookeeper;

import org.apache.zookeeper.AsyncCallback;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.concurrent.CountDownLatch;

public class ZNodeDelete {

    /**
     * 定义成员变量
     */
    String IP = "127.0.0.1:2181";
    ZooKeeper zooKeeper;

    @Before
    public void Before(){
        System.out.println("Before:获取连接对象");

        try {

            // 计数器对象
            final CountDownLatch countDownLatch = new CountDownLatch(1);

            // 创建一个zookeeper对象,通过构造器转入连接IP跟端口、客户端与服务器之间会话连接超时,毫秒单位,4 秒、匿名内部类监视器对象
            zooKeeper = new ZooKeeper(IP, 4000, new Watcher() {
                /**
                 * process方法是Watcher接口中的一个回调方法,当ZooKeeper向客户端发送一个Watcher事件通知时,客户端就会对相应的process方法进行回调
                 *
                 * WatchedEvent包含了每一个事件的三个基本属性:通知状态(keeperState),事件类型(EventType)和节点路径(path)
                 */
                @Override
                public void process(WatchedEvent watchedEvent) {

                    // 判断回调'watchedEvent(事件状态)'等于Event.KeeperState.SyncConnected常量时
                    if (watchedEvent.getState() == Event.KeeperState.SyncConnected){
                        System.out.println("连接成功");

                        // 告诉主线程不用再阻塞
                        countDownLatch.countDown();
                    }

                }
            });

            // 主线程阻塞等待连接对象的创建成功对象,经过连接,不一定连接成功,所有要进行阻塞等待
            countDownLatch.await();

        } catch (Exception e){
            e.printStackTrace();
        }
    }

    @After
    public void After(){
        System.out.println("After:释放资源");

        try {

            // 释放资源
            zooKeeper.close();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void delete(){
        System.out.println("delete:编写对应业务逻辑代码");

        try {

            // 删除节点 参数1:节点路径,参数2:数据版本号 -1代表:不使用版本号,乐观锁机制
            zooKeeper.delete("/testNode/testNode01", -1);

        }  catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Test
    public void delete2(){
        System.out.println("delete:编写对应业务逻辑代码");

        // 异步方式删除节点
        try {

            // 删除节点 参数1:节点路径,参数2:节点数据,参数3:数据版本号 -1代表:不使用版本号,乐观锁机制
            zooKeeper.delete("/testNode/testNode02", -1, new AsyncCallback.VoidCallback() {
                @Override
                public void processResult(int i, String s, Object o) {

                    // 0代表:删除成功
                    System.out.println(i);
                    // 节点路径
                    System.out.println(s);
                    // 上下文参数
                    System.out.println(o);
                }
            }, "这里传上下文参数内容,就会传给o");

            Thread.sleep(10000);
            System.out.println("结束");

        }  catch (Exception e) {
            e.printStackTrace();
        }
    }
}

查看节点

同样也有两种修改方式(异步和同步)

 // 同步
 getData(String path, boolean watch, Stat stat)
 getData(String path, Watcher watcher, Stat stat)
 // 异步
 getData(String path, boolean watch, DataCallback callBack, Object ctx)
 getData(String path, Watcher watcher, DataCallback callBack, Object ctx)
参数解释
path节点路径
boolean是否使用连接对象中注册的监听器
stat返回znode的属性数据
callBack异步回调接口,可以获得状态和数据
ctx传递上下文参数

案例:

package com.zookeeper;

import org.apache.zookeeper.AsyncCallback;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;

public class ZNodeGet {

    /**
     * 定义成员变量
     */
    String IP = "127.0.0.1:2181";
    ZooKeeper zooKeeper;

    @Before
    public void Before(){
        System.out.println("Before:获取连接对象");

        try {

            // 计数器对象
            final CountDownLatch countDownLatch = new CountDownLatch(1);

            // 创建一个zookeeper对象,通过构造器转入连接IP跟端口、客户端与服务器之间会话连接超时,毫秒单位,7秒、匿名内部类监视器对象
            zooKeeper = new ZooKeeper(IP, 7000, new Watcher() {
                /**
                 * process方法是Watcher接口中的一个回调方法,当ZooKeeper向客户端发送一个Watcher事件通知时,客户端就会对相应的process方法进行回调
                 *
                 * WatchedEvent包含了每一个事件的三个基本属性:通知状态(keeperState),事件类型(EventType)和节点路径(path)
                 */
                @Override
                public void process(WatchedEvent watchedEvent) {

                    // 判断回调'watchedEvent(事件状态)'等于Event.KeeperState.SyncConnected常量时
                    if (watchedEvent.getState() == Event.KeeperState.SyncConnected){
                        System.out.println("连接成功");

                        // 告诉主线程不用再阻塞
                        countDownLatch.countDown();
                    }

                }
            });

            // 主线程阻塞等待连接对象的创建成功对象,经过连接,不一定连接成功,所有要进行阻塞等待
            countDownLatch.await();

        } catch (Exception e){
            e.printStackTrace();
        }
    }

    @After
    public void After(){
        System.out.println("After:释放资源");

        try {

            // 释放资源
            zooKeeper.close();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void get(){
        System.out.println("get:编写对应业务逻辑代码");
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        try {

            Stat stat = new Stat();
            // 获取节点 参数1:节点路径,参数2:是否使用连接对象中注册的监听器,参数3:读取节点属性对象
            byte [] bytes = zooKeeper.getData("/testNode/testNode01", false, stat);
            // 打印
            System.out.println("打印该节点数据:" + new String(bytes));
            // 打印该节点的属性对象
            System.out.println("当前版本号:" + stat.getVersion() + "\n" + "当前创建时间:" + simpleDateFormat.format(new Date(stat.getCtime())) );

        }  catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Test
    public void get2(){
        System.out.println("get:编写对应业务逻辑代码");

        // 异步方式获取节点
        try {

            // 获取节点 参数1:节点路径,参数2:是否使用连接对象中注册的监听器,参数3:匿名内部类对象,参数4:给匿名内部类传递的上下文参数对象
            zooKeeper.getData("/testNode/testNode01", false, new AsyncCallback.DataCallback() {
                @Override
                public void processResult(int i, String s, Object o, byte[] bytes, Stat stat) {

                    // 0代表:获取成功
                    System.out.println(i);
                    // 节点路径
                    System.out.println(s);
                    // 上下文参数
                    System.out.println(o);
                    // 节点数据
                    System.out.println("当前节点数据:" + new String(bytes));
                    // 节点属性结构对象
                    System.out.println("当前版本号:" + stat.getVersion());
                }
            },"这里传上下文参数内容,就会传给o");

            Thread.sleep(10000);
            System.out.println("结束");

        }  catch (Exception e) {
            e.printStackTrace();
        }
    }
}

查看子节点

同样也有两种修改方式(异步和同步)

 // 同步
 getChildren(String path, boolean watch)
 getChildren(String path, Watcher watcher)
 getChildren(String path, boolean watch, Stat stat)    
 getChildren(String path, Watcher watcher, Stat stat)
 // 异步
 getChildren(String path, boolean watch, ChildrenCallback callBack, Object ctx)    
 getChildren(String path, Watcher watcher, ChildrenCallback callBack, Object ctx)
 getChildren(String path, Watcher watcher, Children2Callback callBack, Object ctx)    
 getChildren(String path, boolean watch, Children2Callback callBack, Object ctx)
参数解释
path节点路径
boolean是否使用连接对象中注册的监听器
callBack异步回调,可以获取节点列表
ctx传递上下文参数

案例:

package com.zookeeper;

import org.apache.zookeeper.AsyncCallback;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class ZNodeGetChildren {

    /**
     * 定义成员变量
     */
    String IP = "127.0.0.1:2181";
    ZooKeeper zooKeeper;

    @Before
    public void Before(){
        System.out.println("Before:获取连接对象");

        try {

            // 计数器对象
            final CountDownLatch countDownLatch = new CountDownLatch(1);

            // 创建一个zookeeper对象,通过构造器转入连接IP跟端口、客户端与服务器之间会话连接超时,毫秒单位,8秒、匿名内部类监视器对象
            zooKeeper = new ZooKeeper(IP, 8000, new Watcher() {
                /**
                 * process方法是Watcher接口中的一个回调方法,当ZooKeeper向客户端发送一个Watcher事件通知时,客户端就会对相应的process方法进行回调
                 *
                 * WatchedEvent包含了每一个事件的三个基本属性:通知状态(keeperState),事件类型(EventType)和节点路径(path)
                 */
                @Override
                public void process(WatchedEvent watchedEvent) {

                    // 判断回调'watchedEvent(事件状态)'等于Event.KeeperState.SyncConnected常量时
                    if (watchedEvent.getState() == Event.KeeperState.SyncConnected){
                        System.out.println("连接成功");

                        // 告诉主线程不用再阻塞
                        countDownLatch.countDown();
                    }

                }
            });

            // 主线程阻塞等待连接对象的创建成功对象,经过连接,不一定连接成功,所有要进行阻塞等待
            countDownLatch.await();

        } catch (Exception e){
            e.printStackTrace();
        }
    }

    @After
    public void After(){
        System.out.println("After:释放资源");

        try {

            // 释放资源
            zooKeeper.close();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void getChildren(){
        System.out.println("getChildren:编写对应业务逻辑代码");

        try {

            Stat stat = new Stat();
            // 获取节点 参数1:节点路径,参数2:是否使用连接对象中注册的监听器
            List<String> childrenList = zooKeeper.getChildren("/testNode", false);
            // 打印
            for (String childrenName:
                 childrenList) {

                System.out.println("当前节点下有这些子节点:" + childrenName);
            }

        }  catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Test
    public void getChildren2(){
        System.out.println("getChildren:编写对应业务逻辑代码");
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        try {

            Stat stat = new Stat();
            // 获取节点 参数1:节点路径,参数2:是否使用连接对象中注册的监听器,参数3:读取节点属性对象
            List<String> childrenList = zooKeeper.getChildren("/testNode", false, stat);
            // 打印所有子节点
            for (String childrenName:
                    childrenList) {

                System.out.println("当前节点下有这些子节点:" + childrenName + ",当前节点版本号:" + stat.getVersion()  + ",当前节点创建时间:" + simpleDateFormat.format(new Date(stat.getCtime())));
            }

        }  catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Test
    public void getChildren3(){
        System.out.println("getChildren:编写对应业务逻辑代码");

        // 异步方式获取子节点
        try {

            zooKeeper.getChildren("/testNode", false, new AsyncCallback.ChildrenCallback() {
                @Override
                public void processResult(int i, String s, Object o, List<String> list) {

                    // 0代表:获取子节点成功
                    System.out.println(i);
                    // 节点路径
                    System.out.println(s);
                    // 上下文参数
                    System.out.println(o);
                    // 打印所有子节点
                    for (String childrenName:
                            list) {

                        System.out.println("当前节点下有这些子节点:" + childrenName);
                    }
                }
            },"这里传上下文参数内容,就会传给o");

            Thread.sleep(10000);
            System.out.println("结束");

        }  catch (Exception e) {
            e.printStackTrace();
        }
    }
}

检查节点是否存在

同样也有两种修改方式(异步和同步)

 // 同步
 exists(String path, boolean watch)
 exists(String path, Watcher watcher)
 // 异步
 exists(String path, boolean watch, StatCallback cb, Object ctx)
 exists(String path, Watcher watcher, StatCallback cb, Object ctx)
参数解释
path节点路径
boolean是否使用连接对象中注册的监听器
callBack异步回调,可以获取节点列表
ctx传递上下文参数

案例:

package com.zookeeper;

import org.apache.zookeeper.AsyncCallback;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.concurrent.CountDownLatch;

public class ZNodeExists {

    /**
     * 定义成员变量
     */
    String IP = "127.0.0.1:2181";
    ZooKeeper zooKeeper;

    @Before
    public void Before(){
        System.out.println("Before:获取连接对象");

        try {

            // 计数器对象
            final CountDownLatch countDownLatch = new CountDownLatch(1);

            // 创建一个zookeeper对象,通过构造器转入连接IP跟端口、客户端与服务器之间会话连接超时,毫秒单位,9秒、匿名内部类监视器对象
            zooKeeper = new ZooKeeper(IP, 9000, new Watcher() {
                /**
                 * process方法是Watcher接口中的一个回调方法,当ZooKeeper向客户端发送一个Watcher事件通知时,客户端就会对相应的process方法进行回调
                 *
                 * WatchedEvent包含了每一个事件的三个基本属性:通知状态(keeperState),事件类型(EventType)和节点路径(path)
                 */
                @Override
                public void process(WatchedEvent watchedEvent) {

                    // 判断回调'watchedEvent(事件状态)'等于Event.KeeperState.SyncConnected常量时
                    if (watchedEvent.getState() == Event.KeeperState.SyncConnected){
                        System.out.println("连接成功");

                        // 告诉主线程不用再阻塞
                        countDownLatch.countDown();
                    }

                }
            });

            // 主线程阻塞等待连接对象的创建成功对象,经过连接,不一定连接成功,所有要进行阻塞等待
            countDownLatch.await();

        } catch (Exception e){
            e.printStackTrace();
        }
    }

    @After
    public void After(){
        System.out.println("After:释放资源");

        try {

            // 释放资源
            zooKeeper.close();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void exists(){
        System.out.println("exists:编写对应业务逻辑代码");

        try {
            // 判断该节点是否存在
            // 获取节点 参数1:节点路径,参数2:是否使用连接对象中注册的监听器
            Stat stat = zooKeeper.exists("/testNode2",false);
            // 打印节点属性对象,存在时:显示该节点属性对象数据;不存在时:显示null
            System.out.println(stat);

        }  catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Test
    public void exists2(){
        System.out.println("exists:编写对应业务逻辑代码");

        // 异步方式判断节点是否存在
        try {
            // 判断该节点是否存在
            // 获取节点 参数1:节点路径,参数2:是否使用连接对象中注册的监听器
            zooKeeper.exists("/testNode", false, new AsyncCallback.StatCallback() {
                @Override
                public void processResult(int i, String s, Object o, Stat stat) {
                    // 0代表:获取子节点成功
                    System.out.println(i);
                    // 节点路径
                    System.out.println(s);
                    // 上下文参数
                    System.out.println(o);
                    // 打印节点属性对象,存在时:显示该节点属性对象数据;不存在时:显示null
                    System.out.println(stat);
                }
            },"这里传上下文参数内容,就会传给o");

            Thread.sleep(10000);
            System.out.println("结束");

        }  catch (Exception e) {
            e.printStackTrace();
        }
    }
}

事件监听机制

watcher概念

zookeeper——watches官方入口

zookeeper提供了数据的发布/订阅功能,多个订阅者可同时监听某一特定主题对象,当该主题对象的自身状态发生变化时例如节点内容改变、节点下的子节点列表改变等,会实时、主动通知所有订阅者。

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

watcher机制事件上与观察者模式类似,也可看作是一种观察者模式在分布式场景下的实现方式。

zookeeper 所有的读操作——getData(), getChildren(), 和 exists() 都可以设置监视(watch),并且这些watch都由写操作来触发:createdeletesetData。监视事件可以理解为一次性的触发器。

watcher架构

watcher实现由三个部分组成

  • zookeeper服务端
  • zookeeper客户端
  • 客户端的WatchManager管理对象

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

watcher特性

特性说明
一次性watcher一次性的,一旦被触发就会移除,再次使用时需要重新注册
客户端顺序回调watcher回调是顺序串行执行的,只有回调后客户端才能看到最新的数据状态。一个watcher回调逻辑不应该太多,以免影响别的watcher执行
轻量级WatchEvent是最小的通信单位,结构上只包含通知状态、事件类型和节点路径,并不会告诉数据节点变化前后的具体内容
时效性watcher只有在当前session彻底失效时才会无效,若在session有效期内快速重连成功,则watcher依然存在,仍可接收到通知;

watcher接口设计

Watcher是一个接口,任何实现了Watcher接口的类就算一个新的WatcherWatcher内部包含了两个枚举类:KeeperStateEventType
在这里插入图片描述

Watcher通知状态(KeeperState)

KeeperState是客户端与服务端连接状态发生变化时对应的通知类型。路径为org.apache.zookeeper.Watcher.EventKeeperState,是一个枚举类,其枚举属性如下:

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

Watcher事件类型(EventType)

EventType数据节点znode发生变化时对应的通知类型。EventType变化时KeeperState永远处于SyncConnected通知状态下;当keeperState发生变化时,EventType永远为None。其路径为org.apache.zookeeper.Watcher.Event.EventType,是一个枚举类,枚举属性如下:

枚举属性说明
None
NodeCreatedWatcher监听的数据节点被创建时
NodeDeletedWatcher监听的数据节点被删除时
NodeDataChangedWatcher监听的数据节点内容发生更改时(无论数据是否真的变化)
NodeChildrenChangedWatcher监听的数据节点的子节点列表发生变更时
  • 注意:客户端接收到的相关事件通知中只包含状态以及类型等信息,不包含节点变化前后的具体内容,变化前的数据需业务自身存储,变化后的数据需要调用get等方法重新获取

捕获相应的事件

上面讲到zookeeper客户端连接的状态和zookeeperznode节点监听的事件类型,下面我们来讲解如何建立zookeeperwatcher监听。在zookeeper中采用zk.getData(path,watcher,stat)、zk.getChildren(path,watch)、zk.exists(path,watch)这样的方式来为某个znode注册监听 。

下表以node-x节点为例,说明调用的注册方法和可用监听事件间的关系:

注册方式created创建childrenChanged子节点内容变化Changed内容变化Deleted 删除
zk.getData("/node-x",watcher)可监控可监控
zk.getChildren("/node-x",watcher)可监控可监控
zk.exists("/node-x",watcher)可监控可监控可监控

注册watcher的方法

客户端与服务器端的连接状态

  • KeeperState:通知状态

  • SyncConnected:客户端与服务器正常连接时

  • Disconnected:客户端与服务器断开连接时

  • Expired:会话session失效时

  • AuthFailed:身份认证失败时

  • 事件类型为:None

案例:

package com.watcher;

import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import java.util.concurrent.CountDownLatch;

/**
 *
 * 当前任何类实现Watcher接口后,该类就是一个监听对象了
 */
public class ZookeeperConnectionWatcher implements Watcher {

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

    /**
     * 实现Watcher接口的process方法
     *
     * process方法是Watcher接口中的一个回调方法,当ZooKeeper服务端向客户端发送一个Watcher事件通知时,客户端就会对相应的process方法进行回调
     *
     * WatchedEvent包含了每一个事件的三个基本属性:通知状态(keeperState),事件类型(EventType)和节点路径(path)
     * @param watchedEvent 观察者监听事件对象
     */
    @Override
    public void process(WatchedEvent watchedEvent) {

        try{
            // 判断是否:是事件类型
            if (watchedEvent.getType() == Event.EventType.None){
                // 判断是否:是回调'watchedEvent(通知状态(keeperState))'等于Event.KeeperState.SyncConnected常量时
                if (watchedEvent.getState() == Event.KeeperState.SyncConnected){
                    System.out.println("连接创建成功!");
                    // 连接成功后,可以让等待的线程继续向下执行了
                    countDownLatch.countDown();
                }
                // 判断是否:是回调'watchedEvent(通知状态(keeperState))'等于Event.KeeperState.Disconnected常量时
                else if (watchedEvent.getState() == Event.KeeperState.Disconnected){
                    System.out.println("断开连接");
                    // 网络异常断开连接的时候,可以捕获到这个异常,此时可以做相对应的业务处理
                }
                // 判断是否:是回调'watchedEvent(通知状态(keeperState))'等于Event.KeeperState.Expired常量时
                else  if (watchedEvent.getState() == Event.KeeperState.Expired){
                    System.out.println("会话超时");
                    // 超过了客户端与服务器之间会话连接超时时间,可以捕获到这个异常,此时可以做相对应的业务处理
                    // 重新连接zookeeper服务器
                    zooKeeper = new ZooKeeper("192.168.1.136:2181", 5000,new ZookeeperConnectionWatcher());
                }
                // 判断是否:是回调'watchedEvent(通知状态(keeperState))'等于Event.KeeperState.AuthFailed常量时
                else  if (watchedEvent.getState() == Event.KeeperState.AuthFailed){
                    System.out.println("认证失败");
                    // KeeperException$AuthFailedException: KeeperErrorCode = AuthFailed for /testNode2认证失败后,可以捕获到这个异常,此时可以做相对应的业务处理
                }
            }
        } catch (Exception e){
            e.printStackTrace();
        }

    }

    public static void main(String[] args) {

        try{
            // 连接zookeeper服务器 参数1:IP地址:端口号,参数2:客户端与服务器之间会话连接超时,毫秒单位,5秒,参数3:实例化当前类对象,因为当前类已经实现了Watcher接口
            zooKeeper = new ZooKeeper("192.168.1.136:2181", 5000,new ZookeeperConnectionWatcher());
            // 因为连接zookeeper服务器是异步执行的,所有要阻塞线程等待连接创建
            countDownLatch.await();
            // 打印当前连接sessionID会话
            System.out.println(zooKeeper.getSessionId());
            /**
             * start-测试用户认证
             *
             * [zk: localhost:2181(CONNECTED) 0] addauth digest testUser:1234567
             * [zk: localhost:2181(CONNECTED) 1] create /testNode2 'testNodeConten2'
             * Created /testNode2
             * [zk: localhost:2181(CONNECTED) 2] getAcl /testNode2
             * 'world,'anyone
             * : cdrwa
             * [zk: localhost:2181(CONNECTED) 3] setAcl /testNode2 auth:testUser:crdwa
             * [zk: localhost:2181(CONNECTED) 4] getAcl /testNode2
             * 'digest,'testUser:9qfzgMhZAYAB5UvvhlpjjkRhwng=
             * : cdrwa
             * [zk: localhost:2181(CONNECTED) 5]
             */
            // 添加错误的授权用户
            zooKeeper.addAuthInfo("digest1","testUser:1234561".getBytes());
            byte[] bytes = zooKeeper.getData("/testNode2",false,null);
            System.out.println("数据:" + new String(bytes));
            /**
             * end-测试用户认证
             */
            // 休眠50秒
            Thread.sleep(50000);

            // 释放资源,关闭连接
            zooKeeper.close();
            System.out.println("结束");
        } catch (Exception e){

            e.printStackTrace();
        }
    }

}

watcher检查节点

exists

  • exists(String path, boolean b):使用连接对象的监视器

  • exists(String path, Watcher w):自定义监视器

能捕获3种事件类型

  • NodeCreated节点创建

  • NodeDeleted节点删除

  • NodeDataChanged节点内容

案例:

package com.watcher;

import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;

public class ZookeeperWatcherExists {

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

    /**
     * 前置通知, 在方法执行之前执行
     */
    @Before
    public void Before() throws IOException,InterruptedException {
        System.out.println("Before:获取连接对象");
        // 计数器对象
        final CountDownLatch countDownLatch = new CountDownLatch(1);

        zooKeeper = new ZooKeeper(IP, 6000, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                System.out.println("连接对象的参数!");
                // 连接成功
                if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
                    // 连接成功后,可以让等待的线程继续向下执行了
                    countDownLatch.countDown();
                }
                /**
                 * watcherExists()方法开启监听后,打印监听参数:
                 * 连接对象的参数!
                 * path =/testNode
                 * watchedEvent =NodeCreated
                 * 
                 * 连接对象的参数!
                 * path =/testNode
                 * watchedEvent =NodeDataChanged
                 *
                 * 连接对象的参数!
                 * path =/testNode
                 * watchedEvent =NodeDeleted
                 */
                System.out.println("path =" + watchedEvent.getPath());
                System.out.println("watchedEvent =" + watchedEvent.getType());
            }
        });
        // 因为连接zookeeper服务器是异步执行的,所以要阻塞线程等待连接创建
        countDownLatch.await();
    }

    /**
     * 后置通知, 在方法执行之后执行
     */
    @After
    public void After() throws InterruptedException{
        System.out.println("After:释放资源");

        zooKeeper.close();
    }

    /**
     * 使用连接对象的监听器
     */
    @Test
    public void watcherExists() throws KeeperException, InterruptedException {

        // 参数1:节点路径,参数2:是否监听,false:不开启监听;true:开启监听,使用连接对象中的Watcher对象进行监听
        zooKeeper.exists("/testNode", true);

        Thread.sleep(50000);
        System.out.println("结束");
    }

    /**
     * 使用自定义监听对象
     */
    @Test
    public void watcherExists2() throws KeeperException, InterruptedException {

        // 参数1:节点路径,参数2:使用自定义监听器
        zooKeeper.exists("/testNode", new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                System.out.println("自定义监听器");

                /**
                 * 自定义监听器
                 * path =/testNode2
                 * watchedEvent =NodeCreated
                 *
                 * 自定义监听器
                 * path =/testNode
                 * watchedEvent =NodeDataChanged
                 *
                 * 自定义监听器
                 * path =/testNode2
                 * watchedEvent =NodeDeleted
                 */
                System.out.println("path =" + watchedEvent.getPath());
                System.out.println("watchedEvent =" + watchedEvent.getType());
            }
        });

        Thread.sleep(50000);
        System.out.println("结束");
    }

    /**
     * watcher一次性通知
     */
    @Test
    public void watcherExists3() throws KeeperException, InterruptedException {

        // watcher一次性的,注册一次就只能使用一次。但是可以通过代码实现不是一次性,请看watcherExists4()方法
        Watcher watcher = new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                System.out.println("path =" + watchedEvent.getPath());
                System.out.println("watchedEvent =" + watchedEvent.getType());
            }
        };

        zooKeeper.exists("/testNode", watcher);
        Thread.sleep(50000);
        System.out.println("结束");
    }

    /**
     * 实现非一次性通知
     */
    @Test
    public void watcherExists4() throws KeeperException, InterruptedException {

        Watcher watcher = new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                try{

                    System.out.println("path =" + watchedEvent.getPath());
                    System.out.println("watchedEvent =" + watchedEvent.getType());
                    // 在通知里面在再次注册通知,调用自己
                    zooKeeper.exists("/testNode", this);
                } catch (Exception e){
                    e.printStackTrace();
                }

            }
        };

        zooKeeper.exists("/testNode", watcher);
        Thread.sleep(50000);
        System.out.println("结束");
    }

    /**
     * 注册多个监听器对象
     */
    @Test
    public void watcherExists5() throws KeeperException, InterruptedException {
        
        zooKeeper.exists("/testNode", new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                System.out.println("1");
                System.out.println("path =" + watchedEvent.getPath());
                System.out.println("watchedEvent =" + watchedEvent.getType());
            }
        });
        zooKeeper.exists("/testNode", new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                System.out.println("2");
                System.out.println("path =" + watchedEvent.getPath());
                System.out.println("watchedEvent =" + watchedEvent.getType());
            }
        });

        Thread.sleep(50000);
        System.out.println("结束");
    }
}

getData

  • getData(String path, boolean b, Stat stat):使用连接对象的监视器
  • getData(String path, Watcher w, Stat stat):自定义监视器

能捕获2种事件类型

  • NodeDeleted节点删除
  • NodeDataChange节点内容发生变化

案例:

package com.watcher;

import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;

public class ZookeeperWatcherGetData {

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

    /**
     * 前置通知, 在方法执行之前执行
     */
    @Before
    public void Before() throws IOException,InterruptedException {
        System.out.println("Before:获取连接对象");
        // 计数器对象
        final CountDownLatch countDownLatch = new CountDownLatch(1);

        zooKeeper = new ZooKeeper(IP, 7000, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                System.out.println("连接对象的参数!");
                // 连接成功
                if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
                    // 连接成功后,可以让等待的线程继续向下执行了
                    countDownLatch.countDown();
                }
                /**
                 * watcherGetData()方法开启监听后,打印监听参数:
                 * 连接对象的参数!
                 * path =/testNode
                 * watchedEvent =NodeDataChanged
                 *
                 * 连接对象的参数!
                 * path =/testNode
                 * watchedEvent =NodeDeleted
                 */
                System.out.println("path =" + watchedEvent.getPath());
                System.out.println("watchedEvent =" + watchedEvent.getType());
            }
        });
        // 因为连接zookeeper服务器是异步执行的,所以要阻塞线程等待连接创建
        countDownLatch.await();
    }

    /**
     * 后置通知, 在方法执行之后执行
     */
    @After
    public void After() throws InterruptedException{
        System.out.println("After:释放资源");

        zooKeeper.close();
    }

    @Test
    public void watcherGetData() throws KeeperException, InterruptedException {

        // 参数1:节点路径,参数2:是否监听,false:不开启监听;true:开启监听,使用连接对象中的Watcher对象进行监听,参数3:节点属性数据结构
        byte[] bytes = zooKeeper.getData("/testNode",true,null);

        System.out.println("获取的内容:" + new String(bytes));

        Thread.sleep(50000);
        System.out.println("结束");

    }

    /**
     * 使用自定义监听对象
     */
    @Test
    public void watcherGetData2() throws KeeperException, InterruptedException {

        // 参数1:节点路径,参数2:使用自定义监听器,参数3:节点属性数据结构
        byte[] bytes = zooKeeper.getData("/testNode", new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                System.out.println("自定义监听器");

                /**
                 * 自定义监听器
                 * path =/testNode
                 * watchedEvent =NodeDataChanged
                 *
                 * 自定义监听器
                 * path =/testNode
                 * watchedEvent =NodeDeleted
                 */
                System.out.println("path =" + watchedEvent.getPath());
                System.out.println("watchedEvent =" + watchedEvent.getType());
            }
        }, null);

        System.out.println("获取的内容:" + new String(bytes));

        Thread.sleep(50000);
        System.out.println("结束");

    }

    /**
     * watcher一次性通知
     */
    @Test
    public void watcherGetData3() throws KeeperException, InterruptedException {

        // watcher一次性的,注册一次就只能使用一次。但是可以通过代码实现不是一次性,请看watcherGetData4()方法
        Watcher watcher = new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                System.out.println("path =" + watchedEvent.getPath());
                System.out.println("watchedEvent =" + watchedEvent.getType());
            }
        };

        byte[] bytes = zooKeeper.getData("/testNode", watcher, null);

        System.out.println("获取的内容:" + new String(bytes));

        Thread.sleep(50000);
        System.out.println("结束");
    }

    /**
     * 实现非一次性通知
     */
    @Test
    public void watcherGetData4() throws KeeperException, InterruptedException {

        Watcher watcher = new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                try{

                    System.out.println("path =" + watchedEvent.getPath());
                    System.out.println("watchedEvent =" + watchedEvent.getType());
                    // 判断节点内容是否改变,true:才再次注册监听;false:节点不存在被删除了就没有必要再注册监听了
                    if (watchedEvent.getType() == Event.EventType.NodeDataChanged) {
                        // 在通知里面在再次注册通知,调用自己
                        byte[] bytes = zooKeeper.getData("/testNode", this, null);

                        System.out.println("获取的内容:" + new String(bytes));
                    }
                } catch (Exception e){
                    e.printStackTrace();
                }

            }
        };

        byte[] bytes = zooKeeper.getData("/testNode", watcher,null);
        System.out.println("获取的内容:" + new String(bytes));
        Thread.sleep(50000);
        System.out.println("结束");
    }

    /**
     * 注册多个监听器对象
     */
    @Test
    public void watcherGetData5() throws KeeperException, InterruptedException {

        byte[] bytes = zooKeeper.getData("/testNode", new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                try{
                    System.out.println("1");
                    System.out.println("path =" + watchedEvent.getPath());
                    System.out.println("watchedEvent =" + watchedEvent.getType());
                    // 判断节点内容是否改变,true:才再次注册监听;false:节点不存在被删除了就没有必要再注册监听了
                    if (watchedEvent.getType() == Event.EventType.NodeDataChanged) {
                        // 在通知里面在再次注册通知,调用自己
                        byte[] bytes = zooKeeper.getData("/testNode", this, null);

                        System.out.println("获取的内容1-1:" + new String(bytes));
                    }
                } catch (Exception e){
                    e.printStackTrace();
                }
            }
        }, null);

        byte[] bytes2 = zooKeeper.getData("/testNode", new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                try{
                    System.out.println("2");
                    System.out.println("path =" + watchedEvent.getPath());
                    System.out.println("watchedEvent =" + watchedEvent.getType());
                    // 判断节点内容是否改变,true:才再次注册监听;false:节点不存在被删除了就没有必要再注册监听了
                    if (watchedEvent.getType() == Event.EventType.NodeDataChanged) {
                        // 在通知里面在再次注册通知,调用自己
                        byte[] bytes = zooKeeper.getData("/testNode", this, null);

                        System.out.println("获取的内容2-2:" + new String(bytes));
                    }
                } catch (Exception e){
                    e.printStackTrace();
                }
            }
        }, null);

        System.out.println("获取的内容1:" + new String(bytes));
        System.out.println("获取的内容2:" + new String(bytes2));

        Thread.sleep(50000);
        System.out.println("结束");

    }
}

getChildren

  • getChildren(String path, boolean b)
  • getChildren(String path, Watcher w)

能捕获2种事件类型

  • NodeChildrenChanged子节点发生变化
  • NodeDeleted节点删除

案例:

package com.watcher;

import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class ZookeeperWatcherGetChild {

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

    /**
     * 前置通知, 在方法执行之前执行
     */
    @Before
    public void Before() throws IOException,InterruptedException {
        System.out.println("Before:获取连接对象");
        // 计数器对象
        final CountDownLatch countDownLatch = new CountDownLatch(1);

        zooKeeper = new ZooKeeper(IP, 8000, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                System.out.println("连接对象的参数!");
                // 连接成功
                if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
                    // 连接成功后,可以让等待的线程继续向下执行了
                    countDownLatch.countDown();
                }
                /**
                 * watcherGetData()方法开启监听后,打印监听参数:
                 * 连接对象的参数!
                 * path =/testNode
                 * watchedEvent =NodeChildrenChanged
                 *
                 * 连接对象的参数!
                 * path =/testNode
                 * watchedEvent =NodeDeleted
                 */
                System.out.println("path =" + watchedEvent.getPath());
                System.out.println("watchedEvent =" + watchedEvent.getType());
            }
        });
        // 因为连接zookeeper服务器是异步执行的,所以要阻塞线程等待连接创建
        countDownLatch.await();
    }

    /**
     * 后置通知, 在方法执行之后执行
     */
    @After
    public void After() throws InterruptedException{
        System.out.println("After:释放资源");

        zooKeeper.close();
    }

    /**
     * 使用连接对象的监听器
     */
    @Test
    public void watcherGetChild() throws KeeperException, InterruptedException {

        // 参数1:节点路径,参数2:是否监听,false:不开启监听;true:开启监听,使用连接对象中的Watcher对象进行监听
        List<String> list = zooKeeper.getChildren("/testNode",true);

        for (String str:
             list) {

            System.out.println("/testNode节点下的所有子节点:"+str);
        }

        Thread.sleep(50000);
        System.out.println("结束");

    }

    /**
     * 使用自定义监听对象
     */
    @Test
    public void watcherGetChild2() throws KeeperException, InterruptedException {

        // 参数1:节点路径,参数2:使用自定义监听器,参数3:节点属性数据结构
        List<String> list = zooKeeper.getChildren("/testNode", new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                System.out.println("自定义监听器");

                /**
                 * 自定义监听器
                 * path =/testNode
                 * watchedEvent =NodeChildrenChanged
                 *
                 * 自定义监听器
                 * path =/testNode
                 * watchedEvent =NodeDeleted
                 */
                System.out.println("path =" + watchedEvent.getPath());
                System.out.println("watchedEvent =" + watchedEvent.getType());
            }
        }, null);

        for (String str:
                list) {

            System.out.println("/testNode节点下的所有子节点:"+str);
        }

        Thread.sleep(50000);
        System.out.println("结束");

    }

    /**
     * watcher一次性通知
     */
    @Test
    public void watcherGetChild3() throws KeeperException, InterruptedException {

        // watcher一次性的,注册一次就只能使用一次。但是可以通过代码实现不是一次性,请看watcherGetChild4()方法
        Watcher watcher = new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                System.out.println("path =" + watchedEvent.getPath());
                System.out.println("watchedEvent =" + watchedEvent.getType());
            }
        };

        List<String> list  = zooKeeper.getChildren("/testNode", watcher, null);

        for (String str:
                list) {

            System.out.println("/testNode节点下的所有子节点:"+str);
        }

        Thread.sleep(50000);
        System.out.println("结束");
    }

    /**
     * 实现非一次性通知
     */
    @Test
    public void watcherGetChild4() throws KeeperException, InterruptedException {

        Watcher watcher = new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                try{

                    System.out.println("path =" + watchedEvent.getPath());
                    System.out.println("watchedEvent =" + watchedEvent.getType());
                    // 判断节点内容是否改变,true:才再次注册监听;false:节点不存在被删除了就没有必要再注册监听了
                    if (watchedEvent.getType() == Event.EventType.NodeDataChanged) {
                        // 在通知里面在再次注册通知,调用自己
                        List<String> list  = zooKeeper.getChildren("/testNode", this, null);

                        for (String str:
                                list) {

                            System.out.println("/testNode节点下的所有子节点:"+str);
                        }
                    }
                } catch (Exception e){
                    e.printStackTrace();
                }

            }
        };

        List<String> list = zooKeeper.getChildren("/testNode", watcher,null);
        for (String str:
                list) {

            System.out.println("/testNode节点下的所有子节点:"+str);
        }
        Thread.sleep(50000);
        System.out.println("结束");
    }

    /**
     * 注册多个监听器对象
     */
    @Test
    public void watcherGetChild5() throws KeeperException, InterruptedException {

        List<String> list = zooKeeper.getChildren("/testNode", new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                try{
                    System.out.println("1");
                    System.out.println("path =" + watchedEvent.getPath());
                    System.out.println("watchedEvent =" + watchedEvent.getType());
                    // 判断节点内容是否改变,true:才再次注册监听;false:节点不存在被删除了就没有必要再注册监听了
                    if (watchedEvent.getType() == Event.EventType.NodeDataChanged) {
                        // 在通知里面在再次注册通知,调用自己
                        List<String> list = zooKeeper.getChildren("/testNode", this, null);

                        for (String str:
                                list) {

                            System.out.println("/testNode节点下的所有子节点1-1:"+str);
                        }
                    }
                } catch (Exception e){
                    e.printStackTrace();
                }
            }
        }, null);

        List<String> list2 = zooKeeper.getChildren("/testNode", new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                try{
                    System.out.println("2");
                    System.out.println("path =" + watchedEvent.getPath());
                    System.out.println("watchedEvent =" + watchedEvent.getType());
                    // 判断节点内容是否改变,true:才再次注册监听;false:节点不存在被删除了就没有必要再注册监听了
                    if (watchedEvent.getType() == Event.EventType.NodeDataChanged) {
                        // 在通知里面在再次注册通知,调用自己
                        List<String> list = zooKeeper.getChildren("/testNode", this, null);

                        for (String str:
                                list) {

                            System.out.println("/testNode节点下的所有子节点2-2:"+str);
                        }
                    }
                } catch (Exception e){
                    e.printStackTrace();
                }
            }
        }, null);

        for (String str:
                list) {

            System.out.println("/testNode节点下的所有子节点1:"+str);
        }
        for (String str:
                list2) {

            System.out.println("/testNode节点下的所有子节点2:"+str);
        }

        Thread.sleep(50000);
        System.out.println("结束");

    }

}

配置中心案例

工作中有这样的一个场景:数据库用户名和密码信息放在一个配置文件中,应用读取该配置文件,配置文件信息放入缓存。

若数据库的用户名和密码改变时候,还需要重新加载媛存,比较麻烦,通过 Zookeeper可以轻松完成,当数据库发生变化时自动完成缓存同步。

使用事件监听机制可以做出一个简单的配置中心

设计思路:

  1. 连接zookeeper服务器
  2. 读取zookeeper中的配置信息,注册watcher监听器,存入本地变量
  3. zookeeper中的配置信息发生变化时,通过watcher的回调方法捕获数据变化事件
  4. 重新获取配置信息

案例:

package com.config;

import com.watcher.ZookeeperConnectionWatcher;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import java.util.concurrent.CountDownLatch;

/**
 * 模拟一个配置中心
 */
public class ConfigCenter implements Watcher{

    String IP = "192.168.1.136:2181";
    // 计数器对象
    static CountDownLatch countDownLatch = new CountDownLatch(1);
    // 连接对象
    static ZooKeeper zooKeeper;

    // 用于本地化存储配置信息
    private String url;
    private String userName;
    private String password;

    /**
     * 实现Watcher接口的process方法 当前类监听器
     *
     * process方法是Watcher接口中的一个回调方法,当ZooKeeper服务端向客户端发送一个Watcher事件通知时,客户端就会对相应的process方法进行回调
     *
     * WatchedEvent包含了每一个事件的三个基本属性:通知状态(keeperState),事件类型(EventType)和节点路径(path)
     * @param watchedEvent 观察者监听事件对象
     */
    @Override
    public void process(WatchedEvent watchedEvent) {

        try{
            // 判断事件类型是否变化:没有变化将执行下面代码
            if (watchedEvent.getType() == Watcher.Event.EventType.None){
                // 判断是否:是回调'watchedEvent(通知状态(keeperState))'等于Event.KeeperState.SyncConnected常量时
                if (watchedEvent.getState() == Watcher.Event.KeeperState.SyncConnected){
                    System.out.println("连接创建成功!");
                    // 连接成功后,可以让等待的线程继续向下执行了
                    countDownLatch.countDown();
                }
                // 判断是否:是回调'watchedEvent(通知状态(keeperState))'等于Event.KeeperState.Disconnected常量时
                else if (watchedEvent.getState() == Watcher.Event.KeeperState.Disconnected){
                    System.out.println("断开连接");
                    // 网络异常断开连接的时候,可以捕获到这个异常,此时可以做相对应的业务处理
                }
                // 判断是否:是回调'watchedEvent(通知状态(keeperState))'等于Event.KeeperState.Expired常量时
                else if (watchedEvent.getState() == Watcher.Event.KeeperState.Expired){
                    System.out.println("会话超时");
                    // 超过了客户端与服务器之间会话连接超时时间,可以捕获到这个异常,此时可以做相对应的业务处理
                    // 重新连接zookeeper服务器
                    zooKeeper = new ZooKeeper("192.168.1.136:2181", 5000,new ZookeeperConnectionWatcher());
                }
                // 判断是否:是回调'watchedEvent(通知状态(keeperState))'等于Event.KeeperState.AuthFailed常量时
                else if (watchedEvent.getState() == Watcher.Event.KeeperState.AuthFailed){
                    System.out.println("认证失败");
                    // KeeperException$AuthFailedException: KeeperErrorCode = AuthFailed for /testNode2认证失败后,可以捕获到这个异常,此时可以做相对应的业务处理
                }
            }
            // 判断配置信息是否变化:有变化将执行下面代码
            else if (watchedEvent.getType() == Event.EventType.NodeDataChanged){
                // 重新读取最新的配置文件
                initValue();
            }
        } catch (Exception e){
            e.printStackTrace();
        }

    }

    /**
     * 创建构造方法
     *
     * 当前创建configCenter对象时,就会初始化到成员变量当中去了
     */
    public ConfigCenter() {

        initValue();
    }

    /**
     * 连接zookeeper服务器,读取配置信息
     */
    private void initValue(){
        try {
            // 创建连接对象
            // 调用当前类的监听器
            zooKeeper = new ZooKeeper(IP,5000, this);
            // 阻塞线程,等待连接创建
            countDownLatch.await();

            // 读取配置信息
            this.url = new String(zooKeeper.getData("/testNode/url",true,null));
            this.userName = new String(zooKeeper.getData("/testNode/userName",true,null));
            this.password = new String(zooKeeper.getData("/testNode/password",true,null));

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 模拟客户端读取配置中心配置信息
     */
    public static void main(String[] args) {

        try {
            // 通过构造方法获取配置文件中的配置信息
            ConfigCenter configCenter = new ConfigCenter();
            for (int i = 0; i <10 ; i++) {
                Thread.sleep(5000);
                // 获取Url、UserName、Password
                System.out.println("Url:" + configCenter.getUrl());
                System.out.println("UserName:" + configCenter.getUserName());
                System.out.println("Password:" + configCenter.getPassword());
            }
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

分布式唯一id案例

在过去的单库单表型系统中,通常第可以使用数据库字段自带的auto_ increment属性来自动为每条记录生成个唯一的ID。但是分库分表后,就无法在依靠数据库的auto_ increment属性来唯一标识一条记录了。此时我们就可以用zookeeper在分布式环境下生成全局唯一ID

设计思路:

  1. 连接zookeeper服务器
  2. 指定路径生成临时有序节点
  3. 取序列号及为分布式环境下的唯一ID

案例:

package com.Distributed;

import com.watcher.ZookeeperConnectionWatcher;
import org.apache.zookeeper.*;
import java.util.concurrent.CountDownLatch;

/**
 * 模拟分布式————唯一ID
 */
public class DistributedUniqueId implements Watcher{

    String IP = "192.168.1.136:2181";
    // 计数器对象
    static CountDownLatch countDownLatch = new CountDownLatch(1);
    // 连接对象
    static ZooKeeper zooKeeper;

    // 用户生成序列的节点
    String defaultPath = "/uniqueId";

    /**
     * 实现Watcher接口的process方法 当前类监听器
     *
     * process方法是Watcher接口中的一个回调方法,当ZooKeeper服务端向客户端发送一个Watcher事件通知时,客户端就会对相应的process方法进行回调
     *
     * WatchedEvent包含了每一个事件的三个基本属性:通知状态(keeperState),事件类型(EventType)和节点路径(path)
     * @param watchedEvent 观察者监听事件对象
     */
    @Override
    public void process(WatchedEvent watchedEvent) {

        try{
            // 判断事件类型是否变化:没有变化将执行下面代码
            if (watchedEvent.getType() == Watcher.Event.EventType.None){
                // 判断是否:是回调'watchedEvent(通知状态(keeperState))'等于Event.KeeperState.SyncConnected常量时
                if (watchedEvent.getState() == Watcher.Event.KeeperState.SyncConnected){
                    System.out.println("连接创建成功!");
                    // 连接成功后,可以让等待的线程继续向下执行了
                    countDownLatch.countDown();
                }
                // 判断是否:是回调'watchedEvent(通知状态(keeperState))'等于Event.KeeperState.Disconnected常量时
                else if (watchedEvent.getState() == Watcher.Event.KeeperState.Disconnected){
                    System.out.println("断开连接");
                    // 网络异常断开连接的时候,可以捕获到这个异常,此时可以做相对应的业务处理
                }
                // 判断是否:是回调'watchedEvent(通知状态(keeperState))'等于Event.KeeperState.Expired常量时
                else if (watchedEvent.getState() == Watcher.Event.KeeperState.Expired){
                    System.out.println("会话超时");
                    // 超过了客户端与服务器之间会话连接超时时间,可以捕获到这个异常,此时可以做相对应的业务处理
                    // 重新连接zookeeper服务器
                    zooKeeper = new ZooKeeper("192.168.1.136:2181", 6000,new ZookeeperConnectionWatcher());
                }
                // 判断是否:是回调'watchedEvent(通知状态(keeperState))'等于Event.KeeperState.AuthFailed常量时
                else if (watchedEvent.getState() == Watcher.Event.KeeperState.AuthFailed){
                    System.out.println("认证失败");
                    // KeeperException$AuthFailedException: KeeperErrorCode = AuthFailed for /testNode2认证失败后,可以捕获到这个异常,此时可以做相对应的业务处理
                }
            }
            // 判断配置信息是否变化:有变化将执行下面代码
//            else if (watchedEvent.getType() == Watcher.Event.EventType.NodeDataChanged){
//
//            }
        } catch (Exception e){
            e.printStackTrace();
        }

    }

    /**
     * 构造方法
     */
    public DistributedUniqueId() {

        try{
            // 创建连接对象
            // 调用当前类的监听器
            zooKeeper = new ZooKeeper(IP,5000, this);
            // 阻塞线程,等待连接创建
            countDownLatch.await();
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 生成ID的方法
     */
    public String getUniqueId(){
        String path = "";

        try{
            // 创建节点 参数1:节点路径,参数2:节点数据,参数3:world:anyone:cdrwa权限列表,参数4:临时有序节点
            path = zooKeeper.create(defaultPath,new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        } catch (Exception e){
            e.printStackTrace();
        }

        // 节点路径:/uniqueId000000001,然后截取掉前面的'节点名称'后得到临时ID
        return path.substring(9);
    }

    /**
     * 模拟客户端获取ID
     */
    public static void main(String[] args) {

        DistributedUniqueId distributedUniqueId = new DistributedUniqueId();

        for (int i = 0; i < 5; i++) {
            String id = distributedUniqueId.getUniqueId();
            System.out.println("唯一ID:" + id);
        }
    }
}

分布式锁

分布式锁有多种实现方式,比如通过数据库、redis都可实现。作为分布式协同工具Zookeeper,当然也有着标准的实现方式。下面介绍在zookeeper中如果实现排他锁

设计思路

  1. 每个客户端往/Locks下创建临时有序节点/Locks/Lock_,创建成功后/Locks下面会有每个客户端对应的节点,如/Locks/Lock_000000001
  2. 客户端取得/Locks下子节点,并进行排序,判断排在前面的是否为自己,如果自己的锁节点在第一位,代表获取锁成功
  3. 如果自己的锁节点不在第一位,则监听自己前一位的锁节点。例如,自己锁节点Lock_000000002,那么则监听Lock_000000001
  4. 当前一位锁节点(Lock_000000001)对应的客户端执行完成,释放了锁,将会触发监听客户端(Lock_000000002)的逻辑
  5. 监听客户端重新执行第2步逻辑,判断自己是否获得了锁
  6. zookeeper是有工具包的(这里采用手写)

案例:

package com.Distributed;

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;

/**
 * 模拟分布式————锁
 */
public class DistributedLock {

    String IP = "127.0.0.1:2181";
    // 计数器对象
    static CountDownLatch countDownLatch = new CountDownLatch(1);
    // 连接对象
    static ZooKeeper zooKeeper;

    // 创建节点路径
    private static final String LOCK_ROOT_PATH = "/Locks";
    private static final String LOCK_NODE_NAME = "/Lock_";
    private String lockPath;

    public DistributedLock() {
        try{
            // 创建连接对象
            // 调用当前类的监听器
            zooKeeper = new ZooKeeper(IP, 5000, new Watcher() {
                @Override
                public void process(WatchedEvent watchedEvent) {
                    // 判断事件类型是否变化:没有变化将执行下面代码
                    if (watchedEvent.getType() == Event.EventType.None){
                        // 判断是否:是回调'watchedEvent(通知状态(keeperState))'等于Event.KeeperState.SyncConnected常量时
                        if (watchedEvent.getState() == Event.KeeperState.SyncConnected){
                            System.out.println("连接成功");
                            countDownLatch.countDown();
                        }
                    }
                }
            });
            // 阻塞线程,等待连接创建
            countDownLatch.await();
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 获取锁
     */
    public void acquireLock(){
        // 创建锁节点
        createLock();
        // 尝试获取锁
        attemptLock();

    }

    /**
     * 创建锁节点
     */
    public void createLock(){

        try {

            Stat stat = zooKeeper.exists(LOCK_ROOT_PATH,false);
            // 判断Lock路径是否存在,不存在就创建
            if (stat == null){
                // 创建节点 参数1:节点路径,参数2:节点数据,参数3:world:anyone:cdrwa权限列表,参数4:持久化类型节点
                zooKeeper.create(LOCK_ROOT_PATH,new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
            }
            // 创建临时有序节点 参数1:节点路径,参数2:节点数据,参数3:world:anyone:cdrwa权限列表,参数4:临时有序节点
            lockPath = zooKeeper.create(LOCK_ROOT_PATH+LOCK_NODE_NAME,new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);
            // 打印节点
            System.out.println("节点创建成功:"+lockPath);
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 监视对象,监视上一个节点是否被删除,已删除说明已关联的客户端已获取到锁,并且已执行完代码将锁已释放掉了
     */
    Watcher watcher = new Watcher() {
        @Override
        public void process(WatchedEvent watchedEvent) {
            // 判断锁已经被删除了
            if (watchedEvent.getType() == Event.EventType.NodeDeleted){
                // 就可以释放同步代码中的等待,让它继续往下执行-苏醒
                synchronized (this) {
                    notifyAll();
                }
            }
        }
    };

    /**
     * 尝试获取锁
     */
    public void attemptLock(){

        try {
            // 获取'Locks'节点下的所有子节点
            List<String> stringList = zooKeeper.getChildren(LOCK_ROOT_PATH,false);
            // 对所有的子节点排序
            // 调用java下的工具类进行排序
            Collections.sort(stringList);
            // 截取字符串'/Locks/Lock_0000000001'截取后,'Lock_0000000001'
            int index = stringList.indexOf(lockPath.substring(LOCK_ROOT_PATH.length()+1));
            // 如果等于'0',说明是在第一位的,表明获取到了锁
            if (index == 0){
                System.out.println("获取锁成功");
                return;
            } else {
                // 先获取上一个节点的节点路径
                String paht = stringList.get(index-1);
                // 当前节点应该对上一个节点进行监视
                Stat stat = zooKeeper.exists(LOCK_ROOT_PATH+"/"+paht,watcher);
                // 等于null时,说明用完锁并释放了
                if (stat == null){
                    // 重新获取锁
                    attemptLock();
                }else {
                    // 有可能在等待着获取锁状态,也有可能在执行中没有释放掉锁,就应该将线程处于阻塞状态,等上一个锁释放掉再尝试获取锁
                    // 使用同步代码块来执行,传入锁的监视对象
                    synchronized (watcher){
                        // 进行等待
                        watcher.wait();
                    }

                    // 监听对象的同步代码中已经苏醒了,就继续让它获取锁
                    attemptLock();

                }
            }
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 释放锁
     */
    public void releaseLock(){

        try {
            // 删除临时有序节点
            zooKeeper.delete(this.lockPath,-1);
            // 释放连接资源
            zooKeeper.close();

            System.out.println("锁已释放:"+this.lockPath);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {

        try {
            DistributedLock distributedLock = new DistributedLock();
            distributedLock.createLock();
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}


package com.Distributed;

/**
 * 测试分布式————锁
 */
public class TestDistributedLock {
    /**
     * 模拟售票过程
     */
    private void sell(){
        System.out.println("售票开始");
        // 线程随机休眠数毫秒,模拟现实中的费时操作
        int sleepMills = 5000;

        try {
            // 代表复杂逻辑执行了一段时间
            Thread.sleep(sleepMills);
        } catch (Exception e){
            e.printStackTrace();
        }

        System.out.println("售票结束");
    }

    /**
     * 使用分布式锁使, 下面代码同步执行的
     */
    public void sellTicketWithLock(){
        // 创建锁对象
        DistributedLock lock = new DistributedLock();

        // 获取锁
        lock.acquireLock();

        // 售票过程
        sell();

        // 释放锁
        lock.releaseLock();
    }

    public static void main(String[] args) {
        TestDistributedLock testDistributedLock = new TestDistributedLock();
		// 调用
        for (int i = 0; i < 10; i++) {

            testDistributedLock.sellTicketWithLock();
        }
    }
}

Zookeeper集群搭建

zookeeper——复制模式官方入口

zookeeper——群集(多服务器)设置官方入口

zookeeper——配置参数官方入口

运行时复制的zookeeper

说明:对于复制模式,至少需要三个服务器,并且强烈建议您使用奇数个服务器。如果只有两台服务器,那么您将处于一种情况,如果其中一台服务器发生故障,则没有足够的计算机构成多数仲裁(zk采用的是过半数仲裁。因此,搭建的集群要容忍n个节点的故障,就必须有2n+1台计算机,这是因为宕掉n台后,集群还残余n+1台计算机,n+1台计算机中必定有一个最完整最接近leaderfollower,假如宕掉的n台都是有完整信息的,剩下的一台就会出现在残余的zk集群中。也就是说:zk为了安全,必须达到多数仲裁,否则没有leader,集群失败,具体体现在**leader选举-章**)。由于存在两个单点故障,因此两个服务器还不如单个服务器稳定。

——关于2n+1原则,Kafka官网有权威的解释(虽然Kafka不采用)http://kafka.apache.org/0110/documentation.html#design_replicatedlog

多数仲裁的设计是为了避免脑裂(zk,已经采用了多数仲裁,所以不会出现),和数据一致性的问题

  • 脑裂:由于网络延迟等各种因素,最终导致集群一分为二,各自独立运行(两个leader),集群就是坏的
  • 如果有两台服务器,两台都认为另外的zk宕掉,各自成为leader运行(假设可以,实际上选不出leader,可以实际搭建一个集群,看看一台zk是否能够成功集群,详见**leader选举**),就会导致数据不一致。
  • 如果有三台服务器,一台因为网络分区,无法连接,剩下两台网络正常,选举出了leader,集群正常
  • 以此类推
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9pQRoEgw-1608776267964)(assets/脑裂.png)]
    • zk的设计天生就是cap中的cp,所以不会出现上述的脑裂和数据一致性问题,我们搭建zk仅需保证2n+1原则

复制模式所需的conf / zoo.cfg文件类似于独立模式下使用的文件,但有一些区别。这是一个例子:

tickTime=2000
dataDir=/var/lib/zookeeper
clientPort=2181
initLimit=5
syncLimit=2
server.1=zoo1:2888:3888 # 这是多机部署
server.2=zoo2:2888:3888
server.3=zoo3:2888:3888
  • 新的键值**initLimitzookeeper用于限制选举中zookeeper服务连接到leader的时间,syncLimit**限制服务器与leader的过期时间
  • 对于这两个超时,您都可以使用tickTime指定时间单位。在此示例中,initLimit的超时为5个滴答声,即2000毫秒/滴答声,即10
  • 表格*server.X的条目列出了组成ZooKeeper服务的服务器。服务器启动时,它通过在数据目录中查找文件myid*来知道它是哪台服务器。该文件包含ASCII的服务器号。
  • 最后,记下每个服务器名称后面的两个端口号:“ 2888”“ 3888”。对等方使用前一个端口连接到其他对等方。这种连接是必需的,以便对等方可以进行通信,例如,以商定更新顺序。更具体地说,ZooKeeper服务器使用此端口将follower连接到leader。当出现新的leader者时,follower使用此端口打开与leaderTCP连接。因为默认的leader选举也使用TCP,所以我们当前需要另一个端口来进行leader选举。这是第二个端口。

正文搭建:单机环境下,jdkzookeeper安装完毕,基于一台虚拟机,进行zookeeper伪集群搭建zookeeper集群中包含3个节点,节点对外提供服务端口号,分别为218121822183

  1. 进入/usr/local/Cellar/zookeeper/基于zookeeper-3.6.2_1复制三份zookeeper安装好的服务器文件,目录名称分别为zookeeper2181zookeeper2182zookeeper2183
 cp -r zookeeper-3.6.2_1  zookeeper2182
 cp -r zookeeper-3.6.2_1  zookeeper2183
 cp -r zookeeper-3.6.2_1  zookeeper2184
 
 # cp -r zookeeper-3.1.10 ./zookeeper218{1..3}
  1. 修改zookeeper2182服务器对应配置文件
# 服务器对应端口号
clientPort=2181
# 数据快照文件所在路径
dataDir=/opt/zookeeper2181/data
# 集群配置信息
   # server:A=B:C:D
   # A:是一个数字,表示这个是服务器的编号
   # B:是这个服务器的ip地址
   # C:Zookeeper服务器之间通信的端口(数据互通,必须的)
   # D:Leader选举的端口
server.1=192.168.133.133:2287:3387  # 这是伪集群部署,注意端口号  
server.2=192.168.133.133:2288:3388
server.3=192.168.133.133:2289:3389
# 对,这些都是2181的配置文件
  1. 在上一步 dataDir指定的目录下,创建myid文件,然后在该文件添加上一步server配置的对应A数字
 # zookeeper2181对应的数字为1
 # /opt/zookeeper2181/data目录(即dataDir的目录下)下执行命令
 echo "1" > myid
  1. zookeeper2182、2183参照2/3进行相应配置

  2. 分别启动三台服务器,检验集群状态

    检查:cd进入bin目录./zkServer status

    登录命令:

 ./zkCli.sh -server 192.168.60.130:2181
 ./zkCli.sh -server 192.168.60.130:2182
 ./zkCli.sh -server 192.168.60.130:2183
 # 如果启动后没有显示出集群的状态,请自己检查端口和配置文件问题,主要是端口占用和配置文件问题
 # ss -lntpd | grep 2181

macOS环境:进行zookeeper伪集群搭建

  1. 进入/usr/local/etc/zookeeper配置目录下,分别创建3个配置文件zoo2182.cfgzoo2183.cfgzoo2184.cfg
  2. 进入/usr/local/etc/zookeeper配置目录下,分别创建3个文件夹存放data数据文件zk2182zk2183zk2183
  3. 分别进入/usr/local/etc/zookeeper/zk2182/usr/local/etc/zookeeper/zk2183/usr/local/etc/zookeeper/zk2184配置目录下,创建data文件夹用于存放myid文件
  4. 分别进入/usr/local/etc/zookeeper/zk2182/data/usr/local/etc/zookeeper/zk2183/data/usr/local/etc/zookeeper/zk2184/data文件夹,分别创建/usr/local/etc/zookeeper/zk2182/data/myid文件,内容为:1/usr/local/etc/zookeeper/zk2183/data/myid文件,内容为:2/usr/local/etc/zookeeper/zk2184/data/myid文件,内容为:3

zoo2182.cfg配置文件:

# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
# 数据目录
dataDir=/usr/local/etc/zookeeper/zk2182/data
# dataLogDir=/usr/local/var/run/zookeeper/zk1/logs
# the port at which the clients will connect
# 端口号
clientPort=2182
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1

# 集群配置信息
   # server:A=B:C:D
   # A:是一个数字,表示这个是服务器的编号
   # B:是这个服务器的ip地址
   # C:Zookeeper服务器之间通信的端口(数据互通,必须的)
   # D:Leader选举的端口
server.1=localhost:2287:3387 # 这是伪集群部署,注意端口号
server.2=localhost:2288:3388
server.3=localhost:2289:3389

zoo2183.cfg配置文件:

# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
# 数据目录
dataDir=/usr/local/etc/zookeeper/zk2183/data
# dataLogDir=/usr/local/var/run/zookeeper/zk1/logs
# the port at which the clients will connect
# 端口号
clientPort=2183
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1

server.1=localhost:2287:3387
server.2=localhost:2288:3388
server.3=localhost:2289:3389

zoo2184.cfg配置文件:

# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
# 数据目录
dataDir=/usr/local/etc/zookeeper/zk2184/data
# dataLogDir=/usr/local/var/run/zookeeper/zk1/logs
# the port at which the clients will connect
# 端口号
clientPort=2184
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1

server.1=localhost:2287:3387
server.2=localhost:2288:3388
server.3=localhost:2289:3389

启动zookeeper
先进入对应目录下$ cd /usr/local/etc/zookeeper,再分别使用命令zkServer start /usr/local/etc/zookeeper/zoo2182.cfgzkServer start /usr/local/etc/zookeeper/zoo2183.cfgzkServer start /usr/local/etc/zookeeper/zoo2184.cfg

查看zookeeper状态
先进入对应目录下$ cd /usr/local/etc/zookeeper,再分别使用命令zkServer status /usr/local/etc/zookeeper/zoo2182.cfg
执行结果:

ZooKeeper JMX enabled by default
Using config: /usr/local/etc/zookeeper/zoo2182.cfg
Client port found: 2182. Client address: localhost. Client SSL: false.
Mode: follower

zkServer status /usr/local/etc/zookeeper/zoo2183.cfg
执行结果:

ZooKeeper JMX enabled by default
Using config: /usr/local/etc/zookeeper/zoo2183.cfg
Client port found: 2183. Client address: localhost. Client SSL: false.
Mode: leader

zkServer status /usr/local/etc/zookeeper/zoo2184.cfg
执行结果:

ZooKeeper JMX enabled by default
Using config: /usr/local/etc/zookeeper/zoo2184.cfg
Client port found: 2184. Client address: localhost. Client SSL: false.
Mode: follower

连接zookeeper
先进入对应目录下$ cd /usr/local/etc/zookeeper,再分别使用命令zkServer start /usr/local/etc/zookeeper/zoo2182.cfg
执行结果:

ZooKeeper JMX enabled by default
Using config: /usr/local/etc/zookeeper/zoo2182.cfg
Starting zookeeper … STARTED

zkServer start /usr/local/etc/zookeeper/zoo2183.cfg
执行结果:

ZooKeeper JMX enabled by default
Using config: /usr/local/etc/zookeeper/zoo2183.cfg
Starting zookeeper … STARTED

zkServer start /usr/local/etc/zookeeper/zoo21834.cfg
执行结果:

ZooKeeper JMX enabled by default
Using config: /usr/local/etc/zookeeper/zoo2184.cfg
Starting zookeeper … STARTED

启动客户端
分别使用命令zkCli -server 127.0.0.1:2182zkCli -server 127.0.0.1:2183zkCli -server 127.0.0.1:2184

关闭zookeeper
zkServer stop /usr/local/etc/zookeeper/zoo2182.cfg
执行结果:

ZooKeeper JMX enabled by default
Using config: /usr/local/etc/zookeeper/zoo2182.cfg
Stopping zookeeper … STOPPED

zkServer stop /usr/local/etc/zookeeper/zoo2183.cfg
执行结果:

ZooKeeper JMX enabled by default
Using config: /usr/local/etc/zookeeper/zoo2183.cfg
Stopping zookeeper … STOPPED

zkServer stop /usr/local/etc/zookeeper/zoo2184.cfg
执行结果:

ZooKeeper JMX enabled by default
Using config: /usr/local/etc/zookeeper/zoo2184.cfg
Stopping zookeeper … STOPPED

一致性协议——zab协议

zab协议的全称是 Zookeeper Atomic Broadcast (zookeeper原子广播)。zookeeper是通过zab协议来保证分布式事务的最终一致性

基于zab协议,zookeeper集群中的角色主要有以下三类,如下所示:

角色描述
领导者(Leader)领导者负责进行投票的发起和决议,更新系统状态
学习者(Learner)-跟随者(Follower)Follower用于接收客户端请求并向客户端返回结果,在选主过程中参与投票
学习者(Learner)-观察者(ObServer)ObServer可以接收客户端连接,将写请求转发给leader节点。但ObServer不参加投票过程,只同步leader的状态。ObServer的目的是为了扩展系统,提高读取速度
客户端(Client)请求发起方

·zab广播模式工作原理,通过类似两端式提交协议的方式解决数据一致性:

在这里插入图片描述

  1. leader从客户端收到一个写请求
  2. leader生成一个新的事务并为这个事务生成一个唯一的ZXID
  3. leader将事务提议(propose)发送给所有的follows节点
  4. follower节点将收到的事务请求加入到本地历史队列(history queue)中,并发送ackleader,表示确认提议
  5. leader收到大多数follower(半数以上节点)的ack(acknowledgement)确认消息,leader会本地提交,并发送commit请求
  6. follower收到commit请求时,从历史队列中将事务请求commit

因为是半数以上的结点就可以通过事务请求,所以延迟不高。

如果在follows随从者写入信息,它将先request转发leader领导者,然后再执行以上6项流程

leader选举

1、服务器状态一共有四种:

  • looking:寻找leader状态。当服务器处于该状态时,它会认为当前集群中没有leader,因此需要进入leader选举状态
  • leading:领导者状态。表明当前服务器角色是leading
  • following:跟随者状态。表明当前服务器角色是follower
  • observing:观察者状态。表明当前服务器角色是observer

分为两种选举,服务器启动时的选举服务器运行时期的选举

2、服务器启动时期的leader选举

在集群初始化节点,当有一台服务器server1启动时,其单独无法进行和完成leader选举,当第二台服务器server2启动时,此时两台及其可以相互通信,每台及其都试图找到leader于是进入leader选举过程。选举过程如下:

  1. 每个server发出一个投票。由于是初始状态,server1server2都会将自己作为leader服务器来进行投票,每次投票都会包含所推举的myid服务器编号zxid事务ID,使用(myid,zxid)投票格式,此时server1的投票为(1,0),server2的投票为(2,0),然后各自将这个投票发给集群中的其它机器

  2. 集群中的每台服务器都接收来自集群中各个服务器的投票

  3. 处理投票。针对每一个投票,服务器都需要将别人的投票和自己的投票进行pk,规则如下

    • 优先检查zxidzxid比较大的服务器优先作为leader(zxid较大者保存的数据更多)

    • 如果zxid相同。那么就比较myidmyid较大的服务器作为leader服务器

      对于Server1而言,它的投票是(1,0),接收Server2的投票为(2,0),首先会比较两者的zxid,均为0,再比较myid,此时server2myid最大,于是更新自己的投票为(2,0),然后重新投票,对于server2而言,无需更新自己的投票,只是再次向集群中所有机器发出上一次投票信息即可

  4. 统计投票。每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息,对于server1、server2而言,都统计出集群中已经有两台机器接受了(2,0)的投票信息,此时便认为已经选举出了leader

  5. 改变服务器状态。一旦确定了leader,每个服务器就会更新自己的状态,如果是follower,那么就变更为following,如果是leader,就变更为leading

举例:如果我们有三个节点的集群,1,2,3,启动 1 和 2 后,2 一定会是 leader,3 再加入不会进行选举,而是直接成为follower—— 仔细观察 一台zk无法集群,没有leader

3、服务器运行时期选举

zookeeper运行期间,leader与非leader服务器各司其职,即使当有非leader服务器宕机或者新加入,此时也不会影响leader,但是一旦leader服务器挂了,那么整个集群将暂停对外服务,进入新一轮leader选举,其过程和启动时期的leader选举过程基本一致

假设正在运行的有server1server2server3三台服务器,当前leaderserver2,若某一时刻leader挂了,此时便开始Leader选举。选举过程如下

  1. 变更状态。leader挂后,余下的服务器都会将自己的服务器状态变更为looking,然后开始进入leader选举过程
  2. 每个server发出一个投票。在运行期间,每个服务器上的zxid可能不同,此时假定server1zxid122server3zxid122在第一轮投票中,server1和server3都会投自己,产生投票(1,122),(3,122),然后各自将投票发送给集群中所有机器
  3. 接收来自各个服务器的投票。与启动时过程相同
  4. 处理投票。与启动时过程相同,此时,server3将会成为leader
  5. 统计投票。与启动时过程相同
  6. 改变服务器的状态。与启动时过程相同

observer角色及其配置

zookeeper官方入口——Observers Guide

尽管ZooKeeper通过使用客户端直接连接到该集合的投票成员表现良好,但是此体系结构使其很难扩展到大量客户端。问题在于,随着我们添加更多的投票成员,写入性能会下降。这是由于以下事实:写操作需要(通常)集合中至少一半节点的同意,因此,随着添加更多的投票者,投票的成本可能会显着增加。

我们引入了一种称为Observer的新型ZooKeeper节点,该节点有助于解决此问题并进一步提高ZooKeeper的可伸缩性。观察员是合法的非投票成员,他们仅听取投票结果,而听不到投票结果。除了这种简单的区别之外,观察者的功能与跟随者的功能完全相同-客户端可以连接到观察者,并向其发送读写请求。观察者像追随者一样将这些请求转发给领导者,但是他们只是等待听取投票结果。因此,我们可以在不影响投票效果的情况下尽可能增加观察员的数量。

观察者还有其他优点。因为他们不投票,所以它们不是ZooKeeper选举中的关键部分。因此,它们可以在不损害ZooKeeper服务可用性的情况下发生故障或与群集断开连接。给用户带来的好处是,观察者可以通过比跟随者更不可靠的网络链接进行连接。实际上,观察者可用于与另一个数据中心的ZooKeeper服务器进行对话。观察者的客户端将看到快速读取,因为所有读取均在本地提供,并且由于缺少表决协议而需要的消息数量较小,因此写入会导致网络流量最小

ovserver角色特点

  1. 不参与集群的leader选举
  2. 不参与集群中写数据时的ack反馈

为了使用observer角色,在任何想变成observer角色的配置文件中加入如下配置:

peerType=observer

并在所有server的配置文件中,配置成observer模式的server的那行配置追加***:observer***,例如

server.1=192.168.133.133:2287:3387  # 注意端口号  
server.2=192.168.133.133:2288:3388
server.3=192.168.133.133:2289:3389:observer

注意2n+1原则——集群搭建

Zookeeper API连接集群

Zookeeper(String connectionString, int sessionTimeout, Watcher watcher)

  • connectionStringzookeeper集合主机
  • sessionTimeout:会话超时(以毫秒为单位)
  • watcher:实现"监听器"界面的对象。zookeeper集合通过监视器对象返回连接状态
    public static void main(String[] args) throws Exception {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        ZooKeeper connection = new ZooKeeper("192.168.133.133:2181,192.168.133.133:2182,192.168.133.133:2183", 5000, watchedEvent -> {
            if (watchedEvent.getState() == Watcher.Event.KeeperState.SyncConnected)
                System.out.println("连接成功");
            countDownLatch.countDown();
        });
        countDownLatch.await();
        connection.create("/hadoop",new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
        System.out.println(connection.getSessionId());
    }

curator介绍

关于第三方客户端的小介绍

zkClient有对dubbo的一些操作支持,但是zkClient几乎没有文档,下面是curator

curator简介

curatorNetflix公司开源的一个 zookeeper客户端,后捐献给 apachecurator框架在zookeeper原生API接口上进行了包装,解决了很多zooKeeper客户端非常底层的细节开发。提供zooKeeper各种应用场景(比如:分布式锁服务、集群领导选举、共享计数器、缓存机制、分布式队列等的抽象封装,实现了Fluent风格的APl接口,是最好用,最流行的zookeeper的客户端

原生zookeeperAPI的不足

  • 连接对象异步创建,需要开发人员自行编码等待
  • 连接没有自动重连超时机制
  • watcher一次注册生效一次
  • 不支持递归创建树形节点

curator特点

  • 解决session会话超时重连
  • watcher反复注册
  • 简化开发api
  • 遵循Fluent风格API

基础用法

案例:
pom.xml依赖

<dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <!-- Zookeeper 以及 curator -->
    <dependency>
      <groupId>org.apache.zookeeper</groupId>
      <artifactId>zookeeper</artifactId>
      <version>3.6.2</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.curator/curator-framework -->
    <dependency>
      <groupId>org.apache.curator</groupId>
      <artifactId>curator-framework</artifactId>
      <version>4.2.0</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.curator/curator-recipes -->
    <dependency>
      <groupId>org.apache.curator</groupId>
      <artifactId>curator-recipes</artifactId>
      <version>4.2.0</version>
    </dependency>
</dependencies>

连接zookeeper

package com.zookeeper.curator;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.retry.RetryNTimes;
import org.apache.curator.retry.RetryOneTime;
import org.apache.curator.retry.RetryUntilElapsed;

/**
 * 使用Curator连接Zookeeper集群
 */
public class CuratorConnection {

    public static void main(String[] args) {

        try{
            // session重连策略
            // 3秒后重连一次,只重连1次
//            RetryPolicy retryPolicy = new RetryOneTime(3000);

            // 每3秒重连一次,重连3次
//            RetryPolicy retryPolicy = new RetryNTimes(3,3000);

            // 每3秒重连一次,总等待时间超过10秒后停止重连
//            RetryPolicy retryPolicy = new RetryUntilElapsed(10000,3000);

            // 随着重连的次数增加,重连的间隔也会变成重连3次
            RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);

            // 创建连接对象,通过Factory工厂对象生产连接
            CuratorFramework curatorFramework = CuratorFrameworkFactory.builder()
                    // 客户端连接服务端所依赖的IP地址以及端口号,集群连接每一个IP地址端口号后面加上','就可以了
                    .connectString("127.0.0.1:2182,127.0.0.1:2183,127.0.0.1:2184")
                    // 客户端与服务器端连接会话session超时时间,单位:毫秒 5秒
                    .sessionTimeoutMs(5000)
                    // 客户端与服务器端连接会话超时时,尝试重新连接
                    .retryPolicy(retryPolicy)
                    // curatorFramework执行一系列命令时,会根据namespace作为父级节点。命令空间
                    .namespace("createCurator")
                    // 构建连接对象
                    .build();

            // 打开连接
            curatorFramework.start();

            // true:连接成功;false:连接失败
            System.out.println("连接状态:"+curatorFramework.isStarted());

            // 关闭连接
            curatorFramework.close();

        } catch (Exception e){
            e.printStackTrace();
        }
    }
}
  • session重连策略
    • RetryPolicy retry Policy = new RetryOneTime(3000);
      说明:三秒后重连一次,只重连一次
    • RetryPolicy retryPolicy = new RetryNTimes(3,3000);
      说明:每三秒重连一次,重连三次
    • RetryPolicy retryPolicy = new RetryUntilElapsed(1000,3000);
      说明:每三秒重连一次,总等待时间超过个10秒后停止重连
    • RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3)
      说明:这个策略的重试间隔会越来越长
      • 公式:baseSleepTImeMs * Math.max(1,random.nextInt(1 << (retryCount + 1)))
        • baseSleepTimeMs = 1000 例子中的值
        • maxRetries = 3 例子中的值

创建节点

package com.zookeeper.curator;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.BackgroundCallback;
import org.apache.curator.framework.api.CuratorEvent;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;

/**
 * 使用Curator创建节点
 */
public class CuratorCreate {

    String IP = "127.0.0.1:2182,127.0.0.1:2183,127.0.0.1:2184";

    CuratorFramework curatorFramework;

    @Before
    public void Before(){
        System.out.println("Before:获取连接对象");

        try {

            // session策略:随着重连的次数增加,重连的间隔也会变成重连3次
            RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
            // 连接对象
            curatorFramework = CuratorFrameworkFactory.builder()
                    // 客户端连接服务端所依赖的IP地址以及端口号,集群连接每一个IP地址端口号后面加上','就可以了
                    .connectString(IP)
                    // 客户端与服务器端连接会话session超时时间,单位:毫秒 5秒
                    .sessionTimeoutMs(5000)
                    // 客户端与服务器端连接会话超时时,尝试重新连接
                    .retryPolicy(retryPolicy)
                    // curatorFramework执行一系列命令时,会根据namespace作为父级节点。命令空间
                    .namespace("createCurator")
                    // 构建连接对象
                    .build();

            // 打开连接
            curatorFramework.start();
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    @After
    public void After(){
        System.out.println("After:释放资源");

        try {

            // 关闭连接
            curatorFramework.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 创建节点
     */
    @Test
    public void create(){
        System.out.println("create:编写对应业务逻辑代码");

        try {
            String str = "testNodeContent01";

            // 创建节点并返回路径
            String path = curatorFramework.create()
                    // 节点的类型
                    .withMode(CreateMode.PERSISTENT)
                    // 节点的acl权限列表 world:anyone:cdrwa权限
                    .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)
                    // arg1:节点路径,arg2:节点数据
                    .forPath("/testNode01",str.getBytes());

            System.out.println("文件路径:" + path);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 自定义权限列表
     */
    @Test
    public void create2(){
        System.out.println("create:编写对应业务逻辑代码");

        try {
            ArrayList<ACL> acls = new ArrayList<>();
            Id id = new Id("world", "anyone");
            acls.add(new ACL(ZooDefs.Perms.READ,id));
            // 创建节点
            curatorFramework.create()
                    // 节点的类型
                    .withMode(CreateMode.EPHEMERAL)
                    // 节点的acl权限列表
                    .withACL(acls)
                    // arg1:节点路径,arg2:节点数据
                    .forPath("/node2",new byte[0]);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 递归创建节点树
     */
    @Test
    public void create3(){
        System.out.println("create:编写对应业务逻辑代码");

        try {
            curatorFramework.create()
                    // 递归创建
                    .creatingParentsIfNeeded()
                    // 节点的类型
                    .withMode(CreateMode.EPHEMERAL)
                    // 节点的acl权限列表
                    .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)
                    // arg1:节点路径,arg2:节点数据
                    .forPath("/node2/nodex",new byte[0]);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 异步递归创建节点树
     */
    @Test
    public void create4(){
        System.out.println("create:编写对应业务逻辑代码");

        try {
            System.out.println("开始");

            curatorFramework.create()
                    // 递归创建
                    .creatingParentsIfNeeded()
                    // 节点的类型
                    .withMode(CreateMode.EPHEMERAL)
                    // 节点的acl权限列表
                    .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)
                    // 异步方法
                    .inBackground(new BackgroundCallback() {
                        /**
                         * 异步回调接口
                         * @param curatorFramework 客户端与服务连接的连接对象
                         * @param curatorEvent 事件对象
                         * @throws Exception
                         */
                        @Override
                        public void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {
                            System.out.println("异步创建成功:"+curatorEvent.getType()+",路径:"+curatorEvent.getPath());
                        }
                    })
                    // arg1:节点路径,arg2:节点数据
                    .forPath("/node2/nodex",new byte[0]);

            System.out.println("结束");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

更新节点

package com.zookeeper.curator;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.BackgroundCallback;
import org.apache.curator.framework.api.CuratorEvent;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * 使用Curator更新节点
 */
public class CuratorSet {

    String IP = "127.0.0.1:2182,127.0.0.1:2183,127.0.0.1:2184";

    CuratorFramework curatorFramework;
    @Before
    public void Before(){
        System.out.println("Before:获取连接对象");

        try {

            // session策略:随着重连的次数增加,重连的间隔也会变成重连3次
            RetryPolicy retryPolicy = new ExponentialBackoffRetry(1100,3);
            // 连接对象
            curatorFramework = CuratorFrameworkFactory.builder()
                    // 客户端连接服务端所依赖的IP地址以及端口号,集群连接每一个IP地址端口号后面加上','就可以了
                    .connectString(IP)
                    // 客户端与服务器端连接会话session超时时间,单位:毫秒 5秒
                    .sessionTimeoutMs(5000)
                    // 客户端与服务器端连接会话超时时,尝试重新连接
                    .retryPolicy(retryPolicy)
                    // curatorFramework执行一系列命令时,会根据namespace作为父级节点。命令空间
                    .namespace("createCurator")
                    // 构建连接对象
                    .build();

            // 打开连接
            curatorFramework.start();
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    @After
    public void After(){
        System.out.println("After:释放资源");

        try {

            // 关闭连接
            curatorFramework.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 更新节点-版本号不参数
     */
    @Test
    public void set(){
        System.out.println("set:编写对应业务逻辑代码");

        try {
            curatorFramework.setData()
                    // 版本,-1:版本号不参与
                    .withVersion(-1)
                    // arg1:更新的路径;arg2:要更新的内容
                    .forPath("/hadoop","hadoop1".getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 更新节点-版本号参数
     */
    @Test
    public void set2(){
        System.out.println("set:编写对应业务逻辑代码");

        try {
            curatorFramework.setData()
                    // 版本:填写了指定版本号参与更新,要与当前节点的版本号一致
                    .withVersion(1)
                    // arg1:更新的路径;arg2:要更新的内容
                    .forPath("/hadoop","hadoop1".getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 异步更新节点
     */
    @Test
    public void set3(){
        System.out.println("set:编写对应业务逻辑代码");

        try {
            curatorFramework.setData()
                    // 版本:填写了指定版本号参与更新,要与当前节点的版本号一致
                    .withVersion(-1)
                    // 异步方法
                    .inBackground(new BackgroundCallback() {
                        /**
                         * 异步回调接口
                         * @param curatorFramework 客户端与服务连接的连接对象
                         * @param curatorEvent 事件对象
                         * @throws Exception
                         */
                        @Override
                        public void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {
                            System.out.println("异步创建成功:"+curatorEvent.getType()+",路径:"+curatorEvent.getPath());
                        }
                    })
                    // arg1:更新的路径;arg2:要更新的内容
                    .forPath("/hadoop","hadoop1".getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

删除节点

package com.zookeeper.curator;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.BackgroundCallback;
import org.apache.curator.framework.api.CuratorEvent;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * 使用Curator删除节点
 */
public class CuratorDelete {

    String IP = "127.0.0.1:2182,127.0.0.1:2183,127.0.0.1:2184";

    CuratorFramework curatorFramework;
    @Before
    public void Before(){
        System.out.println("Before:获取连接对象");

        try {

            // session策略:随着重连的次数增加,重连的间隔也会变成重连3次
            RetryPolicy retryPolicy = new ExponentialBackoffRetry(1200,3);
            // 连接对象
            curatorFramework = CuratorFrameworkFactory.builder()
                    // 客户端连接服务端所依赖的IP地址以及端口号,集群连接每一个IP地址端口号后面加上','就可以了
                    .connectString(IP)
                    // 客户端与服务器端连接会话session超时时间,单位:毫秒 5秒
                    .sessionTimeoutMs(5000)
                    // 客户端与服务器端连接会话超时时,尝试重新连接
                    .retryPolicy(retryPolicy)
                    // curatorFramework执行一系列命令时,会根据namespace作为父级节点。命令空间
                    .namespace("createCurator")
                    // 构建连接对象
                    .build();

            // 打开连接
            curatorFramework.start();
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    @After
    public void After(){
        System.out.println("After:释放资源");

        try {

            // 关闭连接
            curatorFramework.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 删除节点
     */
    @Test
    public void delete(){
        System.out.println("delete:编写对应业务逻辑代码");

        try {
            // 删除节点
            curatorFramework.delete()
                    // 节点路径
                    .forPath("/testNode");

            System.out.println("结束");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 删除节点-版本号参与
     */
    @Test
    public void delete2(){
        System.out.println("delete:编写对应业务逻辑代码");

        try {
            // 删除节点
            curatorFramework.delete()
                    // 版本:填写了指定版本号参与删除,要与当前节点的版本号一致
                    .withVersion(0)
                    // 节点路径
                    .forPath("/testNode");

            System.out.println("结束");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 删除包含有子节点的节点
     */
    @Test
    public void delete3(){
        System.out.println("delete:编写对应业务逻辑代码");

        try {
            // 删除包含有子节点的节点,子节点也会一并删除
            curatorFramework.delete()
                    .deletingChildrenIfNeeded()
                    // 版本:-1:版本号不参与删除
                    .withVersion(-1)
                    // 节点路径
                    .forPath("/testNode");

            System.out.println("结束");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 异步删除节点
     */
    @Test
    public void delete4(){
        System.out.println("delete:编写对应业务逻辑代码");

        try {
            // 删除包含有子节点的节点,子节点也会一并删除
            curatorFramework.delete()
                    .deletingChildrenIfNeeded()
                    // 版本:-1:版本号不参与删除
                    .withVersion(-1)
                    // 异步方法
                    .inBackground(new BackgroundCallback() {
                        /**
                         * 异步回调接口
                         * @param curatorFramework 客户端与服务连接的连接对象
                         * @param curatorEvent 事件对象
                         * @throws Exception
                         */
                        @Override
                        public void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {
                            System.out.println("异步创建成功:"+curatorEvent.getType()+",路径:"+curatorEvent.getPath());
                        }
                    })
                    // 节点路径
                    .forPath("/testNode");

            System.out.println("结束");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

获取节点

package com.zookeeper.curator;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.BackgroundCallback;
import org.apache.curator.framework.api.CuratorEvent;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.data.Stat;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * 使用Curator获取节点
 */
public class CuratorGet {

    String IP = "127.0.0.1:2182,127.0.0.1:2183,127.0.0.1:2184";

    CuratorFramework curatorFramework;
    @Before
    public void Before(){
        System.out.println("Before:获取连接对象");

        try {

            // session策略:随着重连的次数增加,重连的间隔也会变成重连3次
            RetryPolicy retryPolicy = new ExponentialBackoffRetry(1210,3);
            // 连接对象
            curatorFramework = CuratorFrameworkFactory.builder()
                    // 客户端连接服务端所依赖的IP地址以及端口号,集群连接每一个IP地址端口号后面加上','就可以了
                    .connectString(IP)
                    // 客户端与服务器端连接会话session超时时间,单位:毫秒 5秒
                    .sessionTimeoutMs(5000)
                    // 客户端与服务器端连接会话超时时,尝试重新连接
                    .retryPolicy(retryPolicy)
                    // curatorFramework执行一系列命令时,会根据namespace作为父级节点。命令空间
                    .namespace("createCurator")
                    // 构建连接对象
                    .build();

            // 打开连接
            curatorFramework.start();
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    @After
    public void After(){
        System.out.println("After:释放资源");

        try {

            // 关闭连接
            curatorFramework.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取节点
     */
    @Test
    public void get(){
        System.out.println("get:编写对应业务逻辑代码");

        try {
            // 获取节点数据
            byte[] bytes = curatorFramework.getData()
                    // 节点路径
                    .forPath("/testNode01");

            System.out.println("节点数据:"+new String(bytes));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取节点属性
     */
    @Test
    public void get2(){
        System.out.println("get:编写对应业务逻辑代码");

        try {
            // 创建属性对象
            Stat stat = new Stat();
            // 获取节点数据
            byte[] bytes = curatorFramework.getData()
                    // 传入属性对象,便于接受属性数据
                    .storingStatIn(stat)
                    // 节点路径
                    .forPath("/testNode01");

            System.out.println("节点数据:"+new String(bytes));
            System.out.println("节点数据长度:"+stat.getDataLength()+",以及版本号:"+stat.getVersion());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 异步获取节点
     */
    @Test
    public void get3(){
        System.out.println("get:编写对应业务逻辑代码");

        try {
            // 创建属性对象
            Stat stat = new Stat();
            // 获取节点数据
            curatorFramework.getData()
                    // 异步方法
                    .inBackground(new BackgroundCallback() {
                        /**
                         * 异步回调接口
                         * @param curatorFramework 客户端与服务连接的连接对象
                         * @param curatorEvent 事件对象
                         * @throws Exception
                         */
                        @Override
                        public void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {
                            System.out.println("节点路径:"+curatorEvent.getPath());
                            System.out.println("事件类型:"+curatorEvent.getType());
                            System.out.println("节点数据:"+new String(curatorEvent.getData()));
                            System.out.println("节点数据长度:"+curatorEvent.getStat().getDataLength());
                        }
                    })
                    // 节点路径
                    .forPath("/testNode01");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

获取子节点

package com.zookeeper.curator;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.BackgroundCallback;
import org.apache.curator.framework.api.CuratorEvent;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.util.List;

public class CuratorGetChild {

    String IP = "127.0.0.1:2182,127.0.0.1:2183,127.0.0.1:2184";

    CuratorFramework curatorFramework;
    @Before
    public void Before(){
        System.out.println("Before:获取连接对象");

        try {

            // session策略:随着重连的次数增加,重连的间隔也会变成重连3次
            RetryPolicy retryPolicy = new ExponentialBackoffRetry(1211,3);
            // 连接对象
            curatorFramework = CuratorFrameworkFactory.builder()
                    // 客户端连接服务端所依赖的IP地址以及端口号,集群连接每一个IP地址端口号后面加上','就可以了
                    .connectString(IP)
                    // 客户端与服务器端连接会话session超时时间,单位:毫秒 5秒
                    .sessionTimeoutMs(5000)
                    // 客户端与服务器端连接会话超时时,尝试重新连接
                    .retryPolicy(retryPolicy)
                    // 构建连接对象
                    .build();

            // 打开连接
            curatorFramework.start();
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    @After
    public void After(){
        System.out.println("After:释放资源");

        try {

            // 关闭连接
            curatorFramework.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取子节点
     */
    @Test
    public void getChild(){
        System.out.println("getChild:编写对应业务逻辑代码");

        try {
            // 获取子节点数据
            List<String> stringList = curatorFramework.getChildren()
                    // 节点路径
                    .forPath("/createCurator");

            for (String str:
                 stringList) {
                System.out.println("子节点数据:"+str);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 异步获取子节点
     */
    @Test
    public void getChild2(){
        System.out.println("getChild:编写对应业务逻辑代码");

        try {
            // 获取子节点数据
            curatorFramework.getChildren()
                    // 异步方法
                    .inBackground(new BackgroundCallback() {
                        /**
                         * 异步回调接口
                         * @param curatorFramework 客户端与服务连接的连接对象
                         * @param curatorEvent 事件对象
                         * @throws Exception
                         */
                        @Override
                        public void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {
                            System.out.println("节点路径:"+curatorEvent.getPath());
                            System.out.println("事件类型:"+curatorEvent.getType());
                            // 获取所有子节点数据
                            List<String> stringList = curatorEvent.getChildren();
                            for (String str:
                                    stringList) {
                                System.out.println("子节点数据:"+str);
                            }
                            System.out.println("节点数据长度:"+curatorEvent.getStat().getDataLength());
                        }
                    })
                    // 节点路径
                    .forPath("/createCurator");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

检查节点

package com.zookeeper.curator;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.BackgroundCallback;
import org.apache.curator.framework.api.CuratorEvent;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.data.Stat;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * 使用Curator检查是节点否存在
 */
public class CuratorExists {

    String IP = "127.0.0.1:2182,127.0.0.1:2183,127.0.0.1:2184";

    CuratorFramework curatorFramework;
    @Before
    public void Before(){
        System.out.println("Before:获取连接对象");

        try {

            // session策略:随着重连的次数增加,重连的间隔也会变成重连3次
            RetryPolicy retryPolicy = new ExponentialBackoffRetry(1212,3);
            // 连接对象
            curatorFramework = CuratorFrameworkFactory.builder()
                    // 客户端连接服务端所依赖的IP地址以及端口号,集群连接每一个IP地址端口号后面加上','就可以了
                    .connectString(IP)
                    // 客户端与服务器端连接会话session超时时间,单位:毫秒 5秒
                    .sessionTimeoutMs(5000)
                    // 客户端与服务器端连接会话超时时,尝试重新连接
                    .retryPolicy(retryPolicy)
                    // curatorFramework执行一系列命令时,会根据namespace作为父级节点。命令空间
                    .namespace("createCurator")
                    // 构建连接对象
                    .build();

            // 打开连接
            curatorFramework.start();
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    @After
    public void After(){
        System.out.println("After:释放资源");

        try {

            // 关闭连接
            curatorFramework.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 检查节点是否存在
     */
    @Test
    public void exists(){
        System.out.println("exists:编写对应业务逻辑代码");

        try {
            // 创建节点属性对象
            Stat stat = new Stat();
            // 检查节点是否存在
            stat = curatorFramework.checkExists()
                    .forPath("/testNode01");

            System.out.println("节点属性是否有数据:"+stat);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 异步检查节点是否存在
     */
    @Test
    public void exists2(){
        System.out.println("exists:编写对应业务逻辑代码");

        try {
            // 检查节点是否存在
           curatorFramework.checkExists()
                    // 异步方法
                    .inBackground(new BackgroundCallback() {
                        /**
                         * 异步回调接口
                         * @param curatorFramework 客户端与服务连接的连接对象
                         * @param curatorEvent 事件对象
                         * @throws Exception
                         */
                        @Override
                        public void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {
                            System.out.println("节点路径:"+curatorEvent.getPath());
                            System.out.println("事件类型:"+curatorEvent.getType());
                            System.out.println("节点数据长度:"+curatorEvent.getStat().getDataLength());
                        }
                    })
                    .forPath("/testNode01");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

watcher监控节点

curator提供了两种Watcher(Cache)来监听节点的变化

  • Node Cache:只是监听某一个特定的节点,监听节点的新增和修改
  • PathChildren Cache:监控一个ZNode的子节点,当一个子节点增加、更新、删除时,Path Cache会改变它的状态,会包含最新的子节点,子节点的数据和状态
package com.zookeeper.curator;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.*;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * 使用Curator监听节点
 */
public class CuratorWatcher {
    String IP = "127.0.0.1:2182,127.0.0.1:2183,127.0.0.1:2184";

    CuratorFramework curatorFramework;
    @Before
    public void Before(){
        System.out.println("Before:获取连接对象");

        try {

            // session策略:随着重连的次数增加,重连的间隔也会变成重连3次
            RetryPolicy retryPolicy = new ExponentialBackoffRetry(1213,3);
            // 连接对象
            curatorFramework = CuratorFrameworkFactory.builder()
                    // 客户端连接服务端所依赖的IP地址以及端口号,集群连接每一个IP地址端口号后面加上','就可以了
                    .connectString(IP)
                    // 客户端与服务器端连接会话session超时时间,单位:毫秒 5秒
                    .sessionTimeoutMs(5000)
                    // 客户端与服务器端连接会话超时时,尝试重新连接
                    .retryPolicy(retryPolicy)
                    // curatorFramework执行一系列命令时,会根据namespace作为父级节点。命令空间
                    .namespace("createCurator")
                    // 构建连接对象
                    .build();

            // 打开连接
            curatorFramework.start();
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    @After
    public void After(){
        System.out.println("After:释放资源");

        try {

            // 关闭连接
            curatorFramework.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 监听节点
     */
    @Test
    public void watcher(){
        System.out.println("watcher:编写对应业务逻辑代码");

        try {
            // 监听节点对象,arg1:连接对象;arg2:要监听的路径
            final NodeCache nodeCache = new NodeCache(curatorFramework,"/testNode01");
            // 启动监听器
            nodeCache.start();

            // 注册一个监听器
            nodeCache.getListenable().addListener(new NodeCacheListener() {
                // 节点变化时,回调的方法
                @Override
                public void nodeChanged() throws Exception {
                    // 获取节点路径
                    System.out.println(nodeCache.getCurrentData().getPath());
                    // 获取节点数据
                    System.out.println(new String(nodeCache.getCurrentData().getData()));
                }
            });

            // 休眠10秒
            Thread.sleep(100000);
            System.out.println("结束");

            // 使用完成之后关闭
            nodeCache.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 监听子节点
     */
    @Test
    public void watcher2() {
        System.out.println("watcher:编写对应业务逻辑代码");

        try {
            // 监听子节点对象,arg1:连接对象;arg2:要监听的路径;arg3:true允许读取节点数据;false不允许读取节点数据
            PathChildrenCache pathChildrenCache = new PathChildrenCache(curatorFramework,"/testNode01",true);
            // 启动子节点监听器
            pathChildrenCache.start();

            // 注册一个监听器
            pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
                // 子节点变化时,回调的方法
                @Override
                public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
                    // 节点事件类型
                    System.out.println(pathChildrenCacheEvent.getType());
                    // 节点路径
                    System.out.println(pathChildrenCacheEvent.getData().getPath());
                    // 获取节点数据
                    System.out.println(new String(pathChildrenCacheEvent.getData().getData()));
                }
            });

            // 休眠10秒
            Thread.sleep(100000);
            System.out.println("结束");

            // 使用完成之后关闭
            pathChildrenCache.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

事务

package com.zookeeper.curator;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * 使用Curator事务
 */
public class CuratorTransaction {

    String IP = "127.0.0.1:2182,127.0.0.1:2183,127.0.0.1:2184";

    CuratorFramework curatorFramework;
    @Before
    public void Before(){
        System.out.println("Before:获取连接对象");

        try {

            // session策略:随着重连的次数增加,重连的间隔也会变成重连3次
            RetryPolicy retryPolicy = new ExponentialBackoffRetry(1214,3);
            // 连接对象
            curatorFramework = CuratorFrameworkFactory.builder()
                    // 客户端连接服务端所依赖的IP地址以及端口号,集群连接每一个IP地址端口号后面加上','就可以了
                    .connectString(IP)
                    // 客户端与服务器端连接会话session超时时间,单位:毫秒 5秒
                    .sessionTimeoutMs(5000)
                    // 客户端与服务器端连接会话超时时,尝试重新连接
                    .retryPolicy(retryPolicy)
                    // curatorFramework执行一系列命令时,会根据namespace作为父级节点。命令空间
                    .namespace("createCurator")
                    // 构建连接对象
                    .build();

            // 打开连接
            curatorFramework.start();
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    @After
    public void After(){
        System.out.println("After:释放资源");

        try {

            // 关闭连接
            curatorFramework.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 事务保证原子性:一个事务内的操作,要么同时成功,要么同时失败
     */
    @Test
    public void tra(){
        System.out.println("tra:编写对应业务逻辑代码");

        try {
            // 打开事务
            curatorFramework.inTransaction()
                    // 创建节点并返回路径
                    .create()
                    // 节点的类型
                    .withMode(CreateMode.PERSISTENT)
                    // 节点的acl权限列表 world:anyone:cdrwa权限
                    .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)
                    // arg1:节点路径,arg2:节点数据
                    .forPath("/testNode01","testNode01TestContent".getBytes())
                    // 与
                    .and()
                    // 修改节点数据
                    .setData()
                    // 版本:填写了指定版本号参与更新,要与当前节点的版本号一致
                    .withVersion(3)
                    // arg1:更新的路径;arg2:要更新的内容
                    .forPath("/testNode02","testNode02TestContent".getBytes())
                    // 与
                    .and()
                    // 提交事务
                    .commit();

        } catch (Exception e){
            e.printStackTrace();
        }
    }
}

分布式锁

  • InterProcessMutex:分布式可重入排它锁
  • InterProcessReadWriteLock:分布式读写锁
package com.zookeeper.curator;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessLock;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.framework.recipes.locks.InterProcessReadWriteLock;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.concurrent.TimeUnit;

/**
 * 使用Curator分布式锁
 */
public class CuratorLock {

    String IP = "127.0.0.1:2182,127.0.0.1:2183,127.0.0.1:2184";

    CuratorFramework curatorFramework;
    @Before
    public void Before(){
        System.out.println("Before:获取连接对象");

        try {

            // session策略:随着重连的次数增加,重连的间隔也会变成重连3次
            RetryPolicy retryPolicy = new ExponentialBackoffRetry(1215,3);
            // 连接对象
            curatorFramework = CuratorFrameworkFactory.builder()
                    // 客户端连接服务端所依赖的IP地址以及端口号,集群连接每一个IP地址端口号后面加上','就可以了
                    .connectString(IP)
                    // 客户端与服务器端连接会话session超时时间,单位:毫秒 5秒
                    .sessionTimeoutMs(5000)
                    // 客户端与服务器端连接会话超时时,尝试重新连接
                    .retryPolicy(retryPolicy)
                    // curatorFramework执行一系列命令时,会根据namespace作为父级节点。命令空间
                    .namespace("createCurator")
                    // 构建连接对象
                    .build();

            // 打开连接
            curatorFramework.start();
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    @After
    public void After(){
        System.out.println("After:释放资源");

        try {

            // 关闭连接
            curatorFramework.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 排他锁:确保不会同时同一资源进行多重操作
     *
     * 启动两个进程来测试此方法
     */
    @Test
    public void lock(){
        System.out.println("lock:编写对应业务逻辑代码");

        try {
            // 排他锁;arg1:连接对象;arg2:创建锁的路径
            InterProcessLock interProcessLock = new InterProcessMutex(curatorFramework, "/testNode01");
            // 开启两个进程测试,会发现:如果一个分布式排它锁获取了锁,那么直到锁释放为止数据都不会被侵扰
            System.out.println("等待获取锁对象");
            // 申请获取锁
            interProcessLock.acquire();

            for (int i = 0; i < 10; i++) {
                // 休眠3秒
                Thread.sleep(3000);
                // 打印'i'的值
                System.out.println(i);
            }

            // 释放锁资源
            interProcessLock.release();
            System.out.println("等待释放锁");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 读写锁也叫共享-独占锁:读写锁就是分了两种情况,一种是读时的锁,一种是写时的锁,它允许多个线程同时读共享变量,
     * 但是只允许一个线程写共享变量,当写共享变量的时候也会阻塞读的操作。这样在读的时候就不会互斥,提高读的效率。
     * 写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数相关),但不能同时既有读者又有写者。
     */
    @Test
    public void lock2(){
        System.out.println("lock:编写对应业务逻辑代码");

        try {
            // 读锁:读模式下加锁状态 (读锁),读锁能在多个进程并行的。运行读锁时,写锁不能与读锁同时进行,只能等读锁运行完了才执行写锁;
            // arg1:连接对象;arg2:创建锁的路径
            InterProcessReadWriteLock interProcessReadWriteLock =  new InterProcessReadWriteLock(curatorFramework, "/testNode01");
            // 获取读锁对象
            InterProcessLock interProcessLock = interProcessReadWriteLock.readLock();

            System.out.println("等待获取锁对象");
            // 申请获取锁
            interProcessLock.acquire();

            for (int i = 0; i < 10; i++) {
                // 休眠3秒
                Thread.sleep(3000);
                // 打印'i'的值
                System.out.println(i);
            }

            // 释放锁资源
            interProcessLock.release();
            System.out.println("等待释放锁");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 写锁
     */
    @Test
    public void lock3(){
        System.out.println("lock:编写对应业务逻辑代码");

        try {

            // 写锁:写模式下加锁状态 (写锁),运行写锁时,读锁不能与写锁同时进行,只能等写锁运行完了才执行读锁;arg1:连接对象;arg2:创建锁的路径
            InterProcessReadWriteLock interProcessReadWriteLock =  new InterProcessReadWriteLock(curatorFramework, "/testNode01");
            // 获取写锁对象
            InterProcessLock interProcessLock = interProcessReadWriteLock.writeLock();

            System.out.println("等待获取锁对象");
            // 申请获取锁
            interProcessLock.acquire();

            for (int i = 0; i < 10; i++) {
                // 休眠3秒
                Thread.sleep(3000);
                // 打印'i'的值
                System.out.println(i);
            }

            // 释放锁资源
            interProcessLock.release();
            System.out.println("等待释放锁");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

四字监控命令/配置属性

zookeeper——四字命令文档官方入口

zookeeper——配置属性官方入口

zookeeper支持某些特定的四字命令与其的交互。它们大多数是查询命令,用来获取zookeeper服务的当前状态及相关信息。用户再客户端可以通过telnetnczookeeper提交相应的命令。zookeeper常用四字命令见下表所示:

命令描述
conf输出相关服务配置的详细信息。比如端口号、zk数据以及日志配置路径、最大连接数,session超时、serverId
cons列出所有连接到这台服务器的客户端连接/会话的详细信息。包括"接收/发送"的包数量、sessionId、操作延迟、最后的操作执行等信息
crst重置当前这台服务器所有连接/会话的统计信息
dump列出未经处理的会话和临时节点,这仅适用于领导者
envi处理关于服务器的环境详细信息
ruok测试服务是否处于正确运行状态。如果正常返回"imok",否则返回空
stat输出服务器的详细信息:接收/发送包数量、连接数、模式(leader/follower)、节点总数、延迟。所有客户端的列表
srst重置server状态
wchs列出服务器watchers的简洁信息:连接总数、watching节点总数和watches总数
wchc通过session分组,列出watch的所有节点,它的输出是一个与watch相关的会话的节点信息,根据watch数量的不同,此操作可能会很昂贵(即影响服务器性能),请小心使用
mntr列出集群的健康状态。包括"接收/发送"的包数量、操作延迟、当前服务模式(leader/follower)、节点总数、watch总数、临时节点总数

telnet

  • Linux系统安装nc命令 yum install -y telnet
  • telnet 192.168.1.136 2181(进入终端)
    • 输入mntr命令查看(现在可以看到信息)

nc

  • Linux系统安装nc命令 yum install -y nc
    • 通过mntr命令查看系统信息 echo mntr | nc 192.168.133.133:2181

conf命令

conf:输出相关服务配置的详细信息
shell终端输入:echo conf | nc localhost 2181

属性含义
clientPort客户端端口号
dataDir数据快照文件目录,默认情况下10w次事务操作生成一次快照
dataLogDir事务日志文件目录,生产环节中放再独立的磁盘上
tickTime服务器之间或客户端与服务器之间维持心跳的时间间隔(以毫秒为单位)
maxClientCnxns最大连接数
minSessionTimeout最小session超时minSessionTimeout=tickTime*2 ,即使客户端连接设置了会话超时,也不能打破这个限制
maxSessionTimeout最大session超时maxSessionTimeout=tickTime*20,即使客户端连接设置了会话超时,也不能打破这个限制
serverId服务器编号
initLimit集群中follower服务器(F)leader服务器(L)之间初始连接时能容忍的最多心跳数,实际上以tickTime为单位,换算为毫秒数
syncLimit集群中follower服务器(F)leader服务器(L)之间请求和应答之间能容忍的最大心跳数,实际上以tickTime为单位,换算为毫秒数
electionAlg0:基于UDPLeaderElection1:基于UDPFastLeaderElection2:基于UDP和认证的FastLeaderElection3:基于TCPFastLeaderElection3.4.10版本中,默认值为3,另外三种算法以及被弃用,并且有计划在之后的版本中将它们彻底删除且不再支持
electionPort选举端口
quorumPort数据通信端口
peerType是否为观察者 1为观察者 0为随从者

cons命令

cons:列出所有连接到这台服务器的客户端连接/会话的详细信息
shell终端输入:echo cons | nc localhost 2181

属性含义
ipIP地址
port客户端发送请求信息的端口号
queued等待被处理的请求数,请求缓存在队列中
received收到的包数
sent发送的包数
sid会话id
lop最后的操作 GETD-读取数据 DELE-删除数据 CREA-创建数据
est连接时间戳
to超时时间
lcxid当前会话的操作id
lzxid最大事务id
lresp最后响应时间戳
llat最后/最新 延迟
minlat最小延时
maxlat最大延时
avglat平均延时

crst命令

crst:重置当前这台服务器所有连接/会话的统计信息
shell终端输入:echo crst | nc localhost 2181

dump命令

dump:列出未经处理的会话和临时节点信息,适用于leader领导者
shell终端输入:echo dump | nc localhost 2181

envi命令

envi:输出关于服务器的环境配置详细信息
shell终端输入:echo envi | nc localhost 2181

属性含义
zookeeper.version版本
host.namehost信息
java.versionjava版本
java.vendor供应商
java.home运行环境所在目录
java.class.pathclasspath
java.library.path第三方库指定非Java类包的为止(如:dll,so)
java.io.tmpdir默认的临时文件路径
java.compilerJIT编辑器的名称
os.nameLinux
os.archamd64
os.version3.10.0-1062.el7.x86_64
user.namezookeeper
user.home/opt/zookeeper
user.dir/opt/zookeeper/zookeeper2181/bin

ruok命令

ruok:测试服务是否处于正确运行状态,如果目标正确运行会返回imok(are you ok | I’m ok)
shell终端输入:echo ruok | nc localhost 2181

stat命令

stat:输出服务器的详细信息与srvr相似(srvr这里不举例了,官网有一点描述),但是多了每个连接的会话信息
shell终端输入:echo stat | nc localhost 2181

属性含义
zookeeper version版本
Latency min/avg/max延时 最小延时/平均延时/最大延时
Received收包
Sent发包
Connections当前服务器连接数
Outstanding服务器堆积的未处理请求数
Zxid最大事务id
Mode服务器角色
Node count当前服务器所存储的节点数量

srst命令

srst:重置server服务器状态
shell终端输入:echo srst | nc localhost 2181

wchs命令

wchs:列出服务器watches监听器的简洁信息
shell终端输入:echo wchs | nc localhost 2181

属性含义
connectsions连接数
watch-pathswatch节点数
watcherswatcher数量

wchc命令

wchc:通过session分组,列出watch的所有节点,它的输出是一个与watch相关的会话的节点列表
shell终端输入:echo wchc | nc localhost 2181

问题
wchc is not executed because it is not in the whitelist

解决办法

#修改启动指令zkServer.sh
#注意找到这个信息
else
echo “JMX disabled by user request” >&2
ZOOMAIN=“org.apache.zookeeper.server.quorum.QuorumPeerMain”
fi
 
#在这段信息下面添加如下信息
ZOOMAIN="-Dzookeeper.4lw.commands.whitelist=* ${ZOOMAIN}"

每一个客户端的连接的watcher信息都会被收集起来,并且监控的路径都会被展示出来(代价高,消耗性能)

[root@localhost bin]# echo wchc | nc 192.168.133.133 2180
0x171be6c6faf0000
        /node2
        /node1
0x171be6c6faf0001
        /node3

wchp命令

wchp:通过路径分组,列出所有的watchsession id 信息
shell终端输入:echo wchp | nc localhost 2181

配置同wchc

mntr命令

mntr:列出服务器的健康状态
shell终端输入:echo mntr | nc localhost 2181

属性含义
zk_version版本
zk_avg_latency平均延时
zk_max_latency最大延时
zk_min_latency最小延时
zk_packets_received收包数
zk_packets_sent发包数
zk_num_alive_connections连接数
zk_outstanding_requests堆积请求数
zk_server_stateleader/follower状态
zk_znode_countznode数量
zk_watch_countwatch数量
zk_ephemerals_countl临时节点(znode)
zk_approximate_data_size数据大小
zk_open_file_descriptor_count打开的文件描述符数量
zk_max_file_descriptor_count最大文件描述符数量

ZooInspector图形化工具

网上搜一下就好

  • 解压后进入目录ZooInspector\build,运行zookeeper-dev-ZooInspector.jar
  • java -jar 运行,之后会弹出一个客户端

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

taokeeper检控工具

网上搜一下就好

基于zookeeper的监控管理工具taokeeper,由淘宝团队开发的zk管理中间件,安装强要求服务先配置ncsshd

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值