ZooKeeper
是一个集中的服务,用于维护配置信息、命名、提供分布式同步和提供组服务。所有这些类型的服务都以某种形式被分布式应用程序使用。每次它们被实现时,都会有大量的工作来修复不可避免的错误和竞争条件。由于实现这些服务的困难,应用程序最初通常会略过这些服务,这使得它们在出现更改时变得脆弱,并且难以管理。即使正确地执行了这些服务,在部署应用程序时,这些服务的不同实现也会导致管理复杂性。
zookeeper
由雅虎研究院开发,是Google Chubby
的开源实现,后来托管到 Apache
,于2010年11月
正式成为(分布式应用提供协调服务)apache
的顶级项目
zookeeper
= 文件系统 + 监听通知机制。
在分布式应用中把Zookeeper
当作“配置中心”,而且当“配置信息”发生变化后,Zookeeper
还能提供相应数据的“发布/订阅”机制。因为Zookeeper提供了一种监听机制叫watch
。
大数据生态系统里由很多组件的命名都是某些动物或者昆虫,比如hadoop
就是大象,hive
就是蜂巢,zookeeper
即动物园管理员,顾名思义就是管理大数据生态系统各组件的管理员,如下所示:
zookeepepr
是一个经典的分布式数据一致性解决方案,致力于为分布式应用提供一个高性能、高可用,且具有严格顺序访问控制能力的分布式协调存储服务。
应用场景有:
维护配置信息
java
编程经常会遇到配置项,比如数据库的url
、 schema
、user
和 password
等。通常这些配置项我们会放置在配置文件中,再将配置文件放置在服务器上当需要更改配置项时,需要去服务器上修改对应的配置文件。
但是随着分布式系统的兴起,由于许多服务都需要使用到该配置文件,因此有必须保证该配置服务的高可用性(highavailability
)和各台服务器上配置数据的一致性。
通常会将配置文件部署在一个集群上,然而一个集群动辄上千台服务器,此时如果再一台台服务器逐个修改配置文件那将是非常繁琐且危险的的操作,因此就需要一种服务,能够高效快速且可靠地完成配置项的更改等操作,并能够保证各配置项在每台服务器上的数据一致性。
zookeeper
就可以提供这样一种服务,其使用Zab
这种一致性协议来保证一致性。现在有很多开源项目使用zookeeper
来维护配置,如在 hbase
中,客户端就是连接一个 zookeeper
,获得必要的 hbase
集群的配置信息,然后才可以进一步操作。还有在开源的消息队列 kafka
中,也便用zookeeper
来维护 brokers
的信息。在 alibaba
开源的soa
框架dubbo
中也广泛的使用zookeeper
管理一些配置来实现服务治理。
分布式锁服务
一个集群是一个分布式系统,由多台服务器组成。为了提高并发度和可靠性,多台服务器上运行着同一种服务。当多个服务在运行时就需要协调各服务的进度,有时候需要保证当某个服务在进行某个操作时,其他的服务都不能进行该操作,即对该操作进行加锁,如果当前机器挂掉后,释放锁并 fail over
到其他的机器继续执行该服务
集群管理
一个集群有时会因为各种软硬件故障或者网络故障,出现棊些服务器挂掉而被移除集群,而某些服务器加入到集群中的情况,zookeeper
会将这些服务器加入/移出的情况通知给集群中的其他正常工作的服务器,以及时调整存储和计算等任务的分配和执行等。此外zookeeper
还会对故障的服务器做出诊断并尝试修复。
生产分布式唯一ID
在过去的单库单表型系统中,通常可以使用数据库字段自带的auto_ increment
属性来自动为每条记录生成一个唯一的ID
。但是分库分表后,就无法在依靠数据库的auto_ Increment
属性来唯一标识一条记录了。此时我们就可以用zookeeper
在分布式环境下生成全局唯一ID
。
做法如下:每次要生成一个新id
时,创建一个持久顺序节点,创建操作返回的节点序号,即为新id
,然后把比自己节点小的删除即可
zooKeeper
致力于为分布式应用提供一个高性能、高可用,且具有严格顺序访问控制能力的分布式协调服务
zookeeper
将全量数据存储在内存中,并直接服务于客户端的所有非事务请求,尤其用于以读为主的应用场景zookeeper
一般以集群的方式对外提供服务,一般3~5
台机器就可以组成一个可用的 Zookeeper
集群了,每台机器都会在内存中维护当前的服务器状态,井且每台机器之间都相互保持着通信。只要集群中超过一半的机器都能够正常工作,那么整个集群就能够正常对外服务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-1
是itcast
的父节点,itcast
是ns-1
的子节点,itcast
是mysql
的父节点…以此类推
znode
,间距文件和目录两种特点,即像文件一样维护着数据、元信息、ACL、时间戳等数据结构,又像目录一样可以作为路径标识的一部分
那么如何描述一个znode
呢?一个znode
大体上分为3
个部分:
znode data
(节点path
,节点data
)的关系就像是Java map
中的 key value
关系children
stat
:用来描述当前节点的创建、修改记录,包括cZxid
、ctime
等在zookeeper shell
中使用 get
命令查看指定路径节点的data
、stat
信息
节点的各个属性如下。其中重要的概念是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
数据节点最后一次更新时的事务IDmtime
数据节点最后一次更新时的时间pZxid
数据节点最后一次修改此znode
子节点更改的zxid
cversion
子节点的更改次数dataVersion
节点数据的更改次数aclVersion
节点的ACL更改次数——类似linux
的权限列表,维护的是当前节点的权限列表被修改的次数ephemeralOwner
如果节点是临时节点,则表示创建该节点的会话的SessionID
;如果是持久节点,该属性值为0dataLength
数据内容的长度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/
centos
中使用 root
用户创建 zookeeper
用户,用户名:zookeeper
密码:zookeeper
useradd zookeeper
passwd zookeeper
su zookeeper
zookeeper
底层依赖于jdk,zookeeper
用户登录后,根目录下先进行jdk 的安装,jdk使用 jdk-8u131-linux-x64.tar.gz
tar -zxf tar.gz
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
检测jdk安装
java -version
// 如果反馈了Java信息,则成功
zookeeper
上传解压
tar -zxf tar.gz
为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
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
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"
查看节点
更新节点的命令是get path
get /a -s
状态属性 | 说明 |
---|---|
cZxid | 数据节点创建时的事务ID |
ctime | 数据节点创建时的时间 |
mZxid | 数据节点最后一次更新时的事务ID |
mtime | 数据节点最后一次更新时的时间 |
pZxid | 数据节点的子节点列表最后一次被修改(是子节点列表变更,而不是子节点内容变更)时的事务ID |
cversion | 子节点的版本号 |
dataVersion | 数据节点的版本号 |
aclVersion | 数据节点的ACL版本号 |
ephemeralOwner | 如果节点是临时节点,则表示创建该节点的会话的 SessionID;如 果节点是持久节点,则该属性值为0x0 |
dataLength | 数据内容的长度 |
numChildren | 数据节点当前的子节点个数数量 |
zxid
的含义:
znode
节点的状态信息中包含czxid
和mzxid
, 那么什么是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 path
和ls -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可以作用为注册中心,将应用中心所依赖的一系列相关的配置信息放在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
类似文件系统,client
可以创建节点、更新节点、删除节点,那么如何做到节点的权限控制呢?
zookeeper
的 access 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 | 根据指定用户,使用"用户名:密码"方式认证 ,密码是经过加密(密文) |
授权对象
授权的权限
授予什么权限
create、delete、read、writer、admin
也就是 增、删、查、改、管理权限,这5种权限简写为 c d r w a,注意:这五种权限中,有的权限并不是对节点自身操作的例如:delete是指对子节点的删除权限
可以试图删除父结点,但是子节点必须删除干净,所以delete
的权限也是很有用的
权限 | ACL简写 | 描述 |
---|---|---|
create | c | 可以创建子节点 |
delete | d | 可以删除子节点(仅下一级节点) |
read | r | 可以读取节点数据以及显示子节点列表 |
write | w | 可以设置节点数据 |
admin | a | 可以设置节点访问控制权限列表 |
授权的相关命令
命令 | 使用方式 | 描述 |
---|---|---|
getAcl | getAcl | 读取ACL权限 |
setAcl | setAcl | 设置ACL权限 |
addauth | addauth | 添加认证用户 |
world权限模式
命令
setAcl
world:anyone: // 对指定的节点授权,指定路径,权限模式: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
设置auth形式授权模式,命令:
setAcl
[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
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]
zonde
是 zookeeper
集合的核心组件,zookeeper API
提供了一小组使用 zookeeper
集群来操作znode
的所有细节。
客户端应该遵循以下步骤,与zookeeper
服务器进行清晰和干净的交互。
zookeeper
服务器。zookeeper
服务器为客户端分配会话ID
zookeeper
服务器将过期会话ID
,客户端需要重新连接Id
处于活动状态,就可以获取/设置znode
zookeeper
服务器连接,如果客户端长时间不活动,则zookeeper
服务器将自动断开客户端案例:
pom.xml
依赖
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>compilescope>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-coreartifactId>
<version>2.8.2version>
dependency>
<dependency>
<groupId>org.apache.zookeepergroupId>
<artifactId>zookeeperartifactId>
<version>3.6.2version>
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)
参数 | 解释 |
---|---|
path |
znode 路径 |
data |
要存储在指定znode路径中的数据 |
acl |
要创建的节点的访问控制列表。zookeeper API 提供了一个静态接口 ZooDefs.Ids 来获取一些基本的acl 列表。例如,ZooDefs.Ids.OPEN_ACL_UNSAFE 返回打开znode 的acl 列表 |
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都由写操作来触发:create
、delete
和setData
。监视事件可以理解为一次性的触发器。
watcher
实现由三个部分组成
zookeeper
服务端zookeeper
客户端WatchManager管理对象
客户端首先将 Watcher
注册到服务端,同时将 Watcher
对象保存到客户端的watch
管理器中。当Zookeeper
服务端监听的数据状态发生变化时,服务端会主动通知客户端,接着客户端的 WatchManager
管理器会**触发相关 Watcher
**来回调相应处理逻辑,从而完成整体的数据 发布/订阅
流程。
特性 | 说明 |
---|---|
一次性 | watcher 是一次性的,一旦被触发就会移除,再次使用时需要重新注册 |
客户端顺序回调 | watcher 回调是顺序串行执行的,只有回调后客户端才能看到最新的数据状态。一个watcher 回调逻辑不应该太多,以免影响别的watcher 执行 |
轻量级 | WatchEvent 是最小的通信单位,结构上只包含通知状态、事件类型和节点路径,并不会告诉数据节点变化前后的具体内容 |
时效性 | watcher 只有在当前session 彻底失效时才会无效,若在session 有效期内快速重连成功,则watcher 依然存在,仍可接收到通知; |
watcher接口设计
Watcher
是一个接口,任何实现了Watcher
接口的类就算一个新的Watcher
。Watcher
内部包含了两个枚举类:KeeperState
、EventType
KeeperState
是客户端与服务端连接状态发生变化时对应的通知类型。路径为org.apache.zookeeper.Watcher.EventKeeperState
,是一个枚举类,其枚举属性如下:
枚举属性 | 说明 |
---|---|
SyncConnected |
客户端与服务器正常连接时 |
Disconnected |
客户端与服务器断开连接时 |
Expired |
会话session 失效时 |
AuthFailed |
身份认证失败时 |
EventType
是数据节点znode
发生变化时对应的通知类型。EventType
变化时KeeperState
永远处于SyncConnected
通知状态下;当keeperState
发生变化时,EventType
永远为None
。其路径为org.apache.zookeeper.Watcher.Event.EventType
,是一个枚举类,枚举属性如下:
枚举属性 | 说明 |
---|---|
None |
无 |
NodeCreated |
Watcher 监听的数据节点被创建时 |
NodeDeleted |
Watcher 监听的数据节点被删除时 |
NodeDataChanged |
Watcher 监听的数据节点内容发生更改时(无论数据是否真的变化) |
NodeChildrenChanged |
Watcher 监听的数据节点的子节点列表发生变更时 |
get
等方法重新获取上面讲到zookeeper
客户端连接的状态和zookeeper
对znode
节点监听的事件类型,下面我们来讲解如何建立zookeeper
的watcher
监听。在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) |
可监控 | 可监控 | 可监控 |
客户端与服务器端的连接状态
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();
}
}
}
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
可以轻松完成,当数据库发生变化时自动完成缓存同步。
使用事件监听机制可以做出一个简单的配置中心
设计思路:
zookeeper
服务器zookeeper
中的配置信息,注册watcher
监听器,存入本地变量zookeeper
中的配置信息发生变化时,通过watcher
的回调方法捕获数据变化事件案例:
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;
}
}
在过去的单库单表型系统中,通常第可以使用数据库字段自带的auto_ increment
属性来自动为每条记录生成个唯一的ID
。但是分库分表后,就无法在依靠数据库的auto_ increment
属性来唯一标识一条记录了。此时我们就可以用zookeeper
在分布式环境下生成全局唯一ID
。
设计思路:
zookeeper
服务器案例:
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
中如果实现排他锁
设计思路
/Locks
下创建临时有序节点/Locks/Lock_
,创建成功后/Locks
下面会有每个客户端对应的节点,如/Locks/Lock_000000001
Lock_000000002
,那么则监听Lock_000000001
(Lock_000000001)
对应的客户端执行完成,释放了锁,将会触发监听客户端(Lock_000000002)
的逻辑2
步逻辑,判断自己是否获得了锁案例:
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
说明:对于复制模式,至少需要三个服务器,并且强烈建议您使用奇数个服务器。如果只有两台服务器,那么您将处于一种情况,如果其中一台服务器发生故障,则没有足够的计算机构成多数仲裁(zk
采用的是过半数仲裁。因此,搭建的集群要容忍n个节点的故障,就必须有2n+1
台计算机,这是因为宕掉n台后,集群还残余n+1
台计算机,n+1
台计算机中必定有一个最完整最接近leader
的follower
,假如宕掉的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
,集群正常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
initLimit
是zookeeper
用于限制选举中zookeeper
服务连接到leader
的时间,syncLimit
**限制服务器与leader
的过期时间initLimit
的超时为5个滴答声,即2000
毫秒/滴答声,即10
秒server.X
的条目列出了组成ZooKeeper
服务的服务器。服务器启动时,它通过在数据目录中查找文件myid
*来知道它是哪台服务器。该文件包含ASCII
的服务器号。“ 2888”
和“ 3888”
。对等方使用前一个端口连接到其他对等方。这种连接是必需的,以便对等方可以进行通信,例如,以商定更新顺序。更具体地说,ZooKeeper
服务器使用此端口将follower
连接到leader
。当出现新的leader
者时,follower
使用此端口打开与leader
的TCP
连接。因为默认的leader
选举也使用TCP
,所以我们当前需要另一个端口来进行leader
选举。这是第二个端口。正文搭建:单机环境下,jdk
、zookeeper
安装完毕,基于一台虚拟机,进行zookeeper
伪集群搭建,zookeeper
集群中包含3个节点,节点对外提供服务端口号,分别为2181
、2182
、2183
/usr/local/Cellar/zookeeper/
基于zookeeper-3.6.2_1
复制三份zookeeper
安装好的服务器文件,目录名称分别为zookeeper2181
、zookeeper2182
、zookeeper2183
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}
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的配置文件
dataDir
指定的目录下,创建myid
文件,然后在该文件添加上一步server
配置的对应A
数字 # zookeeper2181对应的数字为1
# /opt/zookeeper2181/data目录(即dataDir的目录下)下执行命令
echo "1" > myid
zookeeper2182、2183
参照2/3进行相应配置
分别启动三台服务器,检验集群状态
检查: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伪集群搭建
/usr/local/etc/zookeeper
配置目录下,分别创建3个配置文件
:zoo2182.cfg
、zoo2183.cfg
、zoo2184.cfg
/usr/local/etc/zookeeper
配置目录下,分别创建3个文件夹存放data数据文件
:zk2182
、zk2183
、zk2183
/usr/local/etc/zookeeper/zk2182
、/usr/local/etc/zookeeper/zk2183
、/usr/local/etc/zookeeper/zk2184
配置目录下,创建data
文件夹用于存放myid
文件/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.cfg
、zkServer start /usr/local/etc/zookeeper/zoo2183.cfg
、zkServer 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:2182
、zkCli -server 127.0.0.1:2183
、zkCli -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
协议的全称是 Zookeeper Atomic Broadcast
(zookeeper
原子广播)。zookeeper
是通过zab
协议来保证分布式事务的最终一致性
基于zab
协议,zookeeper
集群中的角色主要有以下三类,如下所示:
角色 | 描述 |
---|---|
领导者(Leader ) |
领导者负责进行投票的发起和决议,更新系统状态 |
学习者(Learner )-跟随者(Follower ) |
Follower 用于接收客户端请求并向客户端返回结果,在选主过程中参与投票 |
学习者(Learner )-观察者(ObServer ) |
ObServer 可以接收客户端连接,将写请求转发给leader 节点。但ObServer 不参加投票过程,只同步leader 的状态。ObServer 的目的是为了扩展系统,提高读取速度 |
客户端(Client ) |
请求发起方 |
·zab
广播模式工作原理,通过类似两端式提交协议的方式解决数据一致性:
leader
从客户端收到一个写请求leader
生成一个新的事务并为这个事务生成一个唯一的ZXID
leader
将事务提议(propose
)发送给所有的follows
节点follower
节点将收到的事务请求加入到本地历史队列(history queue
)中,并发送ack
给leader
,表示确认提议leader
收到大多数follower
(半数以上节点)的ack(acknowledgement)
确认消息,leader
会本地提交,并发送commit
请求follower
收到commit
请求时,从历史队列中将事务请求commit
因为是半数以上的结点就可以通过事务请求,所以延迟不高。
如果在follows随从者
写入信息,它将先request转发
给leader领导者
,然后再执行以上6项流程
。
1、服务器状态一共有四种:
looking
:寻找leader
状态。当服务器处于该状态时,它会认为当前集群中没有leader
,因此需要进入leader
选举状态leading
:领导者状态。表明当前服务器角色是leading
following
:跟随者状态。表明当前服务器角色是follower
observing
:观察者状态。表明当前服务器角色是observer
分为两种选举,服务器启动时的选举和服务器运行时期的选举
2、服务器启动时期的leader选举
在集群初始化节点,当有一台服务器server1
启动时,其单独无法进行和完成leader
选举,当第二台服务器server2
启动时,此时两台及其可以相互通信,每台及其都试图找到leader
,于是进入leader
选举过程。选举过程如下:
每个server
发出一个投票。由于是初始状态,server1
和server2
都会将自己作为leader
服务器来进行投票,每次投票都会包含所推举的myid服务器编号
和zxid事务ID
,使用(myid,zxid
)投票格式,此时server1
的投票为(1,0),server2
的投票为(2,0),然后各自将这个投票发给集群中的其它机器
集群中的每台服务器都接收来自集群中各个服务器的投票
处理投票。针对每一个投票,服务器都需要将别人的投票和自己的投票进行pk,规则如下
优先检查zxid
。zxid
比较大的服务器优先作为leader
(zxid
较大者保存的数据更多)
如果zxid
相同。那么就比较myid
。myid
较大的服务器作为leader
服务器
对于Server1
而言,它的投票是(1,0),接收Server2
的投票为(2,0),首先会比较两者的zxid
,均为0,再比较myid
,此时server2
的myid
最大,于是更新自己的投票为(2,0),然后重新投票,对于server2而言,无需更新自己的投票,只是再次向集群中所有机器发出上一次投票信息即可
统计投票。每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息,对于server1、server2
而言,都统计出集群中已经有两台机器接受了(2,0)的投票信息,此时便认为已经选举出了leader
改变服务器状态。一旦确定了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
选举过程基本一致
假设正在运行的有server1
、server2
、server3
三台服务器,当前leader
是server2
,若某一时刻leader
挂了,此时便开始Leader
选举。选举过程如下
leader
挂后,余下的服务器都会将自己的服务器状态变更为looking
,然后开始进入leader
选举过程server
发出一个投票。在运行期间,每个服务器上的zxid
可能不同,此时假定server1
的zxid
为122
,server3
的zxid
为122
,在第一轮投票中,server1和server3都会投自己,产生投票(1,122),(3,122),然后各自将投票发送给集群中所有机器server3
将会成为leader
zookeeper
官方入口——Observers Guide
尽管ZooKeeper
通过使用客户端直接连接到该集合的投票成员表现良好,但是此体系结构使其很难扩展到大量客户端。问题在于,随着我们添加更多的投票成员,写入性能会下降。这是由于以下事实:写操作需要(通常)集合中至少一半节点的同意,因此,随着添加更多的投票者,投票的成本可能会显着增加。
我们引入了一种称为Observer的新型ZooKeeper
节点,该节点有助于解决此问题并进一步提高ZooKeeper
的可伸缩性。观察员是合法的非投票成员,他们仅听取投票结果,而听不到投票结果。除了这种简单的区别之外,观察者的功能与跟随者的功能完全相同-客户端可以连接到观察者,并向其发送读写请求。观察者像追随者一样将这些请求转发给领导者,但是他们只是等待听取投票结果。因此,我们可以在不影响投票效果的情况下尽可能增加观察员的数量。
观察者还有其他优点。因为他们不投票,所以它们不是ZooKeeper
选举中的关键部分。因此,它们可以在不损害ZooKeeper
服务可用性的情况下发生故障或与群集断开连接。给用户带来的好处是,观察者可以通过比跟随者更不可靠的网络链接进行连接。实际上,观察者可用于与另一个数据中心的ZooKeeper
服务器进行对话。观察者的客户端将看到快速读取,因为所有读取均在本地提供,并且由于缺少表决协议而需要的消息数量较小,因此写入会导致网络流量最小
ovserver
角色特点:
leader
选举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(String connectionString, int sessionTimeout, Watcher watcher)
connectionString
:zookeeper
集合主机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());
}
关于第三方客户端的小介绍
zkClient
有对dubbo
的一些操作支持,但是zkClient
几乎没有文档,下面是curator
curator简介
curator
是Netflix
公司开源的一个 zookeeper
客户端,后捐献给 apache
,curator
框架在zookeeper
原生API
接口上进行了包装,解决了很多zooKeeper
客户端非常底层的细节开发。提供zooKeeper
各种应用场景(比如:分布式锁服务、集群领导选举、共享计数器、缓存机制、分布式队列等的抽象封装,实现了Fluent
风格的APl接口,是最好用,最流行的zookeeper
的客户端
原生zookeeperAPI
的不足
curator
特点
session
会话超时重连watcher
反复注册api
Fluent
风格API
案例:
pom.xml
依赖
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.11version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.apache.zookeepergroupId>
<artifactId>zookeeperartifactId>
<version>3.6.2version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-frameworkartifactId>
<version>4.2.0version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-recipesartifactId>
<version>4.2.0version>
dependency>
dependencies>
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();
}
}
}
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
服务的当前状态及相关信息。用户再客户端可以通过telnet
或nc
向zookeeper
提交相应的命令。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
yum install -y telnet
telnet 192.168.1.136 2181
(进入终端)
mntr
命令查看(现在可以看到信息)nc
yum install -y nc
mntr
命令查看系统信息 echo mntr | nc 192.168.133.133:2181
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 为单位,换算为毫秒数 |
electionAlg |
0:基于UDP 的LeaderElection 1:基于UDP 的FastLeaderElection 2:基于UDP和认证的FastLeaderElection 3:基于TCP 的FastLeaderElection 在3.4.10 版本中,默认值为3,另外三种算法以及被弃用,并且有计划在之后的版本中将它们彻底删除且不再支持 |
electionPort |
选举端口 |
quorumPort |
数据通信端口 |
peerType |
是否为观察者 1为观察者 0为随从者 |
cons:列出所有连接到这台服务器的客户端连接/会话的详细信息
shell终端输入:echo cons | nc localhost 2181
属性 | 含义 |
---|---|
ip |
IP地址 |
port |
客户端发送请求信息的端口号 |
queued |
等待被处理的请求数,请求缓存在队列中 |
received |
收到的包数 |
sent |
发送的包数 |
sid |
会话id |
lop |
最后的操作 GETD-读取数据 DELE-删除数据 CREA-创建数据 |
est |
连接时间戳 |
to |
超时时间 |
lcxid |
当前会话的操作id |
lzxid |
最大事务id |
lresp |
最后响应时间戳 |
llat |
最后/最新 延迟 |
minlat |
最小延时 |
maxlat |
最大延时 |
avglat |
平均延时 |
crst:重置当前这台服务器所有连接/会话的统计信息
shell终端输入:echo crst | nc localhost 2181
dump:列出未经处理的会话和临时节点信息,适用于leader领导者
shell终端输入:echo dump | nc localhost 2181
envi:输出关于服务器的环境配置详细信息
shell终端输入:echo envi | nc localhost 2181
属性 | 含义 |
---|---|
zookeeper.version |
版本 |
host.name |
host 信息 |
java.version |
java 版本 |
java.vendor |
供应商 |
java.home |
运行环境所在目录 |
java.class.path |
classpath |
java.library.path |
第三方库指定非Java类包的为止(如:dll,so) |
java.io.tmpdir |
默认的临时文件路径 |
java.compiler |
JIT 编辑器的名称 |
os.name |
Linux |
os.arch |
amd64 |
os.version |
3.10.0-1062.el7.x86_64 |
user.name |
zookeeper |
user.home |
/opt/zookeeper |
user.dir |
/opt/zookeeper/zookeeper2181/bin |
ruok:测试服务是否处于正确运行状态,如果目标正确运行会返回imok(are you ok | I’m ok)
shell终端输入:echo ruok | nc localhost 2181
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:重置server服务器
状态
shell终端输入:echo srst | nc localhost 2181
wchs:列出服务器watches监听器
的简洁信息
shell终端输入:echo wchs | nc localhost 2181
属性 | 含义 |
---|---|
connectsions |
连接数 |
watch-paths |
watch 节点数 |
watchers |
watcher 数量 |
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:通过路径分组,列出所有的watch
的session id
信息
shell终端输入:echo wchp | nc localhost 2181
配置同wchc
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_state |
leader/follower 状态 |
zk_znode_count |
znode 数量 |
zk_watch_count |
watch 数量 |
zk_ephemerals_count |
l临时节点(znode) |
zk_approximate_data_size |
数据大小 |
zk_open_file_descriptor_count |
打开的文件描述符数量 |
zk_max_file_descriptor_count |
最大文件描述符数量 |
网上搜一下就好
ZooInspector\build
,运行zookeeper-dev-ZooInspector.jar
java -jar
运行,之后会弹出一个客户端taokeeper检控工具
网上搜一下就好
基于zookeeper
的监控管理工具taokeeper
,由淘宝团队开发的zk
管理中间件,安装强要求服务先配置nc
和sshd