Zookeeper一致性要求
什么时是zk
的一致性呢?其满足的以下几点要求:
- 顺序一致性:从同一客户端发起的n多个事务请求,最终将会严格按照发起顺序被应用到
Zookeeper
中。 - 原子性:所有事务请求的结果在集群中所有机器上的应用情况是一致的,也就是说要么整个集群所有主机都成功应用了某一事务,要么都没有成功应用,不会出现集群中部分主机应用某事务成功,而另外一部分主机失败的情况。
- 单一视图:无论客户端连接的是哪一个
Zookeeper
服务器,其看到的服务端数据模型都是一致的。 - 可靠性:一旦服务端成功地应用了一个事务,并完成对客户端地响应,那么该事务所引起地服务端状态地变更将会被一直保留下来,除非有另一个事务又对其进行了变更。
- 实时性:通常人们看到实时性地第一反应是,一旦一个事务被成功应用,那么客户端能够立即从服务端上读取到这个事务变更后地最新数据状态,这里需要注意的是,
Zookeeper
仅仅保证在一段较短时间内,客户端最终一定能够从服务端上读取到最新地数据状态。
Zookeeper安装
安装单机Zookeeper
对于系统安全性、实时性要求不是很高的系统,为了节约成本,使用单机Zookeeper
作为协调服务器也是可以的。
-
1.准备一台虚拟机。
-
2.下载
Zookeeper
安装包,zookeeper.apache.org官网下载,将下载的Zookeeper
上传安装包到主机的/usr/tools
目录下。 -
3.解压安装包,如下所示
- 4.创建软连接
- 5.复制配置文件,复制
Zookeeper
安装目录下的conf
目录中的zoo_sample.cfg
文件,并命名为zoo.cfg
。
- 6.修改配置文件
- 7.先创建数据存放目录如下:
- 8.注册
bin
目录,由于我们要使用的命令都在zk
安装目录下的bin
目录中,所以在/etc/profile
文件中将该bin
目录注册到系统环境变量PATH
中。并重新加载/etc/profile
操作zk
开启zk
查看状态
重启zk
停止zk
搭建Zookeeper集群
对于单机Zookeeper
,存在单点故障问题,系统的安全性降低,Zookeeper
在生产场景中一般都是以集群的形式出现的,而集群中法定人数主机一般为奇数。下面要搭建一个由四台zk
构成的zk
集群,其中一台为Leader
,两台为Follower
,一台为Observer
- 1.配置第一台主机,修改
zoo.cfg
配置文件,在zoo.cfg
添加zk
集群节点列表
配置文件中各个参数解释如下图所示
- 2.删除无效数据。
zk
运行期间会在/usr/data/zookeeper
目录中生成一些文件以及目录,这些文件和目录与当前的zkServer
具有绑定关系,可以先将其全部删除。
- 3.创建
myid
文件。在/usr/data/zookeeper
目录中创建表示当前主机编号的myid
文件,该主机编号与zoo.cfg
文件中设置的编号一致。
- 4.同理按照第一台主机配置另外两台非
Observer
主机。 - 5.配置第四台主机,因为第四台主机要做
Observer
主机除了要完成以上配置,修改myid
为4外,还需要修改zoo.cfg
文件,添加peerType=observer
,用于指定当前Server
即为Observer
。
zk集群操作
启动zk集群
使用zkServer.start
命令,逐个启动每一个zookeeper
节点主机,并且每启动一台,就查看一下状态。在启动第一台时,其状态如下的错误状态,因为只有一台主机无法完成Leader
选举。
再启动第二台主机再查看这两台的状态,发现第一台主机的状态为Follower
,而第二台状态为Leader
。
再启动第三台主机,查看其状态,状态为Follower
启动最后一台配置文件中配置为Observer
的主机,查看其状态确实为Observer
。
leader重新选举
将当前的Leader
主机停机,模拟Leader
宕机的情况。
再次查看未停机的zkServer
,发现其中有一台已经由原来的Follower
变为了Leader
,而这台一定不会是zkOS4
,因为其只能是Observer
。
高可用集群容灾
服务器数量奇偶数
对于zk
集群中所包含的服务器数量存在一个误区:为了防止出现赞同票数与反对票数各占一半的问题,必须要将服务器的数量部署为奇数(不包含Observer)。其实,部署奇数台服务器确实要比偶数台要好,但不是为了防止出现赞同票数与反对票数各占一半的问题,因为投票平均,则当作提案无法通过处理。
无论是写操作投票,还是Leader
选举投票,都必须过半才能通过,也就是说若出现超过半数的主机宕机,则投票永远无法通过,基于该理论,由5台主机构成的集群,最多允许2台主机,而由6台主机构成的集群,其最多也只允许2台主机,即6台主机和5台主机的容灾能力是相同的,基于此容灾能力的原因,建议使用奇数台主机构成集群,以避免资源浪费。
容灾设计方案
对于一个高可用的系统,除了要设置多台主机部署为一个集群避免单点问题之外,还需要考虑将集群部署在多个机房。下面就多个机房的部署进行分析。
三机房部署
在生产环境下,三机房部署是最常见的,容灾性最好的部署方案。假定zk
集群中机器总数(不含Observer
)为N
,三个机房中部署的机器数量分别为N1、N2、N3
。
A、N1的值
N1 = (N - 1) / 2
,例如N
的值为15,则N1
为7,即要保证第一机房中具有刚不到半数的主机数量。
B、N2的值
N2
的值是一个取值范围:1 ~ (N - N1) / 2
,仍然以N
为15为例,N2
的取值范围为1 ~ 4
台,也就是说,N2
的取值只要不超过4台即可。
C、N3的值
N3 = N - N1 - N2
,若N
为15,N2
为4,则N3
为4。
为什么要这样设计呢?这样可以保证,三个机房中若有一个机房断电或者断网,其他两个机房中的辑器仍然可以正常对外提供服务。当然,若两个机房出现了问题,那么整个集群旧瘫痪了,这种情况下出现的概率要远低于一个机房出现的问题的情况。
双机房部署
zk
官网没有给出较好的双机房部署的容灾方案,只能是让其中一个机房占有超过半数的主机,使其成为主机房,而另一机房少于半数,当然,若主机房出现问题,则整个集群会瘫痪。
扩容与缩容
水平扩容对于提高系统服务的能力,是一种非常重要的方式,但是zk
对于水平扩容与缩容做的并不完美,主机数量的变化需要修改配置文件后整个集群进行重启,集群重启的方式有两种。
集体重启
整体重启指的是将整个集群停止,然后更新所有主机的配置后再次重启集群,该方式会使集群停止对外提供服务,所以该方式慎用。
部分重启
部分重启指的是每次重启一小部分主机(不能多于半数,生产环境下一般为1/3),该方式不影响系统对外服务。
CAP原则
简介
CAP
原则又称为CAP
定理,指的是在一个分布式系统中,Consistency
(一致性),Availability
(可用性),Partition tolerance
(分区容错性)。三者不可兼得。
一致性(C)
:分布式系统中多个主机之间是否能够保持数据一致的特性,即,当系统数据发生更新操作之后,各个主机中的数据仍然处于一致的状态。可用性(A)
:系统提供的服务必须一直处于可用的状态,即对于用户的每一个请求,系统总是可以在有限的时间内对用户做出响应。分区容错性(P)
:分布式系统在遇到任何网络分区故障时,仍能够保证对外提供满足一致性可用性的服务。
三二原则
对于分布式系统,在CAP
原则中分区容错性P
时必须要保证的,但其并不能同时保证一致性与可用性。因为现在的分布式系统在满足了一致性的前提下,会牺牲可用性,为了保证一致性,系统中各个节点需要进行分布式同步,而在分布式同步过程中,整个系统是不对外提供服务的,即无法保证可用性;在满足了可用性的前提下,会牺牲一致性,因为只要系统某一节点上的数据有更新,其他节点必然要做数据同步,在不同过程中各个节点中的数据是不一致的,而这个同步过程,整个系统仍是对外提供服务的,即牺牲了一致性保证了可用性。
所以CAP
原则对于一个分布式系统来说,只可能满足两项,要么CP
,要么AP
,这就是CAP
的三二原则。
BASE理论。
BASE
是Basically Available
(基本可用)、Soft state
(软状态)和Eventually consistent
(最终一致性)三个短语的简写,BASE
是对CAP
中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的结论。是基于CAP
定理逐步演化而来的,其核心思想是即使无法做到强一致性,但每个应用都可以根据自身的业务特点,采用适当的方式时系统达到最终一致性。
基本可用
基本可用是指分布式系统中出现不可预知故障的时候,允许损失部分可用性。
软状态
软状态,是指允许系统中的数据存在中间状态,并认为中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时,软状态,其实就是一种灰度状态,过渡状态。
最终一致性
最终一致性强调的是系统中所有的数据副本,在经过一段时间同步之后,最终能够达到一个一致的状态,因此,最终一致性的本质就是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
在没有发生故障的前提下,数据达到一致的状态的时间延迟,取决于网络延迟,系统负载和数据复制方案设计等因素。
ZK与CP
zk
遵循的是CP
原则,即保证了一致性,但是牺牲了可用性,体现在哪里呢?当Leader
宕机后,zk
集群会马上进行新的Leader
选举,但是选举时长在30~120秒之间,整个选举期间zk
集群是不接受客户端的读写操作的,即zk
集群是处于瘫痪状态的,所以不满足可用性。
为什么Leader
的选举需要这么长的时间呢?为了保证zk
集群各个节点中的数据的一致性,zk
集群做了两类的数据同步:初始化同步和更新同步。当新的Leader
被选举出来之后,各个Follower
需要将新的Leader
的数据同步到自己的本地,这就是初始化同步;当Leader
的数据被客户端修改之后,会向Follower
发出广播,然后各个Follower
会主动同步Leader
的更新数据,这就是更新同步。无论是初始化同步还是更新同步,zk
集群为了保证数据的一致性,若发现超过半数的Follower
同步超时,其会再次进行同步,而这个过程中zk
集群是处于不可用状体的。
重要概念
数据模型Znode
zk
的数据存储结构与标准的Unit
文件系统非常相似,都是在根节点下挂很多多级数据节点,zk
中没有引入传统文件系统中目录与文件的概念,而是使用了称为znode
的数据节点概念,znode
是zk
中数据单元最小的单元,每个znode
上都可以保存数据,同时还可以挂载子节点,形成一个树化命名空间。
节点类型
每个znode
根据节点类型不同,具有不同的生命周期。
- 持久节点:(
zk
里面最多的节点)持久节点会一直在zk
中存放,直到你把它删除掉。 - 持久顺序节点:持久结点上面带上顺序的数字,并且是由父结点维护的。
- 临时节点:叶子节点(即下面不能再有子节点),跟会话是绑定的,即会话消失的话,该结点就会消失。但是不是所有的叶子节点都是临时节点,有些叶子结点不是临时节点。
- 临时顺序节点:临时节点具有顺序性。
节点状态
每个znode
节点,除了其要存放的数据之外,还有描述自身状态的元数据信息,这些数据信息通过后续客户端get
命令可以查看到,这些元数据信息如下:
cZxid
:Created cZxid
,表示当前znode
被创建时的事务ID
。ctime
:Created Time
,表示当前znode
被创建的时间。mZxid
:Modified Zxid
,表示当前znode
最后一次被修改时的事务ID
。mtime
:Modified Time
,表示当前znode
最后一次被修改时的时间。pZxid
:表示当前znode
的子节点列表最后一次被修改时的事务ID
,注意,只能是子节点列表变更了才会引起pZxid
的变更,子节点内容的修改不会影响pZxid
。cversion
:Children Version
,表示子节点的版本号,该版本号用于充当乐观锁。dataVersion
:表示当前znode
数据的版本号,该版本号用于充当乐观锁。aclVersion
:表示当前znode
的权限ACL
的版本号,该版本号用于充当乐观锁。ephemeralOwner
:若当前znode
是持久节点,则其值为0,若为临时节点,则其值为创建该临时节点的会话的SessionID
,当会话消失后,会根据SessionID
来查找与该会话相关的临时节点进行删除。dataLength
:当前znode
中存放的数据的长度。numChildren
:当前znode
所包含的子节点个数。
会话
会话是zk
中最重要的概念之一,客户端与服务端之间的任何交互操作都与会话相关。Zookeeper
客户端启动时,首先会与zk
服务器建立一个TCP
长连接,从第一次连接建立开始,客户端会话的生命周期也就开始了。通过这个长连接,客户端能够通过心跳检测保持与服务器的有效会话,也能够向Zookeeper
服务器发送请求并接收响应,同时还能通过该连接接收来自服务器的Watch
事件通知。
会话状态
会话状态有三种如下:
connecting
connected
close
重连
客户端与服务端的长连接如果失败,则客户端会自动进行反复重连,而这个失连后的状态有三种:
-
1.连接丢失
CONNECTION_LOSS
:跟集群中的主机失连,注意客户端跟集群连接是只要连接上集群中的任意一台主机即可。 -
2.会话超时
SESSION_EXPORED
:当出现连接丢失之后,在超过会话超时时间sessionTimeout
时重新连接上服务端,此时服务端会认为你的会话已经超时了。 -
3.会话转移
SESSION_MOVED
:例如开始时客户端连接的是集群中的1号主机,之后跟1号主机失连,但是在sessionTimeout
时间内,又跟集群中的其他主机连接上了,此时需要将之前的会话中的信息转移,并且将一些信息进行修改,就叫做会话转移。
ACL
ACL
全称为Access Control List
(访问控制列表),用于控制资源的访问权限,是zk
数据安全的保障,zk
利用ACL
策略控制znode
节点的访问权限,如节点数据读写、节点创建、节点删除、读取子节点列表、设置节点权限等。
ACL与UGO对比
说到权限控制,我们最为熟悉的是Unix/Linux
文件系统中使用的UGO(User、Group、Others)
权限控制机制,即针对一个目录/文件,对创建者(User
)、创建者所在组(Group
)及其他用户(Others
)分配不同的权限,UGO
是一种粗粒度的文件系统权限控制模式,其只能对三类用户进行权限控制,但若想让某一与创建者(User
)无关的组或者用户拥有某些权限,UGO
是无法实现的。
ACL
则是一种细粒度的权限管理方式,可以针对任意用户与组进行细粒度的权限控制,目前大多数Unix
已经支持了ACL
,Linux
也从2.6
版本开始支持了ACL
。不过需要注意的是,Linux
系统中的文件或者子目录默认会继承其父目录的ACL
,而在Zookeeper
中,znode
的ACL
是没有继承关系的,每个znode
的权限是独立控制的,只有客户端满足znode
设置的权限要求时,才能完成相应的操作。
zk的ACL维度
Linux
系统的ACL
分为两个维度:组group
与权限permission
,而Zookeeper
的ACL
分为三个维度:授权策略scheme
、授权对象id
和用户权限permission
授权策略scheme
授权策略用于确定权限验证过程中使用的校验策略,在zk
中最常用的四种策略如下:
IP
:根据IP
地址进行权限验证。digest
:(摘要)根据用户名密码进行验证。密码可以是明文,也可以是密文。world
:任何用户在不做任何验证的情况下可以对znode
进行任何操作。super
:超级用户可以对zk
所有的znode
进行任何操作。
授权对象id
授权对象指的是权限赋予的用户,不同的授权策略具有不同类型的授权对象,下面是各个授权模式对应的授权对象。
ip
:授权对象是ip
地址。digest
:授权对象是用户名/密码。world
:只有一个,即anyone
。super
:与digest
模式相同。
权限permission
权限指的是通过验证的用户可以对znode
执行哪些操作,一共有五种权限,不过zk
支持自定义权限。
c
:Create
,创建节点d
:Delete
,删除r
:Read
,读取节点的数据内容及子节点列表。w
:Write
,更新数据内容a
:acl
,数据节点的ACL
管理权限。
Watcher机制
zk
提供了分布式数据的发布订阅功能,一个发布者能够让多个订阅者同时监听某一主题对象,当这个主题对象状态发生变化时,会通知所有订阅者,使它们能够做出相应的处理(回调),zk
通过Watcher
机制实现了分布式发布订阅模式。
Watcher工作原理
说明:客户端生成Watcher
对象后将其存储到本地的WatcherManager
对象中,而没有将watcher
发送给服务端,只是向服务端发送了一个watcher
注册信息,当服务端触发Watcher
事件时,会向客户端发送相应事件通知,客户端根据通知找到对应的watcher
对象,最终执行watcher
中的回调逻辑,回调逻辑也是存在客户端的,而没有在服务端。
Watcher事件
对于同一个事件类型,在不同的通知状态中代表的含义是不同的。
Watcher特性
zk
的watcher
机制具有以下三个较明显的特性。
- 一次性:一旦一个
watcher
被触发,zk
就会从服务端删除,客户端如果还需要对服务端进行监听,则需要重新注册watcher
。 - 串行性:
watcher
回调方法的执行是串行的。 - 轻量级:客户端向服务端注册
watcher
,其并没有将watcher
发送给服务端(存放在了客户端的watcherManager
中),而是向服务端发送了一个watcher
注册信号,另外,watcher
的回调逻辑存在于客户端,而没有在服务端。
客户端命令
启动客户端
连接本机zk服务器
连接其他zk服务器
当看到如下命令时表示已经连接成功,后面的数字代表的时当前命令在当前窗口中的历史编号。
查看子节点-ls
命令语法
- 语法:
ls path
。 - 说明:查看指定节点所包含的所有子节点。
查看操作
查看根节点下面的所有子节点列表如下图所示
创建节点-create
命令语法
语法:create[-s][-e] path data acl
-s
:创建有序节点。如果在创建znode
时,我们使用排序标志的话,Zookeeper
会在我们指定的znode
名字后面增加一个数字,我们继续加入相同名字的znode
时,这个数字会不断增加,这个序号的计数器是由这些排序znode
的父节点来维护的。-e
:创建临时节点。节点类型一旦确定就不能修改了。path
:指定要创建的znode
名称以及其路径。注意,该路径中的所有znode
必须是已经存在的。acl
:权限控制,默认情况下不做任何权限控制。
创建永久节点
例如:创建一个名称为china的znode
,其值为999.
创建顺序节点
例如:在/china
节点下创建顺序子节点beijing、shanghai、guangzhou
,它们的数据内容分别是为bj、sh、gz
。
创建临时节点
临时节点与持久节点的区别,在后面的get
命令中可以看到
获取节点内容-get
命令语法
- 语法:
get path
。 - 说明:获取指定节点的数据内容及属性信息。
获取持久节点数据
如上图所示,节点/china
的数据值为999,数据长度为3,子节点数量为6,ephemeralOwner
为0,说明其为持久节点。
获取顺序节点信息
如上图所示,从节点属性信息中看不出是否是顺序节点,但是从节点名称中可以看出。
获取临时节点信息
如上图所示,这两个节点的ephemeralOwner
值都不为0,且值相同,为什么相同呢?其值为创建该节点的会话的SessionID
,当会话消失之后,会根据SessionID
来查找与该会话相关的临时节点进行删除。
通过quit
命令退出客户端之后,再次连接上Zookeeper
,然后再查看/china
的节点列表会发现原来的节点/china/aaa
、/china/bbb
与/china/ccc
均消失,会随着会话的小时而消失。
更新节点内容-set
命令语法
- 语法:
set path data
。 - 说明:修改指定节点的数据值。
更新数据
更新之前,如下图所示
更新如下图,发现dataVersion
的值由0变为了1。
再查看该节点的值,如下图所示
删除节点-delete
命令语法
- 语法:
delete path
。 - 说明:删除指定节点,不过需要注意,只能删除其下没有子节点的节点。
删除操作
若要删除具有子节点的节点时,会报错,如下图所示
ACL操作
查看权限-getAcl
注意:命令是大小写敏感的,/china
节点的授权策略是world
,授权对象是anyone
,授权是cdrwa
所有权限。
设置权限
这里使用密码为明文的权限设置方式,其需要两步:
- 首先增加一个认证用户,使用命令为:
addauth digest
用户名:密码明文。 - 然后再设置权限,使用命令为:
setAcl /path auth:用户名:密码明文:权限
下面的命令是,首先增加了一个认证用户zs
,密码为123
,然后为/china
节点指定只有zs
用户才可访问该节点,而访问权限为所有权限,并且设置过后,其aclVersion
值增加了1.
不使用授权用户来查看节点,其会显示如下内容,表示zs
用户具有cdrwa
,但密码显示的内容为123
加密过的内容。
通过quit
退出客户端之后,再次进入客户端,然后获取/china
的数据时发现,其给出的提示是权限无效,此时需要再次在客户端中通过addauth
添加zs
用户,然后才能访问。
验证权限不继承
通过quit
退出客户端之后,再次进入客户端,然后获取到/china
的数据时其肯定会提示权限无效,但是查看子节点/china/beijing0000000001
时发现是可以查看的,说明子节点没有继承父节点的ACL
权限。
Curator客户端
简介
Curator
是Netflix
公司开源的一套的zk
客户端框架,与ZKClient
一样,其也封装了zk
原生的API
。其目前已经成为了Apache
的顶级项目,同时Curator
还提供了一套易用性、可读性更强的Fluent
风格的客户端API
框架。
API介绍
这里主要以Fluent
风格客户端API
为主进行介绍。
创建会话
1.普通API
创建会话,在CuratorFramework
类中提供了两个静态方法用于完成会话的创建。
说明:重试策略支持自定义,只需要实现接口RetryPolicy
即可,而该接口是一个函数式接口,其仅仅包含一个方法。
retryCount
:记录已经重试的次数。elapsedTimeMs
:从第一次重试开始已经花费的时间,单位毫秒。sleeper
:设置休眠对象。每sleep
一定的时间后会再重试,即每隔多久重试一次。
Fluent
风格演示如下:
2.curator
中绑定watcher
的操作有三个:checkExists()、getData、getChildren()
。这三个方法的共性是它们都是用于获取的。这三个操作用于watcher
注册的方法是相同的,都是使用usingWatcher()
方法。
这两个方法中的参数CuratorWatcher
与Watcher
都为接口,这两个接口中均包含一个process
方法,它们的区别是CuratorWatcher
中的process
方法能够抛出异常,这样的话,该异常就可以被记录到日志中。
代码演示
1.创建一个Maven
的Java
工程,并导入以下依赖。
<dependencies>
<!--curator依赖-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
</dependencies>
2.演示代码如下:
/***
* @Auther: huangshuai
* @Date: 2020/9/6 23:26
* 操作zookeeper常用的客户端Curator
*/
public class FluentTest {
public static void main(String[] args) throws Exception{
//---------------创建会话-------------------
//创建重试策略对象:第1秒重试1次,最多重试3次
ExponentialBackoffRetry retryPolicy=new ExponentialBackoffRetry(1000,3);
//创建客户端
CuratorFramework client= CuratorFrameworkFactory
.builder()
.connectString("zkOS:2181")//制定服务器:端口号,这里可以指定单机或者集群
.sessionTimeoutMs(15000)//会话超时时间
.connectionTimeoutMs(13000)//连接超时时间
.retryPolicy(retryPolicy)//客户端连接不上得时候重试连接,重试得策略
.namespace("logs")//指定客户端隔离命名空间,即下面所有的操作结点都是相对于logs,即跟相对路径类似,即使以/开头也是相对于logs路径的
.build();
//-------------开启客户端-------------------
client.start();//开启客户端之后,会话就开始了
//指定要创建和操作的节点,注意,其实相当于/logs节点的子节点
//因为上面指定的了命名空间,所以只能相对于命名空间,即只能在/logs下面,即/logs/host
String nodePath="/host";
//---------------创建节点-----------------
/**
* forPath(String path):创建结点指定结点的路径,并且结点的值为空
* forPath(String path,byte[] value):创建结点指定结点的路径,并且结点的值为calue【curator中给结点赋值只能为字节数组类型】
*
* 创建临时结点 CreateMode.EPHEMERAL指定结点类型为临时结点
* String nodeName=client.create().withMode(CreateMode.EPHEMERAL).forPath(nodePath,"myhost".getBytes());
*
* 注意client.create().forPath(nodePath,"myhost".getBytes());如果父节点不存在时,不能一次创建多级节点,例如/aaa/bbb【/host/aaa结点不存在】
*
* 可以通过下面的方式创建多级结点,即使父节点不存在,它也会自动创建
* client.create().creatingParentsIfNeeded().forPath(nodePath,"myhost".getBytes());
*/
//该默认创建的结点的类型为永久结点
String nodeName=client.create().forPath(nodePath,"myhost".getBytes());
System.out.println("新创建的节点名称为:"+nodeName);
//---------------注册watcher--------------------------//
//结点的内容发生改变之后触发打印 System.out.println(event.getPath()+"数据内容发生变化");
byte[] data=client.getData().usingWatcher((CuratorWatcher)event->{
System.out.println(event.getPath()+"数据内容发生变化");
}).forPath(nodePath);
System.out.println("节点数据内容为:"+new String(data));
//获取子结点列表
List<String> childList = client.getChildren().forPath(nodePath);
//---------------更新数据内容------------------
client.setData().forPath(nodePath,"newhost".getBytes());
//获取更新过的数据内容
byte[] newData=client.getData().forPath(nodePath);
System.out.println("更新过的数据内容为:"+new String(newData));
//----------------删除节点-------------------
//若当前结点下存在子结点,则当前结点无法删除
//只能先把子结点删除掉,再删除该节点
client.delete().forPath(nodePath);
/**
* 如果想一次性删除该节点和递归删除该节点的所有子孙结点
* client.delete().deletingChildrenIfNeeded().forPath(nodeName);
*/
//---------------判断节点存在性----------------
Stat stat=client.checkExists().forPath(nodePath);
boolean isExists=true;
if (stat==null){
isExists=false;
}
System.out.println(nodePath+"节点仍存在吗?"+isExists);
}
}