Zookeeper简介、安装、基本使用

1. Zookeeper简介

1.1 分布式系统定义及面临的问题

  ZooKeeper最为主要的使用场景,是作为分布式系统的分布式协同服务

  我们将分布式系统定义为:分布式系统是同时跨越多个物理主机,独立运行的多个软件所组成系统。类比⼀下,分布式系统就是一群人一起干活。人多力量大,每个服务器的能力是有限的,但是通过分布式系统,由n个服务器组成起来的集群,能力是可以无限扩张的。

  优点显而易见,人多干活快,并且互为备份。但是缺点也很明显。我们可以想象⼀下,以⼀个小研发团队开发软件为例,假设我们有⼀个5人的项目组,要开始⼀个系统的开发,项目组将面临如下问题:

在这里插入图片描述
  图中列举的就是项目组将要面临到的问题,这些问题在我们日常⼯作中也是天天发生,并没感觉有多么复杂,但是这是因为我们人类的大脑是个超级计算机,能够灵活应对这些问题,而且现实中信息的交换不依赖网络,不会因网络延迟或者中断,出现信息不对等,而且现实中对以上问题的处理其实并不严谨,从而也引发了很多问题。想⼀想,项目中是不是出现过沟通不畅造成任务分配有歧义?是否由于人员离职造成任务进行不下去,甚至要联系离职人员协助?是不是出现过任务分配不合理?类似这样的各种问题,肯定会发生于你的项目组中。在现实世界,我们可以人为去协调,即使出错了,人工去补错,加加班搞定就好。但在计算机的世界,这样做是行不通的,⼀切都要保证严谨,以上问题要做到尽可能不要发生。因此,分布式系统必须采用合理的方式解决掉以上的问题。

  实际上要想解决这些问题并没有那么复杂,我们仅需要做⼀件事就可以万事无忧—让信息在项目组成员中同步。如果能做到信息同步,那么每个人在干什么,大家都是清楚的,干到什么程度也是清晰的,无论谁离职也不会产生问题。分配的工作,能够及时清晰的同步给每个组员,确保每个组员收到的任务分配没有冲突。

  分布式系统的协调工作就是通过某种方式,让每个节点的信息能够同步和共享。这依赖于服务进程之间的通信。通信方式有两种:

1、通过网络进行信息共享

  这就像现实中,开发leader在会上把任务传达下去,组员通过听leader命令或者看leader的邮件知道自己要干什么。当任务分配有变化时,leader会单独告诉组员,或者再次召开会议。信息通过人与人之间的直接沟通,完成传递。

2、通过共享存储

  这就好比开发leader按照约定的时间和路径,把任务分配表放到了svn上,组员每天去svn上拉取最新的任务分配表,然后干活。其中svn就是共享存储。更好一点的做法是,当svn文件版本更新时,触发邮件通知,每个组员再去拉取最新的任务分配表。这样做更好,因为每次更新,组员都能第⼀时间得到消息,从而让自己⼿中的任务分配表永远是最新的。此种方式依赖于中央存储。整个过程如下图所示:
在这里插入图片描述

1.2 Zookeeper如何解决分布式系统面临的问题

  Zookeeper对分布式系统的协调,使用的是第二种方式,共享存储。其实共享存储,分布式应用也需要和存储进行网络通信

  实际上,通过ZooKeeper实现分布式协同的原理,和项目组通过SVN同步工作任务的例子是⼀样的。ZooKeeper就像是svn,存储了任务的分配、完成情况等共享信息。每个分布式应用的节点就是组员,订阅这些共享信息。当主节点(组leader),对某个从节点的分⼯信息作出改变时,相关订阅的从节点得到zookeeper的通知,取得自己最新的任务分配。完成工作后,把完成情况存储到zookeeper。主节点订阅了该任务的完成情况信息,所以将得到Zookeeper的完工的通知。参考下图,是不是和前面项目组通过svn分配工作的例子一模一样?仅仅是把svn和邮件系统合二为一,以ZooKeeper代替:
在这里插入图片描述
  注:Slave节点要想获取ZooKeeper的更新通知,需事先在关心的数据节点上设置观察点。

  大多数分布式系统中出现的问题,都源于信息的共享出了问题。如果各个节点间信息不能及时共享和同步,那么就会在协作过程中产生各种问题。ZooKeeper解决协同问题的关键,就是在于保证分布式系统信息的⼀致性

1.3 Zookeeper的基本概念

  Zookeeper是⼀个开源的分布式协调服务,其设计目标是将那些复杂的且容易出错的分布式⼀致性服务封装起来,构成⼀个高效可靠的服务集,并以⼀些简单的接口提供给用户使用。Zookeeper是⼀个典型的分布式数据⼀致性的解决方案,分布式应用程序可以基于它实现诸如数据订阅/发布负载均衡命名服务集群管理分布式锁分布式队列等功能。

基本概念

1、集群角色

  通常在分布式系统中,构成⼀个集群的每⼀台机器都有自己的角色,最典型的集群就是Master/Slave模式(主备模式),此情况下把所有能够处理写操作的机器称为Master机器,把所有通过异步复制方式获取最新数据,并提供读服务的机器为Slave机器

  而在Zookeeper中,这些概念被颠覆了。它没有沿用传递的Master/Slave概念,而是引入了LeaderFollowerObserver三种角色。Zookeeper集群中的所有机器通过Leader选举来选定⼀台被称为Leader的机器,Leader服务器为客户端提供读和写服务,除Leader外,其他机器包括Follower和Observer,Follower和Observer都能提供读服务,唯⼀的区别在于Observer不参与Leader选举过程,不参与写操作的过半写成功策略因此Observer可以在不影响写性能的情况下提升集群的性能。

2、会话(session)

  Session指客户端会话,⼀个客户端连接是指客户端和服务端之间的⼀个TCP长连接,Zookeeper对外的服务端口默认为2181,客户端启动的时候,首先会与服务器建立⼀个TCP连接,从第一次连接建立开始,客户端会话的生命周期也开始了,通过这个连接,客户端能够心跳检测与服务器保持有效的会话,也能够向Zookeeper服务器发送请求并接受响应,同时还能够通过该连接接受来自服务器的Watch事件通知。

3、 数据节点(Znode)

  在谈到分布式的时候,我们通常说的“节点”是指组成集群的每⼀台机器。然而,在ZooKeeper中,“节点”分为两类,第⼀类同样是指构成集群的机器,我们称之为机器节点;第⼆类则是指数据模型中的数据单元,我们称之为数据节点——ZNode。ZooKeeper将所有数据存储在内存中,数据模型是⼀棵树(ZNode Tree),由斜杠(/)进行分割的路径,就是⼀个Znode,例如/app/path1。每个ZNode上都会保存自己的数据内容,同时还会保存⼀系列属性信息。

4、版本

  刚刚我们提到,Zookeeper的每个Znode上都会存储数据,对于每个ZNode,Zookeeper都会为其维护⼀个叫作Stat的数据结构,Stat记录了这个ZNode的三个数据版本,分别是version(当前ZNode的版本)cversion(当前ZNode子节点的版本)aversion(当前ZNode的ACL版本)

5、Watcher(事件监听器)

  Wathcer(事件监听器),是Zookeeper中⼀个很重要的特性,Zookeeper允许⽤户在指定节点上注册⼀些Watcher,并且在⼀些特定事件触发的时候,Zookeeper服务端会将事件通知到感兴趣的客户端,该机制是Zookeeper实现分布式协调服务的重要特性。

6、ACL

  Zookeeper采用ACL(Access Control Lists)策略来进行权限控制,其定义了如下五种权限:

  • CREATE:创建子节点的权限。
  • READ:获取节点数据和子节点列表的权限。
  • WRITE:更新节点数据的权限。
  • DELETE:删除子节点的权限。
  • ADMIN:设置节点ACL的权限。

其中需要注意的是,CREATE和DELETE这两种权限都是针对子节点的权限控制。

2. Zookeeper环境搭建

2.1 Zookeeper的搭建方式

Zookeeper安装方式有三种,单机模式集群模式以及伪集群模式

  • 单机模式:Zookeeper只运行在一台服务器上,适合测试环境。
  • 集群模式:Zookeeper运行于一个集群上,适合生产环境,这个计算机群体被称为一个“集合体”。
  • 伪集群模式:就是在一台服务器上运行多个Zookeeper实例。

2.1.1 单机模式搭建:

zookeeper安装以linux环境为例:

1、下载

  首先我们下载稳定版本的zookeeper http://zookeeper.apache.org/releases.html

2、上传

  下载完成后,将zookeeper压缩包zookeeper-3.4.14.tar.gz上传到linux系统

3、解压缩压缩包

tar -zxvf zookeeper-3.4.14.tar.gz

4、进入 zookeeper-3.4.14 目录,创建 data 文件夹(用于存放快照文件和日志文件)

cd zookeeper-3.4.14
mkdir data

5、修改配置文件名称

cd conf
mv zoo_sample.cfg zoo.cfg

6、修改zoo.cfg中的dataDir属性,指向data目录位置

dataDir=/root/zookeeper-3.4.14/data

7、zookeeper服务启动

  进入bin目录,启动服务输入命令

./zkServer.sh start

  输出以下内容表示启动成功

在这里插入图片描述
8、关闭服务输入命令

./zkServer.sh stop

  输出以下提示信息
在这里插入图片描述
9、查看状态

./zkServer.sh status

如果启动状态,提示:
在这里插入图片描述
如果未启动状态,提示:
在这里插入图片描述

2.1.2 伪集群模式

  Zookeeper不但可以在单机上运⾏单机模式Zookeeper,而且可以在单机模拟集群模式 Zookeeper的运行,也就是将不同实例运行在同⼀台机器,用端口进行区分,伪集群模式为我们体验Zookeeper和做⼀些尝试性的实验提供了很大的便利。比如,我们在测试的时候,可以先使用少量数据在伪集群模式下进行测试。当测试可行的时候,再将数据移植到集群模式进行真实的数据实验。这样不但保证了它的可行性,同时大大提高了实验的效率。这种搭建方式,比较简便,成本比较低,适合测试和学习。

注意事项:

  ⼀台机器上部署了3个server,也就是说单台机器及上运行多个Zookeeper实例。这种情况下,必须保证每个配置文档的各个端口号不能冲突,除clientPort不同之外,dataDir也不同。另外,还要在dataDir所对应的目录中创建myid文件来指定对应的Zookeeper服务器实例。

  • clientPort端口:
    如果在1台机器上部署多个server,那么每台机器都要不同的 clientPort,比如 server1是2181,server2是2182,server3是2183
  • dataDir和dataLogDir:
    dataDir和dataLogDir也需要区分下,将数据文件和日志文件分开存放,同时每个server的这两变量所对应的路径都是不同的
  • server.X和myid:
    server.X 这个数字就是对应,data/myid中的数字。在3个server的myid⽂件中分别写⼊了1,2,3,那么每个server中的zoo.cfg都配 server.1、server.2、server.3就行了。因为在同⼀台机器上,后⾯连着的2个端口,3个server都不要⼀样,否则端口冲突。

1、下载

  首先我们下载最新稳定版本的zookeeper http://zookeeper.apache.org/releases.html

2、上传

  下载完成后,将zookeeper压缩包 zookeeper-3.4.14.tar.gz上传到linux系统

3、创建目录zkcluster

mkdir zkcluster

4、解压 zookeeper-3.4.14.tar.gz到zkcluster目录下

tar -zxvf zookeeper-3.4.14.tar.gz -C /zkcluster

5、改变名称

mv zookeeper-3.4.14 zookeeper01

6、复制并改名

cp -r zookeeper01/ zookeeper02
cp -r zookeeper01/ zookeeper03

7、分别在zookeeper01、zookeeper02、zookeeper03目录下创建data及logs目录

mkdir data
cd data
mkdir logs

8、修改配置文件名称

cd conf
mv zoo_sample.cfg zoo.cfg

9、配置每⼀个Zookeeper 的dataDir(zoo.cfg) clientPort 分别为2181 2182 2183

clientPort=2181
dataDir=/root/zkcluster/zookeeper01/data
dataLogDir=/root/zkcluster/zookeeper01/data/logs
clientPort=2182
dataDir=/root/zkcluster/zookeeper02/data
dataLogDir=/root/zkcluster/zookeeper02/data/logs
clientPort=2183
dataDir=/root/zkcluster/zookeeper03/data
dataLogDir=/root/zkcluster/zookeeper03/data/logs

10、配置集群

(1)在每个zookeeper的 data 目录下创建⼀个 myid 文件,内容分别是1、2、3 。这个文件就是记录每个服务器的ID。

touch myid

(2)在每⼀个zookeeper 的 zoo.cfg配置客户端访问端口(clientPort)和集群服务器IP列表。

server.1=49.235.242.61:2881:3881
server.2=49.235.242.61:2882:3882
server.3=49.235.242.61:2883:3883
#server.服务器ID=服务器IP地址:服务器之间通信端口:服务器之间投票选举端口

11、启动集群

  依次启动三个zk实例

3. Zookeeper基本使用

3.1 ZooKeeper系统模型

3.1.1 ZooKeeper数据模型Znode

  在ZooKeeper中,数据信息被保存在⼀个个数据节点上,这些节点被称为znode。ZNode 是Zookeeper 中最最小数据单位,在 ZNode 下面又可以再挂 ZNode,这样⼀层层下去就形成了⼀个层次化命名空间 ZNode 树,我们称为 ZNode Tree,它采用了类似文件系统的层级树状结构进行管理。见下图示例:
在这里插入图片描述
  在 Zookeeper 中,每⼀个数据节点都是⼀个 ZNode,上图根目录下有两个节点,分别是:app1 和app2,其中 app1 下面又有三个子节点,所有ZNode按层次化进行组织,形成这么⼀颗树,ZNode的节点路径标识方式和Unix文件系统路径非常相似,都是由⼀系列使用斜杠(/)进行分割的路径表示,开发人员可以向这个节点写入数据,也可以在这个节点下面创建子节点。

3.1.2 ZNode 的类型

  刚刚已经了解到,Zookeeper的znode tree是由⼀系列数据节点组成的,那接下来,我们就对数据节点做详细讲解。

Zookeeper 节点类型可以分为三大类:

  • 持久性节点(Persistent)
  • 临时性节点(Ephemeral)
  • 顺序性节点(Sequential)

  在开发中在创建节点的时候通过组合可以生成以下四种节点类型:持久节点持久顺序节点临时节点临时顺序节点。不同类型的节点则会有不同的生命周期

  • 持久节点: 是Zookeeper中最常见的⼀种节点类型,所谓持久节点,就是指节点被创建后会⼀直存在服务器,直到删除操作主动清除
  • 持久顺序节点: 就是有顺序的持久节点,节点特性和持久节点是⼀样的,只是额外特性表现在顺序上。顺序特性实质是在创建节点的时候,会在节点名后面加上⼀个数字后缀,来表示其顺序。
  • 临时节点: 就是会被自动清理掉的节点,它的生命周期和客户端会话绑在⼀起,客户端会话结束,节点会被删除掉。与持久性节点不同的是,临时节点不能创建子节点。
  • 临时顺序节点: 就是有顺序的临时节点,和持久顺序节点相同,在其创建的时候会在名字后面加上数字后缀。

3.1.3 事务ID

  首先,先了解,事务是对物理和抽象的应用状态上的操作集合。往往在现在的概念中,狭义上的事务通常指的是数据库事务,⼀般包含了⼀系列对数据库有序的读写操作,这些数据库事务具有所谓的ACID特性,即原子性(Atomic)、⼀致性(Consistency)、隔离性(Isolation)和持久性(Durability)。

  而在ZooKeeper中,事务是指能够改变ZooKeeper服务器状态的操作,我们也称之为事务操作或更新操作,⼀般包括数据节点创建与删除数据节点内容更新等操作。对于每⼀个事务请求,ZooKeeper都会为其分配⼀个全局唯⼀的事务ID,用 ZXID 来表示,通常是⼀个 64 位的数字。每⼀个 ZXID 对应⼀次更新操作,从这些ZXID中可以间接地识别出ZooKeeper处理这些更新操作请求的全局顺序。

3.1.4 ZNode 的状态信息

在这里插入图片描述
  整个 ZNode 节点内容包括两部分:节点数据内容节点状态信息。图中quota 是数据内容,其他的属于状态信息。那么这些状态信息都有什么含义呢?

  • cZxid 就是 Create ZXID,表示节点被创建时的事务ID。
  • ctime 就是 Create Time,表示节点创建时间。
  • mZxid 就是 Modified ZXID,表示节点最后⼀次被修改时的事务ID。
  • mtime 就是 Modified Time,表示节点最后⼀次被修改的时间。
  • pZxid 表示该节点的⼦节点列表最后⼀次被修改时的事务 ID。只有子节点列表变更才会更新pZxid,子节点内容变更不会更新。
  • cversion 表示子节点的版本号。
  • dataVersion 表示内容版本号。
  • aclVersion 标识ACL版本
  • ephemeralOwner 表示创建该临时节点时的会话 sessionID,如果是持久性节点那么值为 0
  • dataLength 表示数据长度。
  • numChildren 表示直系子节点数。

3.1.5 Watcher- -数据变更通知

  Zookeeper使用Watcher机制实现分布式数据的发布/订阅功能

  ⼀个典型的发布/订阅模型系统定义了一种一对多的订阅关系,能够让多个订阅者同时监听某⼀个主题对象,当这个主题对象自身状态变化时,会通知所有订阅者,使它们能够做出相应的处理。

  在 ZooKeeper 中,引⼊了 Watcher 机制来实现这种分布式的通知功能。ZooKeeper 允许客户端向服务端注册⼀个 Watcher 监听,当服务端的⼀些指定事件触发了这个 Watcher,那么就会向指定客户端发送⼀个事件通知来实现分布式的通知功能。

整个Watcher注册与通知过程如图所示。

在这里插入图片描述
  Zookeeper的Watcher机制主要包括客户端线程客户端WatcherManagerZookeeper服务器三部分。

  具体工作流程为:客户端在向Zookeeper服务器注册的同时,会将Watcher对象存储在客户端的WatcherManager当中。当Zookeeper服务器触发Watcher事件后,会向客户端发送通知,客户端线程从WatcherManager中取出对应的Watcher对象来执行回调逻辑。

3.1.6 ACL- -保障数据的安全

  Zookeeper作为⼀个分布式协调框架,其内部存储了分布式系统运行时状态的元数据,这些元数据会直接影响基于Zookeeper进行构造的分布式系统的运行状态,因此,如何保障系统中数据的安全,从而避免因误操作所带来的数据随意变更而导致的数据库异常十分重要,在Zookeeper中,提供了⼀套完善的ACL(Access Control List)权限控制机制来保障数据的安全。

  我们可以从三个方面来理解ACL机制:权限模式(Scheme)授权对象(ID)权限(Permission),通常使用"scheme: id : permission"来标识⼀个有效的ACL信息。

1、权限模式:Scheme

权限模式用来确定权限验证过程中使用的检验策略,有如下四种模式:

  • IP
    IP模式就是通过IP地址粒度来进行权限控制,如"ip:192.168.0.110"表示权限控制针对该IP地址,
    同时IP模式可以支持按照网段方式进行配置,如"ip:192.168.0.1/24"表示针对192.168.0.*这个网段进行权限控制。
  • Digest
    Digest是最常用的权限控制模式,要更符合我们对权限控制的认识,其使用"username:password"形式的权限标识来进行权限配置,便于区分不同应用来进行权限控制。
    当我们通过“username:password”形式配置了权限标识后,Zookeeper会先后对其进行SHA-1加密和BASE64编码。
  • World
    World是⼀种最开放的权限控制模式,这种权限控制方式几乎没有任何作⽤,数据节点的访问权限对所有⽤户开放,即所有用户都可以在不进行任何权限校验的情况下操作ZooKeeper上的数据。另外,World模式也可以看作是⼀种特殊的Digest模式,它只有⼀个权限标识,即“world:anyone”。
  • Super
    Super模式,顾名思义就是超级用户的意思,也是⼀种特殊的Digest模式。在Super模式下,超级⽤户可以对任意ZooKeeper上的数据节点进行任何操作。

2、授权对象:ID

  授权对象指的是权限赋予的用户或⼀个指定实体,例如 IP 地址或是机器等。在不同的权限模式下,授权对象是不同的,表中列出了各个权限模式和授权对象之间的对应关系。
在这里插入图片描述
3、权限

权限就是指那些通过权限检查后可以被允许执行的操作。在ZooKeeper中,所有对数据的操作权限分为以下五大类:

  • CREATE(C):数据节点的创建权限,允许授权对象在该数据节点下创建子节点。
  • DELETE(D):子节点的删除权限,允许授权对象删除该数据节点的子节点。
  • READ(R):数据节点的读取权限,允许授权对象访问该数据节点并读取其数据内容或子节点列表等。
  • WRITE(W):数据节点的更新权限,允许授权对象对该数据节点进行更新操作。
  • ADMIN(A):数据节点的管理权限,允许授权对象对该数据节点进行 ACL 相关的设置操作。

3.2 ZooKeeper命令行操作

  前面我们已经搭建起了⼀个能够正常运行的zookeeper服务了,所以接下来,就是来借助客户端来对zookeeper的数据节点进行操作。

  首先,进入到zookeeper的bin目录之后

  通过zkClient进入zookeeper客户端命令行

./zkCli.sh 连接本地的zookeeper服务器
./zkCli.sh -server ip:port 连接指定的服务器

  连接成功之后,系统会输出Zookeeper的相关环境及配置信息等信息。输⼊help之后,屏幕会输出可用的Zookeeper命令,如下图所示:

在这里插入图片描述
1、创建节点

  使用create命令,可以创建⼀个Zookeeper节点, 如

create [-s][-e] path data acl

  其中,-s或-e分别指定节点特性,顺序或临时节点,若不指定,则创建持久节点; acl用来进行权限控制。

① 创建顺序节点

  使用 create -s /zk-test 123 命令创建zk-test顺序节点
在这里插入图片描述
  执行完后,就在根节点下创建了⼀个叫做/zk-test的节点,该节点内容就是123,同时可以看到创建的zk-test节点后⾯添加了⼀串数字以示区别。

② 创建临时节点

  使用 create -e /zk-temp 123 命令创建zk-temp 临时节点
在这里插入图片描述
  临时节点在客户端会话结束后,就会自动删除,下面使用quit命令退出客户端
在这里插入图片描述
  再次使用客户端连接服务端,并使用ls / 命令查看根目录下的节点
在这里插入图片描述
  可以看到根目录下已经不存在zk-temp临时节点了

③ 创建永久节点

  使用 create /zk-permanent 123 命令创建 zk-permanent 永久节点
在这里插入图片描述
  可以看到永久节点不同于顺序节点,不会自动在后面添加⼀串数字

2、读取节点

  与读取相关的命令有ls 命令和get 命令

  ls命令可以列出Zookeeper指定节点下的所有子节点,但只能查看指定节点下的第⼀级的所有子节点;其中,path表示的是指定数据节点的节点路径。

ls path

  get命令可以获取Zookeeper指定节点的数据内容属性信息

get path

  若获取根节点下⾯的所有子节点,使⽤ ls / 命令 即可
在这里插入图片描述
  若想获取/zk-permanent的数据内容和属性,可使用如下命令:get /zk-permanent
在这里插入图片描述
  从上面的输出信息中,我们可以看到,第一行是节点/zk-permanent 的数据内容,其它几行则是创建该节点的事务ID(cZxid)、最后⼀次更新该节点的事务ID(mZxid)和最后⼀次更新该节点的时间(mtime)等属性信息。

3、更新节点

  使用set命令,可以更新指定节点的数据内容,用法如下

set path data [version]

  其中,data就是要更新的新内容,version表示数据版本,在zookeeper中,节点的数据是有版本概念的,这个参数用于指定本次更新操作是基于Znode的哪⼀个数据版本进行的,如将/zk-permanent节点的数据更新为456,可以使用如下命令:set /zk-permanent 456
在这里插入图片描述
  现在dataVersion已经变为1了,表示进行了更新

4、删除节点

  使用delete命令可以删除Zookeeper上的指定节点,用法如下

 delete path [version]

  其中version也是表示数据版本,使⽤delete /zk-permanent 命令即可删除/zk-permanent节点
在这里插入图片描述
  可以看到,已经成功删除/zk-permanent节点。值得注意的是,若删除节点存在子节点,那么无法删除该节点,必须先删除子节点,再删除父节点

3.3 Zookeeper的api使用

  Zookeeper作为⼀个分布式框架,主要用来解决分布式⼀致性问题,它提供了简单的分布式原语,并且对多种编程语⾔提供了API,所以接下来重点来看下Zookeeper的java客户端API使用方式,Zookeeper API 共包含五个包,分别为:

(1)org.apache.zookeeper
(2)org.apache.zookeeper.data
(3)org.apache.zookeeper.server
(4)org.apache.zookeeper.server.quorum
(5)org.apache.zookeeper.server.upgrade

  其中org.apache.zookeeper,包含Zookeeper类,他是我们编程时最常用的类文件。这个类是Zookeeper客户端的主要类文件。如果要使用Zookeeper服务,应用程序首先必须创建⼀个Zookeeper实例,这时就需要使用此类。⼀旦客户端和Zookeeper服务端建立起了连接,Zookeeper系统将会给本次连接会话分配⼀个ID值,并且客户端将会周期性的向服务器端发送心跳来维持会话连接。只要连接有效,客户端就可以使用Zookeeper API来做相应处理了。

1、准备工作:导入依赖

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.14</version>
</dependency>

2、建立会话

/**
 * 建立会话
 */
public class CreateSession implements Watcher {

    private static CountDownLatch countDownLatch = new CountDownLatch(1);

    public static void main(String[] args) throws IOException, InterruptedException {

        /**
         *  客户端可以通过创建一个zk实例来连接zk服务器
         * new Zookeeper(connectString, sesssionTimeOut, Wather)
         *  connectString: 连接地址:IP:端口
         *   sesssionTimeOut:会话超时时间:单位毫秒
         *   Wather:监听器(当特定事件触发监听时,zk会通过watcher通知到客户端)
         */
        ZooKeeper zooKeeper = new ZooKeeper("127.0.0.1:2181", 5000, new CreateSession());
        System.out.println(zooKeeper.getState());

        // 计数工具类:CountDownLatch:不让main方法结束,让线程处于等待阻塞
        countDownLatch.await();

        System.out.println("客户端与服务端会话真正建立了");
    }

    /**
     * 回调方法:处理来自服务器端的watcher通知
     */
    public void process(WatchedEvent watchedEvent) {
        // SyncConnected
        if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
            //解除主程序在CountDownLatch上的等待阻塞
            System.out.println("process方法执行了...");
            countDownLatch.countDown();
        }
    }

}

  注意,ZooKeeper 客户端和服务端会话的建⽴是⼀个异步的过程,也就是说在程序中,构造方法会在处理完客户端初始化工作后立即返回,在大多数情况下,此时并没有真正建立好⼀个可用的会话,在会话的生命周期中处于“CONNECTING”的状态。 当该会话真正创建完毕后ZooKeeper服务端会向会话对应的客户端发送⼀个事件通知,以告知客户端,客户端只有在获取这个通知之后,才算真正建⽴了会话。

3、创建节点

public class CreateNode implements Watcher {

    private static CountDownLatch countDownLatch = new CountDownLatch(1);

    private static ZooKeeper zooKeeper;

    public static void main(String[] args) throws IOException, InterruptedException, KeeperException {

        /**
         *  客户端可以通过创建一个zk实例来连接zk服务器
         *  new Zookeeper(connectString, sesssionTimeOut, Wather)
         *  connectString: 连接地址:IP:端口
         *  sesssionTimeOut:会话超时时间:单位毫秒
         *  Wather:监听器(当特定事件触发监听时,zk会通过watcher通知到客户端)
         */
        zooKeeper = new ZooKeeper("127.0.0.1:2181", 5000, new CreateNode());
        System.out.println(zooKeeper.getState());

        // 让线程处于无限休眠状态
        Thread.sleep(Integer.MAX_VALUE);

    }

    /**
     * 回调方法:处理来自服务器端的watcher通知
     */
    public void process(WatchedEvent watchedEvent) {
        // SyncConnected
        if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
            System.out.println("process方法执行了...");
            // 创建节点
            try {
                createNoteSync();
            } catch (KeeperException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }


    /**
     * 创建节点的方法
     */
    private static void createNoteSync() throws KeeperException, InterruptedException {
        /**
         *  path        :节点创建的路径
         *  data[]      :节点创建要保存的数据,是个byte类型的
         *  acl         :节点创建的权限信息(4种类型)
         *                 ANYONE_ID_UNSAFE    : 表示任何人
         *                 AUTH_IDS    :此ID仅可用于设置ACL。它将被客户机验证的ID替换。
         *                 OPEN_ACL_UNSAFE    :这是一个完全开放的ACL(常用)--> world:anyone
         *                 CREATOR_ALL_ACL  :此ACL授予创建者身份验证ID的所有权限
         *  createMode    :创建节点的类型(4种类型)
         *                  PERSISTENT:持久节点
         *				    PERSISTENT_SEQUENTIAL:持久顺序节点
         *                  EPHEMERAL:临时节点
         *                  EPHEMERAL_SEQUENTIAL:临时顺序节点
         String node = zookeeper.create(path,data,acl,createMode);
         */

        // 持久节点
        String note_persistent = zooKeeper.create("/lg-persistent", "持久节点内容".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);

        // 临时节点
        String note_ephemeral = zooKeeper.create("/lg-ephemeral", "临时节点内容".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);

        // 持久顺序节点
        String note_persistent_sequential = zooKeeper.create("/lg-persistent_sequential", "持久顺序节点内容".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);

        System.out.println("创建的持久节点" + note_persistent);
        System.out.println("创建的临时节点" + note_ephemeral);
        System.out.println("创建的持久顺序节点" + note_persistent_sequential);
    }
}

4、获取节点数据

public class GetNodeData implements Watcher {

    private static ZooKeeper zooKeeper;

    public static void main(String[] args) throws IOException, InterruptedException, KeeperException {

        /**
         *  客户端可以通过创建一个zk实例来连接zk服务器
         *  new Zookeeper(connectString,sesssionTimeOut,Wather)
         *  connectString: 连接地址:IP:端口
         *  sesssionTimeOut:会话超时时间:单位毫秒
         *  Wather:监听器(当特定事件触发监听时,zk会通过watcher通知到客户端)
         */
        zooKeeper = new ZooKeeper("127.0.0.1:2181", 5000, new GetNodeData());
        System.out.println(zooKeeper.getState());

        // 让线程处于无限休眠
        Thread.sleep(Integer.MAX_VALUE);

    }

    /**
     * 回调方法:处理来自服务器端的watcher通知
     */
    public void process(WatchedEvent watchedEvent) {
        /**
         *  子节点列表发生改变时,服务器端会发生nodeChildrenChanged事件通知
         *   要重新获取子节点列表,同时注意:通知是一次性的,需要反复注册监听
         */
        if (watchedEvent.getType() == Event.EventType.NodeChildrenChanged) {
            List<String> children = null;
            try {
                children = zooKeeper.getChildren("/lg-persistent", true);
            } catch (KeeperException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(children);
        }

        // SyncConnected
        if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
            System.out.println("process方法执行了...");
            try {
                // 获取节点数据的方法
                getNoteData();

                // 获取节点的子节点列表方法
                getChildrens();
            } catch (KeeperException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 获取某个节点的内容
     */
    private void getNoteData() throws KeeperException, InterruptedException {
        /**
         * path: 获取数据的路径
         * watch: 是否开启监听
         * stat: 节点状态信息
         *      null: 表示获取最新版本的数据
         *  zk.getData(path, watch, stat);
         */
        byte[] data = zooKeeper.getData("/lg-persistent", false, null);
        System.out.println(new String(data));
    }

    /**
     * 获取某个节点的子节点列表方法
     */
    public static void getChildrens() throws KeeperException, InterruptedException {
        /**
         *   path:路径
         *   watch:是否要启动监听,当子节点列表发生变化,会触发监听
         *   zooKeeper.getChildren(path, watch);
         */
        List<String> children = zooKeeper.getChildren("/lg-persistent", true);
        System.out.println(children);
    }

}    

5、修改节点数据

public class UpdateNodeData implements Watcher {

    private static ZooKeeper zooKeeper;

    public static void main(String[] args) throws IOException, InterruptedException, KeeperException {

        /**
         *  客户端可以通过创建一个zk实例来连接zk服务器
         *  new Zookeeper(connectString,sesssionTimeOut,Wather)
         *  connectString: 连接地址:IP:端口
         *  sesssionTimeOut:会话超时时间:单位毫秒
         *   Wather:监听器(当特定事件触发监听时,zk会通过watcher通知到客户端)
         */
        zooKeeper = new ZooKeeper("127.0.0.1:2181", 5000, new UpdateNodeData());
        System.out.println(zooKeeper.getState());

        // 计数工具类:CountDownLatch:不让main方法结束,让线程处于等待阻塞
        //countDownLatch.await();\
        Thread.sleep(Integer.MAX_VALUE);

    }


    /**
     * 回调方法:处理来自服务器端的watcher通知
     */
    public void process(WatchedEvent watchedEvent) {
        // SyncConnected
        if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {

            //解除主程序在CountDownLatch上的等待阻塞
            System.out.println("process方法执行了...");

            // 更新数据节点内容的方法
            try {
                updateNoteSync();
            } catch (KeeperException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }

    }

    /**
     * 更新数据节点内容的方法
     */
    private void updateNoteSync() throws KeeperException, InterruptedException {
        byte[] data = zooKeeper.getData("/lg-persistent", false, null);
        System.out.println("修改前的值:" + new String(data));
        
        /**
         *  path:路径
         *  data:要修改的内容 byte[]
         *  version:为-1,表示对最新版本的数据进行修改
         *  zooKeeper.setData(path, data,version);
         */
        //修改/lg-persistent 的数据 stat: 状态信息对象
        Stat stat = zooKeeper.setData("/lg-persistent", "客户端修改了节点数据".getBytes(), -1);

        byte[] data2 = zooKeeper.getData("/lg-persistent", false, null);
        System.out.println("修改后的值:" + new String(data2));
    }
    
}

6、删除节点

public class DeleteNode implements Watcher {

    private static ZooKeeper zooKeeper;

    public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
        /**
         * 客户端可以通过创建一个zk实例来连接zk服务器
         * new Zookeeper(connectString,sesssionTimeOut,Wather)
         * connectString: 连接地址:IP:端口
         * sesssionTimeOut:会话超时时间:单位毫秒
         *   Wather:监听器(当特定事件触发监听时,zk会通过watcher通知到客户端)
         */
        zooKeeper = new ZooKeeper("127.0.0.1:2181", 5000, new DeleteNode());
        System.out.println(zooKeeper.getState());

        // 计数工具类:CountDownLatch:不让main方法结束,让线程处于等待阻塞
        //countDownLatch.await();\
        Thread.sleep(Integer.MAX_VALUE);

    }
    
    /**
     * 回调方法:处理来自服务器端的watcher通知
     */
    public void process(WatchedEvent watchedEvent) {
        // SyncConnected
        if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {

            //解除主程序在CountDownLatch上的等待阻塞
            System.out.println("process方法执行了...");
            // 删除节点
            try {
                deleteNoteSync();
            } catch (KeeperException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 删除节点的方法
     */
    private void deleteNoteSync() throws KeeperException, InterruptedException {
        /**
         *zooKeeper.exists(path,watch) :判断节点是否存在
         *zookeeper.delete(path,version) : 删除节点
         */
        Stat stat = zooKeeper.exists("/lg-persistent/c1", false);
        System.out.println(stat == null ? "该节点不存在" : "该节点存在");

        if (stat != null) {
            zooKeeper.delete("/lg-persistent/c1", -1);
        }

        Stat stat2 = zooKeeper.exists("/lg-persistent/c1", false);
        System.out.println(stat2 == null ? "该节点不存在" : "该节点存在");
    }
    
}

3.4 Zookeeper-开源客户端

3.4.1 ZkClient

  ZkClient是Github上一个开源的zookeeper客户端,在Zookeeper原生API接口之上进行了包装,是⼀个更易用的Zookeeper客户端,同时,zkClient 在内部还实现了诸如Session超时重连、Watcher反复注册等功能。

  接下来,还是从创建会话、创建节点、读取数据、更新数据、删除节点等方面来介绍如何使用 zkClient 这个zookeeper客户端。

1、添加依赖:

  在pom.xml文件中添加如下内容

<dependency>
    <groupId>com.101tec</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.2</version>
</dependency>

2、创建会话:

  使用ZkClient可以轻松的创建会话,连接到服务端。

public class CreateSession {

    /**
     * 借助zkclient完成会话的创建
     */
    public static void main(String[] args) {
        /**
         *   创建一个zkclient实例就可以完成连接,完成会话的创建
         *   serverString : 服务器连接地址
         *
         *   注意:zkClient通过对zookeeperAPI内部封装,将这个异步创建会话的过程同步化了..
         */
        ZkClient zkClient = new ZkClient("127.0.0.1:2181");
        System.out.println("会话被创建了..");
    }

}

3、创建节点

  ZkClient提供了递归创建节点的接口,即其帮助开发者先完成父节点的创建,再创建子节点。

public class Create_Node {

    /**
     * 借助zkclient完成会话的创建
     */
    public static void main(String[] args) {

        /**
         *   创建一个zkclient实例就可以完成连接,完成会话的创建
         *   serverString : 服务器连接地址
         *
         *   注意:zkClient通过对zookeeperAPI内部封装,将这个异步创建会话的过程同步化了..
         */
        ZkClient zkClient = new ZkClient("127.0.0.1:2181");
        System.out.println("会话被创建了..");
        
        /**
         *  创建节点
         *  cereateParents : 是否要创建父节点,如果值为true,那么就会递归创建节点
         */
        zkClient.createPersistent("/lg-zkclient/c1", true);
        System.out.println("节点递归创建完成");
    }

}        

  值得注意的是,在原生态接口中是无法创建成功的(父节点不存在),但是通过ZkClient 通过设置createParents参数为true可以递归的先创建父节点,再创建子节点。

4、删除节点

  ZkClient提供了递归删除节点的接口,即其帮助开发者先删除所有子节点(存在),再删除父节点。

public class Delete_Node {
    
    /**
     * 借助zkclient完成会话的创建
     */
    public static void main(String[] args) {

        /**
         *   创建一个zkclient实例就可以完成连接,完成会话的创建
         *   serverString : 服务器连接地址
         *
         *  注意:zkClient通过对zookeeperAPI内部封装,将这个异步创建会话的过程同步化了..
         */
        ZkClient zkClient = new ZkClient("127.0.0.1:2181");
        System.out.println("会话被创建了..");

        // 递归删除节点
        String path = "/lg-zkclient/c1";
        zkClient.createPersistent(path + "/c11");
        zkClient.deleteRecursive(path);
        System.out.println("递归删除成功");
    }

}

  结果表明ZkClient可直接删除带子节点的父节点,因为其底层先删除其所有子节点,然后再删除父节点。

5、获取子节点

public class Get_NodeChildren {

    /**
     * 借助zkclient完成会话的创建
     */
    public static void main(String[] args) throws InterruptedException {
        /**
         *   创建一个zkclient实例就可以完成连接,完成会话的创建
         *  serverString : 服务器连接地址
         *
         * 注意:zkClient通过对zookeeperAPI内部封装,将这个异步创建会话的过程同步化了..
         */
        ZkClient zkClient = new ZkClient("127.0.0.1:2181");
        System.out.println("会话被创建了..");

        // 获取子节点列表
        List<String> children = zkClient.getChildren("/lg-zkclient");
        System.out.println(children);

        // 注册监听事件
        /**
         *   客户端可以对一个不存在的节点进行子节点变更的监听
         *   只要该节点的子节点列表发生变化,或者该节点本身被创建或者删除,都会触发监听
         */
        zkClient.subscribeChildChanges("/lg-zkclient-get", new IZkChildListener() {
            /**
             *  s : parentPath
             *  list : 变化后子节点列表
             */
            public void handleChildChange(String parentPath, List<String> list) throws Exception {
                System.out.println(parentPath + "的子节点列表发生了变化,变化后的子节点列表为" + list);
            }
        });

        //测试
        zkClient.createPersistent("/lg-zkclient-get");
        Thread.sleep(1000);

        zkClient.createPersistent("/lg-zkclient-get/c1");
        Thread.sleep(1000);
    }

}

结果表明:

  客户端可以对⼀个不存在的节点进行子节点变更的监听。

  ⼀旦客户端对⼀个节点注册了子节点列表变更监听之后,那么当该节点的子节点列表发生变更时,服务端都会通知客户端,并将最新的子节点列表发送给客户端

  该节点本身的创建或删除也会通知到客户端。

6、zkClient的API

public class Note_API {
    
    /**
     * 借助zkclient完成会话的创建
     */
    public static void main(String[] args) throws InterruptedException {

        /**
         *   创建一个zkclient实例就可以完成连接,完成会话的创建
         *   serverString : 服务器连接地址
         *
         *  注意:zkClient通过对zookeeperAPI内部封装,将这个异步创建会话的过程同步化了..
         */
        ZkClient zkClient = new ZkClient("127.0.0.1:2181");
        System.out.println("会话被创建了..");

        // 判断节点是否存在
        String path = "/lg-zkClient-Ep";
        boolean exists = zkClient.exists(path);

        if (!exists) {
            // 创建临时节点
            zkClient.createEphemeral(path, "123");
        }

        // 读取节点内容
        Object o = zkClient.readData(path);
        System.out.println(o);

        // 注册监听
        zkClient.subscribeDataChanges(path, new IZkDataListener() {
            /**
             *   当节点数据内容发生变化时,执行的回调方法
             *   s: path
             *   o: 变化后的节点内容
             */
            public void handleDataChange(String s, Object o) throws Exception {
                System.out.println(s + "该节点内容被更新,更新的内容" + o);
            }

            /**
             *   当节点被删除时,会执行的回调方法
             *  s : path
             */
            public void handleDataDeleted(String s) throws Exception {
                System.out.println(s + "该节点被删除");
            }
        });

        // 更新节点内容
        zkClient.writeData(path, "456");
        Thread.sleep(1000);

        // 删除节点
        zkClient.delete(path);
        Thread.sleep(1000);
    }

}

3.4.2 Curator客户端

  curator是Netflix公司开源的⼀套Zookeeper客户端框架,和ZKClient⼀样,Curator解决了很多Zookeeper客户端非常底层的细节开发工作,包括连接重连反复注册WatcherNodeExistsException异常等,是最流行的Zookeeper客户端之⼀。从编码风格上来讲,它提供了基于Fluent的编程风格支持。

1、添加依赖

  在pom.xml文件中添加如下内容:

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>2.12.0</version>
</dependency>

2、创建会话

  Curator的创建会话方式与原生的API和ZkClient的创建方式区别很大。Curator创建客户端是通过CuratorFrameworkFactory工厂类来实现的。具体如下:

(1)使用 CuratorFramework 这个工厂类的两个静态方法来创建⼀个客户端

public static CuratorFramework newClient(String connectString, RetryPolicy retryPolicy)

public static CuratorFramework newClient(String connectString, int sessionTimeoutMs, int connectionTimeoutMs, RetryPolicy retryPolicy)

  其中参数RetryPolicy提供重试策略的接口,可以让用户实现自定义的重试策略,默认提供了以下实现,分别为ExponentialBackoffRetry(基于backoff的重连策略)、RetryNTimes(重连N次策略)、RetryForever(永远重试策略)

(2)通过调用CuratorFramework中的start()方法来启动会话

RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181",retryPolicy);
client.start();
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181", 5000,1000,retryPolicy);
client.start();

  其实进⼀步查看源代码可以得知,其实这两种方法内部实现⼀样,只是对外包装成不同的方法。它们的底层都是通过第三个方法builder()来实现的

RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
private static CuratorFramework Client = CuratorFrameworkFactory.builder()
 	.connectString("server1:2181,server2:2181,server3:2181")
 	.sessionTimeoutMs(50000)
 	.connectionTimeoutMs(30000)
 	.retryPolicy(retryPolicy)
 	.build();
client.start();

参数:

  • connectString:zk的server地址,多个server之间使用英文逗号分隔开
  • connectionTimeoutMs:连接超时时间,如上是30s,默认是15s
  • sessionTimeoutMs:会话超时时间,如上是50s,默认是60s
  • retryPolicy:失败重试策略
    • ExponentialBackoffRetry:构造器含有三个参数 ExponentialBackoffRetry(int baseSleepTimeMs, int maxRetries, int maxSleepMs)
      • baseSleepTimeMs:初始的sleep时间,用于计算之后的每次重试的sleep时间,
        • 计算公式:当前sleep时间=baseSleepTimeMs*Math.max(1,
          random.nextInt(1<<(retryCount+1)))
      • maxRetries:最大重试次数
      • maxSleepMs:最大sleep时间,如果上述的当前sleep计算出来比这个大,那么sleep用
        这个时间,默认的最大时间是Integer.MAX_VALUE毫秒。
  • 其他,查看org.apache.curator.RetryPolicy接口的实现类
  • start():完成会话的创建
public class CreateSession {

    // 创建会话
    public static void main(String[] args) {
        //不使用fluent编程风格
        RetryPolicy exponentialBackoffRetry = new ExponentialBackoffRetry(1000, 3);
        CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient("127.0.0.1:2181", exponentialBackoffRetry);
        curatorFramework.start();
        System.out.println( "会话被建立了");

        // 使用fluent编程风格
        CuratorFramework client = CuratorFrameworkFactory.builder()
                .connectString("127.0.0.1:2181")  //server地址
                .sessionTimeoutMs(50000) // 会话超时时间
                .connectionTimeoutMs(30000) // 连接超时时间
                .retryPolicy(exponentialBackoffRetry) // 重试策略
                .namespace("base")  // 独立的命名空间 /base
                .build();

        client.start();

        System.out.println("会话2创建了");
    }
    
}

  需要注意的是session2会话含有隔离命名空间,即客户端对Zookeeper上数据节点的任何操作都是相对/base目录进行的,这有利于实现不同的Zookeeper的业务之间的隔离

3、创建节点

  curator提供了⼀系列Fluent风格的接口,通过使用 Fluent 编程风格的接口,开发人员可以进行自由组合来完成各种类型节点的创建。

下面简单介绍⼀下常用的几个节点创建场景。

(1)创建⼀个初始内容为空的节点

client.create().forPath(path);

Curator默认创建的是持久节点,内容为空。

(2)创建⼀个包含内容的节点

client.create().forPath(path,"我是内容".getBytes());

  Curator和ZkClient不同的是依旧采用 Zookeeper 原生 API 的风格,内容使用 byte[] 作为方法参数。

(3)递归创建父节点,并选择节点类型

client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(path);

  creatingParentsIfNeeded这个接口非常有用,在使用 ZooKeeper 的过程中,开发人员经常会碰到NoNodeException 异常,其中⼀个可能的原因就是试图对⼀个不存在的父节点创建子节点。因此,开发人员不得不在每次创建节点之前,都判断⼀下该父节点是否存在——这个处理通常比较麻烦。在使用 Curator 之后,通过调用 creatingParentsIfNeeded 接口,Curator 就能够自动地递归创建所有需要的父节点。

下面通过⼀个实际例子来演示如何在代码中使用这些API。

public class CreateNode_curator {

    // 创建会话
    public static void main(String[] args) throws Exception {
        //不使用fluent编程风格
        RetryPolicy exponentialBackoffRetry = new ExponentialBackoffRetry(1000, 3);
        CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient("127.0.0.1:2181", exponentialBackoffRetry);
        curatorFramework.start();

        // 使用fluent编程风格
        CuratorFramework client = CuratorFrameworkFactory.builder()
                .connectString("127.0.0.1:2181")
                .sessionTimeoutMs(50000)
                .connectionTimeoutMs(30000)
                .retryPolicy(exponentialBackoffRetry)
                .namespace("base")  // 独立的命名空间 /base
                .build();

        client.start();

        System.out.println("会话2创建了");

        // 创建节点
        String path = "/lg-curator/c1";
        String s = client.create().creatingParentsIfNeeded()
                .withMode(CreateMode.PERSISTENT).forPath(path, "init".getBytes());

        System.out.println("节点递归创建成功,该节点路径" + s);
    }
    
}

4、删除节点

  删除节点的方法也是基于Fluent 方式来进行操作,不同类型的操作调用新增不同的方法调用即可。

(1)删除⼀个子节点

client.delete().forPath(path);

(2)删除节点并递归删除其子节点

client.delete().deletingChildrenIfNeeded().forPath(path);

(3)指定版本进行删除

client.delete().withVersion(1).forPath(path);

如果此版本已经不存在,则删除异常,异常信息如下。

org.apache.zookeeper.KeeperException$BadVersionException: KeeperErrorCode = BadVersion for

(4)强制保证删除⼀个节点

client.delete().guaranteed().forPath(path);

  只要客户端会话有效,那么Curator会在后台持续进⾏删除操作,直到节点删除成功。比如遇到⼀些网络异常的情况,此guaranteed的强制删除就会很有效果。

演示实例:

public class DeleteNode_curator {

    // 创建会话
    public static void main(String[] args) throws Exception {
        //不使用fluent编程风格
        RetryPolicy exponentialBackoffRetry = new ExponentialBackoffRetry(1000, 3);
        CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient("127.0.0.1:2181", exponentialBackoffRetry);
        curatorFramework.start();
        System.out.println("会话被建立了");

        // 使用fluent编程风格
        CuratorFramework client = CuratorFrameworkFactory.builder()
                .connectString("127.0.0.1:2181")
                .sessionTimeoutMs(50000)
                .connectionTimeoutMs(30000)
                .retryPolicy(exponentialBackoffRetry)
                .namespace("base")  // 独立的命名空间 /base
                .build();

        client.start();

        System.out.println("会话2创建了");

        // 删除节点
        String path = "/lg-curator";
        client.delete().deletingChildrenIfNeeded().withVersion(-1).forPath(path);

        System.out.println("删除成功,删除的节点" + path);
    }
    
}

5、获取数据

  获取节点数据内容API相当简单,同时Curator提供了传入⼀个Stat变量的方式来存储服务器端返回的最新的节点状态信息

// 普通查询
client.getData().forPath(path);
// 包含状态查询
Stat stat = new Stat();
client.getData().storingStatIn(stat).forPath(path);

演示:

public class GetNode_curator {

    // 创建会话
    public static void main(String[] args) throws Exception {
        //不使用fluent编程风格
        RetryPolicy exponentialBackoffRetry = new ExponentialBackoffRetry(1000, 3);

        // 使用fluent编程风格
        CuratorFramework client = CuratorFrameworkFactory.builder()
                .connectString("127.0.0.1:2181")
                .sessionTimeoutMs(50000)
                .connectionTimeoutMs(30000)
                .retryPolicy(exponentialBackoffRetry)
                .namespace("base")  // 独立的命名空间 /base
                .build();

        client.start();

        System.out.println("会话2创建了");

        // 创建节点
        String path = "/lg-curator/c1";
        String s = client.create().creatingParentsIfNeeded()
                .withMode(CreateMode.PERSISTENT).forPath(path, "init".getBytes());

        System.out.println("节点递归创建成功,该节点路径" + s);

        // 获取节点的数据内容及状态信息

        // 数据内容
        byte[] bytes = client.getData().forPath(path);
        System.out.println("获取到的节点数据内容:" + new String(bytes));

        // 状态信息
        Stat stat = new Stat();
        client.getData().storingStatIn(stat).forPath(path);

        System.out.println("获取到的节点状态信息:" + stat);

    }

}

6、更新数据

  更新数据,如果未传入version参数,那么更新当前最新版本,如果传入version则更新指定version,如果version已经变更,则抛出异常。

// 普通更新
client.setData().forPath(path,"新内容".getBytes());
// 指定版本更新
client.setData().withVersion(1).forPath(path);

版本不⼀致异常信息:

org.apache.zookeeper.KeeperException$BadVersionException: KeeperErrorCode = BadVersion for

案例演示:

public class UpdateNode_curator {

    // 创建会话
    public static void main(String[] args) throws Exception {
        //不使用fluent编程风格
        RetryPolicy exponentialBackoffRetry = new ExponentialBackoffRetry(1000, 3);

        // 使用fluent编程风格
        CuratorFramework client = CuratorFrameworkFactory.builder()
                .connectString("127.0.0.1:2181")
                .sessionTimeoutMs(50000)
                .connectionTimeoutMs(30000)
                .retryPolicy(exponentialBackoffRetry)
                .namespace("base")  // 独立的命名空间 /base
                .build();

        client.start();

        System.out.println("会话2创建了");

        // 创建节点
        String path = "/lg-curator/c1";

        // 获取节点的数据内容及状态信息

        // 数据内容
        byte[] bytes = client.getData().forPath(path);
        System.out.println("获取到的节点数据内容:" + new String(bytes));

        // 状态信息
        Stat stat = new Stat(); //0
        client.getData().storingStatIn(stat).forPath(path);

        System.out.println("获取到的节点状态信息:" + stat);

        // 更新节点内容 //1
        int version = client.setData().withVersion(stat.getVersion()).forPath(path, "修改内容1".getBytes()).getVersion();
        System.out.println("当前的最新版本是" + version);
        byte[] bytes2 = client.getData().forPath(path);
        System.out.println("修改后的节点数据内容:" + new String(bytes2));

        // BadVersionException
        client.setData().withVersion(stat.getVersion()).forPath(path, "修改内容2".getBytes());
    }
    
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值