Zookeeper技术内幕

363 阅读25分钟

Zookeeper一致性要求

什么时是zk的一致性呢?其满足的以下几点要求:

  • 顺序一致性:从同一客户端发起的n多个事务请求,最终将会严格按照发起顺序被应用到Zookeeper中。
  • 原子性:所有事务请求的结果在集群中所有机器上的应用情况是一致的,也就是说要么整个集群所有主机都成功应用了某一事务,要么都没有成功应用,不会出现集群中部分主机应用某事务成功,而另外一部分主机失败的情况。
  • 单一视图:无论客户端连接的是哪一个Zookeeper服务器,其看到的服务端数据模型都是一致的。
  • 可靠性:一旦服务端成功地应用了一个事务,并完成对客户端地响应,那么该事务所引起地服务端状态地变更将会被一直保留下来,除非有另一个事务又对其进行了变更。
  • 实时性:通常人们看到实时性地第一反应是,一旦一个事务被成功应用,那么客户端能够立即从服务端上读取到这个事务变更后地最新数据状态,这里需要注意的是,Zookeeper仅仅保证在一段较短时间内,客户端最终一定能够从服务端上读取到最新地数据状态。

Zookeeper安装

安装单机Zookeeper

对于系统安全性、实时性要求不是很高的系统,为了节约成本,使用单机Zookeeper作为协调服务器也是可以的。

  • 1.准备一台虚拟机。

  • 2.下载Zookeeper安装包,zookeeper.apache.org官网下载,将下载的Zookeeper上传安装包到主机的/usr/tools目录下。

  • 3.解压安装包,如下所示

image.png

  • 4.创建软连接

image.png

  • 5.复制配置文件,复制Zookeeper安装目录下的conf目录中的zoo_sample.cfg文件,并命名为zoo.cfg

image.png

  • 6.修改配置文件

image.png

  • 7.先创建数据存放目录如下:

image.png

  • 8.注册bin目录,由于我们要使用的命令都在zk安装目录下的bin目录中,所以在/etc/profile文件中将该bin目录注册到系统环境变量PATH中。并重新加载/etc/profile

image.png

image.png

操作zk

开启zk

image.png

查看状态

image.png

重启zk

image.png

停止zk

image.png

搭建Zookeeper集群

对于单机Zookeeper,存在单点故障问题,系统的安全性降低,Zookeeper在生产场景中一般都是以集群的形式出现的,而集群中法定人数主机一般为奇数。下面要搭建一个由四台zk构成的zk集群,其中一台为Leader,两台为Follower,一台为Observer

  • 1.配置第一台主机,修改zoo.cfg配置文件,在zoo.cfg添加zk集群节点列表

image.png 配置文件中各个参数解释如下图所示

zookeeper中配置文件参数的说明示意图.png

zookeeper集群中配置文件说明.png

  • 2.删除无效数据。zk运行期间会在/usr/data/zookeeper目录中生成一些文件以及目录,这些文件和目录与当前的zkServer具有绑定关系,可以先将其全部删除。

image.png

  • 3.创建myid文件。在/usr/data/zookeeper目录中创建表示当前主机编号的myid文件,该主机编号与zoo.cfg文件中设置的编号一致。

image.png

  • 4.同理按照第一台主机配置另外两台非Observer主机。
  • 5.配置第四台主机,因为第四台主机要做Observer主机除了要完成以上配置,修改myid为4外,还需要修改zoo.cfg文件,添加peerType=observer,用于指定当前Server即为Observer

image.png

zk集群操作

启动zk集群

使用zkServer.start命令,逐个启动每一个zookeeper节点主机,并且每启动一台,就查看一下状态。在启动第一台时,其状态如下的错误状态,因为只有一台主机无法完成Leader选举。

image.png

再启动第二台主机再查看这两台的状态,发现第一台主机的状态为Follower,而第二台状态为Leader

image.png

image.png

再启动第三台主机,查看其状态,状态为Follower

image.png 启动最后一台配置文件中配置为Observer的主机,查看其状态确实为Observer

image.png

leader重新选举

将当前的Leader主机停机,模拟Leader宕机的情况。

image.png

再次查看未停机的zkServer,发现其中有一台已经由原来的Follower变为了Leader,而这台一定不会是zkOS4,因为其只能是Observer

image.png

高可用集群容灾

服务器数量奇偶数

对于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理论。

BASEBasically 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

image.png zk的数据存储结构与标准的Unit文件系统非常相似,都是在根节点下挂很多多级数据节点,zk中没有引入传统文件系统中目录与文件的概念,而是使用了称为znode的数据节点概念,znodezk中数据单元最小的单元,每个znode上都可以保存数据,同时还可以挂载子节点,形成一个树化命名空间。

节点类型

每个znode根据节点类型不同,具有不同的生命周期。

  • 持久节点:(zk里面最多的节点)持久节点会一直在zk中存放,直到你把它删除掉。
  • 持久顺序节点:持久结点上面带上顺序的数字,并且是由父结点维护的。
  • 临时节点:叶子节点(即下面不能再有子节点),跟会话是绑定的,即会话消失的话,该结点就会消失。但是不是所有的叶子节点都是临时节点,有些叶子结点不是临时节点。
  • 临时顺序节点:临时节点具有顺序性。

节点状态

每个znode节点,除了其要存放的数据之外,还有描述自身状态的元数据信息,这些数据信息通过后续客户端get命令可以查看到,这些元数据信息如下:

  • cZxidCreated cZxid,表示当前znode被创建时的事务ID
  • ctimeCreated Time,表示当前znode被创建的时间。
  • mZxidModified Zxid,表示当前znode最后一次被修改时的事务ID
  • mtimeModified Time,表示当前znode最后一次被修改时的时间。
  • pZxid:表示当前znode的子节点列表最后一次被修改时的事务ID,注意,只能是子节点列表变更了才会引起pZxid的变更,子节点内容的修改不会影响pZxid
  • cversionChildren 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已经支持了ACLLinux也从2.6版本开始支持了ACL。不过需要注意的是,Linux系统中的文件或者子目录默认会继承其父目录的ACL,而在Zookeeper中,znodeACL是没有继承关系的,每个znode的权限是独立控制的,只有客户端满足znode设置的权限要求时,才能完成相应的操作。

zk的ACL维度

Linux系统的ACL分为两个维度:组group与权限permission,而ZookeeperACL分为三个维度:授权策略scheme、授权对象id和用户权限permission

授权策略scheme

授权策略用于确定权限验证过程中使用的校验策略,在zk中最常用的四种策略如下:

  • IP:根据IP地址进行权限验证。
  • digest:(摘要)根据用户名密码进行验证。密码可以是明文,也可以是密文。
  • world:任何用户在不做任何验证的情况下可以对znode进行任何操作。
  • super:超级用户可以对zk所有的znode进行任何操作。

授权对象id

授权对象指的是权限赋予的用户,不同的授权策略具有不同类型的授权对象,下面是各个授权模式对应的授权对象。

  • ip:授权对象是ip地址。
  • digest:授权对象是用户名/密码。
  • world:只有一个,即anyone
  • super:与digest模式相同。

权限permission

权限指的是通过验证的用户可以对znode执行哪些操作,一共有五种权限,不过zk支持自定义权限。

  • cCreate,创建节点
  • dDelete,删除
  • rRead,读取节点的数据内容及子节点列表。
  • wWrite,更新数据内容
  • aacl,数据节点的ACL管理权限。

Watcher机制

zk提供了分布式数据的发布订阅功能,一个发布者能够让多个订阅者同时监听某一主题对象,当这个主题对象状态发生变化时,会通知所有订阅者,使它们能够做出相应的处理(回调),zk通过Watcher机制实现了分布式发布订阅模式。

Watcher工作原理

image.png

说明:客户端生成Watcher对象后将其存储到本地的WatcherManager对象中,而没有将watcher发送给服务端,只是向服务端发送了一个watcher注册信息,当服务端触发Watcher事件时,会向客户端发送相应事件通知,客户端根据通知找到对应的watcher对象,最终执行watcher中的回调逻辑,回调逻辑也是存在客户端的,而没有在服务端。

Watcher事件

对于同一个事件类型,在不同的通知状态中代表的含义是不同的。

image.png

Watcher特性

zkwatcher机制具有以下三个较明显的特性。

  • 一次性:一旦一个watcher被触发,zk就会从服务端删除,客户端如果还需要对服务端进行监听,则需要重新注册watcher
  • 串行性:watcher回调方法的执行是串行的。
  • 轻量级:客户端向服务端注册watcher,其并没有将watcher发送给服务端(存放在了客户端的watcherManager中),而是向服务端发送了一个watcher注册信号,另外,watcher的回调逻辑存在于客户端,而没有在服务端。

客户端命令

启动客户端

连接本机zk服务器

image.png

连接其他zk服务器

image.png

当看到如下命令时表示已经连接成功,后面的数字代表的时当前命令在当前窗口中的历史编号。

image.png

查看子节点-ls

命令语法

  • 语法:ls path
  • 说明:查看指定节点所包含的所有子节点。

查看操作

查看根节点下面的所有子节点列表如下图所示

image.png

创建节点-create

命令语法

语法:create[-s][-e] path data acl

  • -s:创建有序节点。如果在创建znode时,我们使用排序标志的话,Zookeeper会在我们指定的znode名字后面增加一个数字,我们继续加入相同名字的znode时,这个数字会不断增加,这个序号的计数器是由这些排序znode的父节点来维护的。
  • -e:创建临时节点。节点类型一旦确定就不能修改了。
  • path:指定要创建的znode名称以及其路径。注意,该路径中的所有znode必须是已经存在的。
  • acl:权限控制,默认情况下不做任何权限控制。

创建永久节点

例如:创建一个名称为china的znode,其值为999.

image.png

创建顺序节点

例如:在/china节点下创建顺序子节点beijing、shanghai、guangzhou,它们的数据内容分别是为bj、sh、gz

image.png

创建临时节点

临时节点与持久节点的区别,在后面的get命令中可以看到

image.png

获取节点内容-get

命令语法

  • 语法:get path
  • 说明:获取指定节点的数据内容及属性信息。

获取持久节点数据

image.png 如上图所示,节点/china的数据值为999,数据长度为3,子节点数量为6,ephemeralOwner为0,说明其为持久节点。

获取顺序节点信息

image.png 如上图所示,从节点属性信息中看不出是否是顺序节点,但是从节点名称中可以看出。

获取临时节点信息

image.png 如上图所示,这两个节点的ephemeralOwner值都不为0,且值相同,为什么相同呢?其值为创建该节点的会话的SessionID,当会话消失之后,会根据SessionID来查找与该会话相关的临时节点进行删除。

通过quit命令退出客户端之后,再次连接上Zookeeper,然后再查看/china的节点列表会发现原来的节点/china/aaa/china/bbb/china/ccc均消失,会随着会话的小时而消失。

image.png

更新节点内容-set

命令语法

  • 语法:set path data
  • 说明:修改指定节点的数据值。

更新数据

更新之前,如下图所示

image.png

更新如下图,发现dataVersion的值由0变为了1。

image.png 再查看该节点的值,如下图所示

image.png

删除节点-delete

命令语法

  • 语法:delete path
  • 说明:删除指定节点,不过需要注意,只能删除其下没有子节点的节点。

删除操作

image.png 若要删除具有子节点的节点时,会报错,如下图所示

image.png

ACL操作

查看权限-getAcl

image.png

注意:命令是大小写敏感的,/china节点的授权策略是world,授权对象是anyone,授权是cdrwa所有权限。

设置权限

这里使用密码为明文的权限设置方式,其需要两步:

  • 首先增加一个认证用户,使用命令为:addauth digest 用户名:密码明文。
  • 然后再设置权限,使用命令为:setAcl /path auth:用户名:密码明文:权限

下面的命令是,首先增加了一个认证用户zs,密码为123,然后为/china节点指定只有zs用户才可访问该节点,而访问权限为所有权限,并且设置过后,其aclVersion值增加了1.

image.png

不使用授权用户来查看节点,其会显示如下内容,表示zs用户具有cdrwa,但密码显示的内容为123加密过的内容。

image.png

通过quit退出客户端之后,再次进入客户端,然后获取/china的数据时发现,其给出的提示是权限无效,此时需要再次在客户端中通过addauth添加zs用户,然后才能访问。

image.png

验证权限不继承

通过quit退出客户端之后,再次进入客户端,然后获取到/china的数据时其肯定会提示权限无效,但是查看子节点/china/beijing0000000001时发现是可以查看的,说明子节点没有继承父节点的ACL权限。

image.png

Curator客户端

简介

CuratorNetflix公司开源的一套的zk客户端框架,与ZKClient一样,其也封装了zk原生的API。其目前已经成为了Apache的顶级项目,同时Curator还提供了一套易用性、可读性更强的Fluent风格的客户端API框架。

API介绍

这里主要以Fluent风格客户端API为主进行介绍。

创建会话

1.普通API创建会话,在CuratorFramework类中提供了两个静态方法用于完成会话的创建。

image.png

image.png

说明:重试策略支持自定义,只需要实现接口RetryPolicy即可,而该接口是一个函数式接口,其仅仅包含一个方法。

image.png

  • retryCount:记录已经重试的次数。
  • elapsedTimeMs:从第一次重试开始已经花费的时间,单位毫秒。
  • sleeper:设置休眠对象。每sleep一定的时间后会再重试,即每隔多久重试一次。

Fluent风格演示如下:

image.png

2.curator中绑定watcher的操作有三个:checkExists()、getData、getChildren()。这三个方法的共性是它们都是用于获取的。这三个操作用于watcher注册的方法是相同的,都是使用usingWatcher()方法。

image.png

这两个方法中的参数CuratorWatcherWatcher都为接口,这两个接口中均包含一个process方法,它们的区别是CuratorWatcher中的process方法能够抛出异常,这样的话,该异常就可以被记录到日志中。

代码演示

1.创建一个MavenJava工程,并导入以下依赖。

<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);
    }
}