基础
zk基础
zk是什么
zookeeper是一个高性能、开源的分布式应用协调服务,使用文件系统目录树作为数据模型
提供了简单原始的功能,分布式应用可以基于它实现更高级的服务
- 可以实现同步(分布式锁)
- 配置管理
- 集群管理
zk是一种分层的树形结构
- 树形结构中每个节点称为Znode
- 每个Znode都可以有数据(byte[]类型),也可以有子节点, 每个节点都可以存储1MB(默认)的数据。
- Znode的路径使用斜线分割,例如
/Zoo/Duck
,zk中没有相对路径的说法,即所有节点的路径都要携程绝对路径的方式。
- 当zk中节点的数据发生变化时,版本号会递增。
- 可以对Znode中的数据进行读写操作
数据发布订阅/配置中心
数据发布/订阅即所谓的配置中心
发布者将数据发布到zk的一个或一些列节点上,订阅者进行数据订阅,可以即时得到数据的变化通知
- 应用A将数据发布到zkServer的某个节点上
- 应用B和C会先在zkServer上注册监听该节点的watcher(相当于listener,基于RPC实现)
- 一旦该节点有数据变化,B和C上的watcher变化得到通知,继而从zkServer上获取最新的数据
服务路由与负载均衡
zk本质上是利用zk做配置中心来实现负载均衡
- 服务提供者把自己的域名及IP端口映射注册到zk中
- 服务消费者通过域名从zk中获取到对应的IP及端口,这里的IP及端口可能有多个,只是获取其中一个
- 当服务提供者宕机时,对应的域名与IP的对应就会减少一个映射。
- dubbo服务框架就是基于zk实现服务路由和负载。
基本概念
zk的数据节点
- Znode是zk树形结构中的数据节点,用于存储数据
- Znode分为持久节点和临时节点两种类型
-
- 持久节点:一旦创建,除非主动调用删除操作,否则一直存储在zk上
-
- 临时节点:与客户端会话绑定,一旦客户端失效,这个客户端创建的所有临时节点都会被删除
- 可以为持久节点或临时节点设置Sequential属性,如果设置该属性则会自动在该节点名称后追加一个整形数字。
-
- 持久顺序(PERSISTENT_SEQUENTIAL)节点 :除了具有持久(PERSISTENT)节点的特性之外, 子节点的名称还具有顺序性。比如
/node1/app0000000001
、/node1/app0000000002
。
- 持久顺序(PERSISTENT_SEQUENTIAL)节点 :除了具有持久(PERSISTENT)节点的特性之外, 子节点的名称还具有顺序性。比如
-
- 临时顺序(EPHEMERAL_SEQUENTIAL)节点 :除了具备临时(EPHEMERAL)节点的特性之外,子节点的名称还具有顺序性
zk的watcher
watcher监听在Znode的节点上,当节点的数据更新或子节点的状态发生变化都会使客户端的watcher得到通知
连接zk服务
如果是docker部署的zk, 可以进容器内部访问.
docker exec -it zk bash # 进入容器内部
cd bin
./zkCli.sh -server 127.0.0.1:2181 # 连接zk
zk的基本操作命令
- 帮助命令 help
create [-s] [-e] path data acl
-
- 创建zk节点
-
- -s代表创建的节点具有顺序的属性
-
- -e表示创建的是临时节点,默认情况下创建的是持久节点
-
- path是节点的全路径
-
- data为创建节点中的数据
-
- acl用来进行权限控制, 默认情况下不做任何权限控制
-
create /node1 "node1"
在根目录创建了 node1 节点,存的数据是"node1"
-
create /node1/node1.1 123
在node1节点创建了 node1.1节点, 存的数据是123
ls [-s] path [watch]
-
- 指定节点下的所有子节点,ls只能查看第一级的所有子节点
-
- 其中path指定数据节点的路径
-
- -s 是列出节点以及节点状态, 是ls和stat命令的结合
-
- 加上watch参数表示监听path路径下所有子节点的变化
-
ls /node1
查看node1下的所有一级子节点
get path [watch]
-
- 获取path节点的数据内容和属性信息
-
- 加上watch参数表示监听path路径下所有子节点的变化
-
get /node1
查看node1中的数据
set path data [version]
-
- 更新path路径节点的数据内容
-
- data为更新的数据
-
- version为指定数据被更新的版本,如果version比当前的dataVersion还小会报错
-
set /node1 qwer
delete path [version]
-
- 删除路径为path的节点
-
- version指定被删除数据的版本,一般不指定,表示删除最新的数据版本,若version为旧的版本则会报错
-
delete /node1
stat path
-
- 查看节点状态
-
stat /node1
curator
基本使用
依赖
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version>
</dependency>
连接 ZooKeeper 客户端
public class MyTest {
// 重试之间等待的初始时间
private static final int BASE_SLEEP_TIME = 1000;
// 最大重试次数
private static final int MAX_RETRIES = 3;
public static void main(String[] args) {
// 重试策略
RetryPolicy retryPolicy = new ExponentialBackoffRetry(BASE_SLEEP_TIME, MAX_RETRIES);
CuratorFramework zkClient = CuratorFrameworkFactory.builder()
// 要连接的服务器列表
.connectString("192.168.79.130:2181")
.retryPolicy(retryPolicy)
.build();
zkClient.start();
}
}
创建节点
创建持久化节点
// 创建持久化节点 (默认就是持久化的), 父节点不存在时报错
zkClient.create().forPath("/node1"); // 创建node1父节点
zkClient.create().forPath("/node1/00001");
zkClient.create().withMode(CreateMode.PERSISTENT).forPath("/node1/00002");
// 当父节点不存在时, 会自动创建父节点 更推荐使用
zkClient.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.PERSISTENT)
.forPath("/node1/00001");
创建临时节点
zkClient.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.EPHEMERAL)
.forPath("/node1/00001","数据absdfasf".getBytes());
检查节点是否创建成功
//不为null的话,说明节点创建成功
Stat stat = zkClient.checkExists().forPath("/node1/00001");
获取/更新
获取数据
// 获取节点的数据内容,获取到的是 byte数组
byte[] bytes = zkClient.getData().forPath("/node1/00001");
System.out.println(new String(bytes));
更新节点数据
zkClient.setData().forPath("/node1/00001","qwerqwr".getBytes());
获取某个节点的所有子节点路径
List<String> childrenPaths = zkClient.getChildren().forPath("/node1");
删除节点
zkClient.delete().forPath("/node1/00001");
// 删除节点及其所有子节点
zkClient.delete().deletingChildrenIfNeeded().forPath("/node1");
监听器
监听节点数据变化
// 需要创建zkclient
String path = "/node1";
// 创建 NodeCache 实例
NodeCache nodeCache = new NodeCache(zkClient, path);
// 注册监听器
NodeCacheListener listener = () -> {
if (nodeCache.getCurrentData() != null) {
String data = new String(nodeCache.getCurrentData().getData());
System.out.println("节点数据变化: " + data);
} else {
System.out.println("节点被删除");
}
};
nodeCache.getListenable().addListener(listener);
// 启动 NodeCache
nodeCache.start();
// 模拟程序运行一段时间
Thread.sleep(60000);
监听节点状态变化
// 需要创建zkclient
String path = "/node1";
// 创建 TreeCache 实例
TreeCache treeCache = new TreeCache(zkClient, path);
// 注册监听器
TreeCacheListener listener = (curatorFramework, treeCacheEvent) -> {
switch (treeCacheEvent.getType()) {
case NODE_ADDED:
System.out.println("Node added: " + treeCacheEvent.getData().getPath());
break;
case NODE_REMOVED:
System.out.println("Node removed: " + treeCacheEvent.getData().getPath());
break;
case NODE_UPDATED:
System.out.println("Node updated: " + treeCacheEvent.getData().getPath());
break;
}
};
treeCache.getListenable().addListener(listener);
// 启动 TreeCache
treeCache.start();
// 模拟程序运行一段时间
Thread.sleep(60000);
监听子节点状态变化
// 需要创建zkclient
String path = "/node1";
PathChildrenCache pathChildrenCache = new PathChildrenCache(zkClient, path, true);
// 给某个节点注册子节点监听器
PathChildrenCacheListener pathChildrenCacheListener = (curatorFramework, pathChildrenCacheEvent) -> {
if (pathChildrenCacheEvent.getType() == PathChildrenCacheEvent.Type.CHILD_REMOVED) {
System.out.println("子节点删除");
} else if (pathChildrenCacheEvent.getType() == PathChildrenCacheEvent.Type.CHILD_ADDED) {
System.out.println("子节点新增");
} else if (pathChildrenCacheEvent.getType() == PathChildrenCacheEvent.Type.CHILD_UPDATED) {
System.out.println("子节点修改");
}
};
pathChildrenCache.getListenable().addListener(pathChildrenCacheListener);
// 启动pathChildrenCache
pathChildrenCache.start();
// 模拟程序运行一段时间
Thread.sleep(60000);