目录
- ZooKeeper简介
- ZooKeeper中的一些概念
- ZooKeeper安装与常用命令
- 常用命令
- ZooKeeper Java客户端 Curator入门
ZooKeeper简介
是什么?
ZooKeeper 是一个开源的分布式协调服务,本身就是一个分布式程序(只要半数以上节点存活,ZooKeeper 就能正常服务)。
它的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用。
ZooKeeper 底层其实只提供了两个功能:1、管理(存储、读取)用户程序提交的数据;2、 为用户程序提供数据节点监听服务。
ZooKeeper 将数据保存在内存中,性能是不错的。 在“读”多于“写”的应用程序中尤其地高性能,因为“写”会导致所有的服务器间同步状态。(“读”多于“写”是协调服务的典型场景)。
ZooKeeper 为我们提供了高可用、高性能、稳定的分布式数据一致性解决方案,通常被用于实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。
这些功能的实现主要依赖于 ZooKeeper 提供的 数据存储+事件监听 功能。
特点
顺序一致性: 从同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去。
原子性: 所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用。
单一系统映像: 无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的。
可靠性: 一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次更改覆盖。
实时性: 每个客户端的系统视图都是最新的。
ZooKeeper中的一些概念
数据模型
ZooKeeper 数据模型采用层次化的多叉树形结构,每个节点上都可以存储数据,这些数据可以是数字、字符串或者是二进制序列。并且。每个节点还可以拥有 N 个子节点,最上层是根节点以“/”来代表。
每个数据节点在 ZooKeeper 中被称为`znode`,它是 ZooKeeper 中数据的最小单元。并且,每个 znode 都有一个唯一的路径标识。
ZooKeeper 主要是用来协调服务的,而不是用来存储业务数据的,所以不要放比较大的数据在 znode 上,ZooKeeper 给出的每个节点的数据大小上限是 1M 。
znode
znode 分为 4 大类:
持久(PERSISTENT)节点:一旦创建就一直存在即使 ZooKeeper 集群宕机,直到将其删除。
临时(EPHEMERAL)节点:临时节点的生命周期是与 客户端会话(session) 绑定的,会话消失则节点消失。并且,临时节点只能做叶子节点 ,不能创建子节点。
持久顺序(PERSISTENT_SEQUENTIAL)节点:除了具有持久(PERSISTENT)节点的特性之外, 子节点的名称还具有顺序性。比如 /node1/app0000000001、/node1/app0000000002 。
临时顺序(EPHEMERAL_SEQUENTIAL)节点:除了具备临时(EPHEMERAL)节点的特性之外,子节点的名称还具有顺序性
每个 znode 由 2 部分组成:
stat:状态信息
data:节点存放的数据的具体内容
stat:状态信息
下面通过 get 命令来获取 根目录下的 dubbo 节点的内容,来学习一下stat的各种状态信息。
[zk: 127.0.0.1:2181(CONNECTED) 6] get /dubbo
# 该数据节点关联的数据内容为空
null
# 下面是该数据节点的一些状态信息,其实就是 Stat 对象的格式化输出
cZxid = 0x2 #create ZXID,即该数据节点被创建时的事务 id
ctime = Tue Nov 27 11:05:34 CST 2018 #create time,即该节点的创建时间
mZxid = 0x2 #modified ZXID,即该节点最终一次更新时的事务 id
mtime = Tue Nov 27 11:05:34 CST 2018 #modified time,即该节点最后一次的更新时间
pZxid = 0x3 #该节点的子节点列表最后一次修改时的事务 id,只有子节点列表变更才会更新 pZxid,子节点内容变更不会更新
cversion = 1 #子节点版本号,当前节点的子节点每次变化时值增加 1
dataVersion = 0 #数据节点内容版本号,节点创建时为 0,每更新一次节点内容(不管内容有无变化)该版本号的值增加 1
aclVersion = 0 #节点的 ACL(权限控制)版本号,表示该节点 ACL 信息变更次数
ephemeralOwner = 0x0 #创建该临时节点的会话的 sessionId;如果当前节点为持久节点,则 ephemeralOwner=0
dataLength = 0 #数据节点内容长度
numChildren = 1 #当前节点的子节点个数
对于znode 操作的权限,ZooKeeper 提供了以下 5 种:CREATE : 能创建子节点 READ:能获取节点数据和列出其子节点 WRITE : 能设置/更新节点数据 DELETE : 能删除子节点 ADMIN : 能设置节点 ACL 的权限
对于身份认证,提供了以下几种方式:world:默认方式,所有用户都可无条件访问。auth :不使用任何 id,代表任何已认证的用户。digest :用户名:密码认证方式:username:password 。ip : 对指定 ip 进行限制。
Watcher(事件监听器)
Watcher(事件监听器)
是 ZooKeeper 中的一个很重要的特性
ZooKeeper 允许用户在指定节点上注册一些 Watcher,并且在一些特定事件触发的时候,ZooKeeper 服务端会将事件通知到感兴趣的客户端上去,该机制是 ZooKeeper 实现分布式协调服务的重要特性。
Session sessionTimeout sessionID
Session 是 ZooKeeper 服务器与客户端之间的长连接。通过这个连接,客户端可以保持有效的会话、发送请求并接收响应,还能接收来自服务器的 Watcher 事件通知。
会话有一个属性叫做 sessionTimeout,代表会话的超时时间。如果客户端连接断开,只要在规定的时间内重新连接上集群中的任意一台服务器,之前创建的会话仍然有效。
在为客户端创建会话之前,服务端会为每个客户端分配一个全局唯一的 sessionID,作为会话的标识。
ZooKeeper 集群
ZooKeeper 是一种分布式协调服务,为了保证高可用性,通常以集群形式部署。由 3 台服务器即可组成ZooKeeper 集群。这些服务器在内存中维护当前状态,并通过 ZAB 协议(ZooKeeper Atomic Broadcast)保持数据一致性。常见的集群模式是 Master/Slave,其中 Master 服务器提供写服务,而 Slave 服务器通过异步复制获取最新数据并提供读服务。ZooKeeper 中不再使用传统的 Master/Slave 概念,而是引入了三种角色:Leader、Follower 和 Observer。
ZooKeeper 集群中的所有机器通过 Leader 选举 来选定一台称为“Leader”的机器。Leader 既可以为客户端提供写服务,又能提供读服务。除了 Leader 外,Follower 和 Observer 都只能提供读服务。Follower 和 Observer 唯一的区别在于 Observer 机器不参与 Leader 的选举过程,也不参与写操作的“过半写成功”策略,因此可以在不影响写性能的情况下提升集群的读性能。
当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,就会进入 Leader 选举过程,这个过程会选举产生新的 Leader 服务器。ZooKeeper 集群中的服务器状态有下面几种:LOOKING:寻找 Leader。LEADING:Leader 状态,对应的节点为 Leader。FOLLOWING:Follower 状态,对应的节点为 Follower。OBSERVING:Observer 状态,对应节点为 Observer,该节点不参与 Leader 选举。
由于在选举时一个结点得到超半的票数才能成为Leader,所以ZooKeeper 集群在宕掉几个 ZooKeeper 服务器之后,如果剩下的 ZooKeeper 服务器个数大于宕掉的个数的话整个 ZooKeeper 才依然可用。所以ZooKeeper 集群最好是奇数台,因为2n 和 2n-1 的容忍度是一样的,何必增加那一个不必要的 ZooKeeper 呢。
对于一个集群,通常多台机器会部署在不同机房,来提高这个集群的可用性。保证可用性的同时,会发生一种机房间网络线路故障,导致机房间网络不通,而集群被割裂成几个小集群。这时候子集群各自选主导致“脑裂”的情况。ZooKeeper 的过半机制导致不可能产生 2 个 leader,因为少于等于一半是不可能产生 leader 的,这就使得不论机房的机器如何分配都不可能发生脑裂。
Paxos 算法应该可以说是 ZooKeeper 的灵魂了。但是,ZooKeeper 并没有完全采用 Paxos 算法 ,而是使用 ZAB 协议作为其保证数据一致性的核心算法。ZAB(ZooKeeper Atomic Broadcast 原子广播) 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议。 在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。
ZAB 协议包括两种基本的模式,分别是崩溃恢复:当整个服务框架在启动过程中,或是当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB 协议就会进入恢复模式并选举产生新的 Leader 服务器。当选举产生了新的 Leader 服务器,同时集群中已经有过半的机器与该 Leader 服务器完成了状态同步之后,ZAB 协议就会退出恢复模式。消息广播:当集群中已经有过半的 Follower 服务器完成了和 Leader 服务器的状态同步,那么整个服务框架就可以进入消息广播模式了。
ZooKeeper安装与常用命令
使用Docker安装zookeeper(Dokcer入门可以参考这篇博客)
docker pull zookeeper:3.5.8
运行 ZooKeeper容器
docker run -d --name zookeeper -p 2181:2181 zookeeper:3.5.8
进入容器,然后再进入bin目录
docker exec -it zookeeper bash
cd bin/
连接ZooKeeper服务
./zkCli.sh -server 127.0.0.1:2181
安装演示完毕,下面演示常用命令。
常用命令
可以通过help查看常用命令。
创建节点(create 命令)
使用 create 命令可以在指定路径下创建一个 Znode。标志参数指定了创建的 Znode 是临时、持久还是顺序的。默认情况下,所有 Znodes 都是持久的。
临时 Znodes(标志:-e)在会话过期或客户端断开连接时会自动删除。
顺序 Znodes 确保 Znode 路径是唯一的,并在路径后添加序列号。例如,路径 /myapp 可能会被转换为 /myapp0000000001。
示例:
创建持久 Znode:create /FirstZnode "Myfirstzookeeper-app"
创建顺序 Znode:create -s /FirstZnode "second-data"
创建临时 Znode:create -e /SecondZnode "Ephemeral-data"
通过create 命令在根目录创建了 node1 节点,与它关联的字符串是"node1"
[zk: 127.0.0.1:2181(CONNECTED) 5] create /node1 "node1"
通过 create 命令在node1 节点下创建了 node1.1 节点,与它关联的内容是数字 123
[zk: 127.0.0.1:2181(CONNECTED) 18] get -s /node1/node1.1
123
cZxid = 0x3
ctime = Sat Mar 02 12:04:01 UTC 2024
mZxid = 0x3
mtime = Sat Mar 02 12:04:01 UTC 2024
pZxid = 0x3
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 3
numChildren = 0
获取节点数据(get 命令)
使用 get 命令可以获取指定 Znode 的关联数据和元数据。
默认情况下,get 命令只返回节点的名称,而不包含数据。
要获取节点的数据,请使用 -s 标志。
示例:
获取节点名称:get /myapp
获取节点数据:get -s /myapp
监事节点变化(get -w命令)
使用 get 命令并设置 watch 标志,可以监视指定 Znode 的变化。例如:get -w /myapp:监视 /myapp 的变化。
更新节点数据内容(set 命令)
更新node1结点数据内容为字符串"set node1"
[zk: 127.0.0.1:2181(CONNECTED) 11] set /node1 "set node1"
查看某个目录下的子节点(ls 命令)
通过 ls 命令查看 node1 目录下的节点
[zk: 127.0.0.1:2181(CONNECTED) 12] ls /node1
[node1.1]
查看节点状态(stat 命令)
[zk: 127.0.0.1:2181(CONNECTED) 19] stat /node1
cZxid = 0x2
ctime = Sat Mar 02 12:03:26 UTC 2024
mZxid = 0x2
mtime = Sat Mar 02 12:03:26 UTC 2024
pZxid = 0x3
cversion = 1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 1
查看节点信息和状态(ls2 或ls -s命令)
ls2 命令更像是 ls 命令和stat 命令的结合。ls2 命令返回的信息包括 2 部分:1. 子节点列表 2. 当前节点的 stat 信息。
[zk: 127.0.0.1:2181(CONNECTED) 20] ls2 /node1
‘ls2’ has been deprecated. Please use ‘ls [-s] path’ instead.
[node1.1]
cZxid = 0x2
ctime = Sat Mar 02 12:03:26 UTC 2024
mZxid = 0x2
mtime = Sat Mar 02 12:03:26 UTC 2024
pZxid = 0x3
cversion = 1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 1
删除节点(delete 命令)
如果你要删除某一个节点,那么这个节点必须无子节点才行。
[zk: 127.0.0.1:2181(CONNECTED) 3] delete /node1/node1.1
ZooKeeper Java客户端 Curator入门
Curator 是Netflix公司开源的一套 ZooKeeper Java客户端框架,相比于 Zookeeper 自带的客户端zookeeper 来说,Curator 的封装更加完善,各种 API 都可以比较方便地使用。
Curator4.0+版本对ZooKeeper 3.5.x支持比较好。开始之前将下面的依赖添加进项目。
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version>
</dependency>
连接 ZooKeeper 客户端
通过CuratorFrameworkFactory 创建CuratorFramework 对象,然后再调用 CuratorFramework 对象的 start() 方法即可
private static final int BASE_SLEEP_TIME = 1000; //重试之间等待的初始时间
private static final int MAX_RETRIES = 3; //最大重试次数
// Retry strategy. Retry 3 times, and will increase the sleep time between
retries.
// 创建一个重试策略,使用指数退避重试,这意味着每次重试之间的等待时间会逐渐增加
RetryPolicy retryPolicy = new ExponentialBackoffRetry(BASE_SLEEP_TIME,
MAX_RETRIES);
// 创建一个ZooKeeper客户端
CuratorFramework zkClient = CuratorFrameworkFactory.builder()
// the server to connect to (can be a server list)
.connectString("127.0.0.1:2181") //要连接的服务器列表
.retryPolicy(retryPolicy) //重试策略
.build();
zkClient.start();//启动一个ZooKeeper客户端,建立与ZooKeeper服务器的连接。
数据节点的增删改查
我们前面介绍通常是将 znode 分为 4 大类:
持久(PERSISTENT)节点:一旦创建就一直存在即使 ZooKeeper 集群宕机,直到将其删除。
临时(EPHEMERAL)节点:临时节点的生命周期是与 客户端会话(session) 绑定的,会话消失则节点消失。并且,临时节点只能做叶子节点 ,不能创建子节点。
持久顺序(PERSISTENT_SEQUENTIAL)节点:除了具有持久(PERSISTENT)节点的特性之外, 子节点的名称还具有顺序性。比如 /node1/app0000000001、/node1/app0000000002 。
临时顺序(EPHEMERAL_SEQUENTIAL)节点:除了具备临时(EPHEMERAL)节点的特性之外,子节点的名称还具有顺序性
在使用的 ZooKeeper 的时候,会发现 CreateMode 类中实际有 7种 znode 类型 ,但是用的最多的还是上面介绍的 4 种。
创建节点
a.创建持久化节点
zkClient.create().forPath("/node1/00001");
zkClient.create().withMode(CreateMode.PERSISTENT).forPath("/node1/00002");
//父节点node1还未创建时运行上面代码会报错,或者可以选择下面的代码,父节点node1不存在时自动创建
zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/node1/00001");
b.创建临时节点
zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath("/node1/00001");
c.创建节点并指定数据内容
zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/node1/00001", "java".getBytes());
byte[] dataStr = zkClient.getData().forPath("/node1/00001");//获取节点的数据内容并输出,获取到的是byte数组
System.out.println(new String(dataStr)); //获取节点的数据内容并转换为字符串
d.检测节点是否存在
if (zkClient.checkExists().forPath("/node1/00001") != null) {
System.out.println("节点存在");
} else {
System.out.println("节点不存在");
}
//不为null的话会返回一个Stat对象,Stat对象包含了节点的元数据,如版本号、创建时间等,说明刚刚节点创建成功了,
删除节点
a.删除一个子节点
zkClient.delete().forPath("/node1/00001");
b.删除一个节点以及其下的所有子节点
zkClient.delete().deletingChildrenIfNeeded().forPath("/node1");
获取/更新节点数据内容
zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath("/node1/00001","java".getBytes());
zkClient.getData().forPath("/node1/00001");//获取节点的数据内容
zkClient.setData().forPath("/node1/00001","c++".getBytes());//更新节点数据内容
获取某个节点的所有子节点路径
List<String> childrenPaths = zkClient.getChildren().forPath("/node1");
监听器
下面通过一个案例演示监听器的用法,下面是给某个节点注册子节点监听器 。注册了监听器之后,这个节点的子节点发生变化比如增加、减少或者更新的时候,你可以自定义回调操作。
String path = "/node1";
// 创建一个PathChildrenCache,它用于监听ZNode的子节点的变化
PathChildrenCache pathChildrenCache = new PathChildrenCache(zkClient, path,true);//true代表节点的数据内容也会被获取并存储在ChildData对象中
// 创建一个监听器
PathChildrenCacheListener pathChildrenCacheListener = (curatorFramework,
pathChildrenCacheEvent) -> {
// do something 这里是当监听到子节点变化时要执行的代码
};
// 将监听器添加到PathChildrenCache
pathChildrenCache.getListenable().addListener(pathChildrenCacheListener);
// 启动PathChildrenCache
pathChildrenCache.start();
PathChildrenCacheListener的适用范围确实比较广,它可以用来监听ZooKeeper中任何ZNode的子节点的变化。然而,如果你想要监听ZNode本身的变化(例如,ZNode被删除或者ZNode的数据被改变),那么你需要使用NodeCache和NodeCacheListener。同样,如果你想要监听ZooKeeper的状态变化(例如,从连接状态变为断开状态),那么你需要使用ConnectionStateListener。
对于NodeCacheListener和ConnectionStateListener,它们的使用方式和PathChildrenCacheListener类似,但是需要使用NodeCache和CuratorFramework来注册。具体来说:
- NodeCacheListener需要使用NodeCache来注册,NodeCache用于监听一个ZNode本身的变化。
- ConnectionStateListener需要使用CuratorFramework来注册,CuratorFramework用于监听ZooKeeper的连接状态变化。
如果你要获取节点事件类型的话,可以通过:
pathChildrenCacheEvent.getType()
一共有下面几种类型:
public static enum Type {
CHILD_ADDED,//子节点增加
CHILD_UPDATED,//子节点更新
CHILD_REMOVED,//子节点被删除
CONNECTION_SUSPENDED,// 连接暂停,可能是因为网络问题
CONNECTION_RECONNECTED,// 连接重新建立,之前的问题可能已经解决
CONNECTION_LOST,// 连接丢失,可能是服务器端关闭了连接
INITIALIZED;// PathChildrenCache已经初始化完成
private Type() {
}
}