ZooKeeper 客户端开发
上篇文章 ZooKeeper 原理与服务器集群部署 完成了 ZooKeeper 服务器集群的部署,本文以官方 API 和 zkClient 两种方式,演示了 ZooKeeper 数据的修改和状态监视。并以代码模拟了 ZooKeeper 在 Dubbo 中的作用。
作者:王克锋
出处:https://kefeng.wang/2017/11/10/zookeeper-development/
版权:自由转载-非商用-非衍生-保持署名,转载请标明作者和出处。
1.应用开发
API文档: https://zookeeper.apache.org/doc/current/api/index.html
Java示例: https://zookeeper.apache.org/doc/current/javaExample.html
程序员指南: https://zookeeper.apache.org/doc/current/zookeeperProgrammers.html
通常,ZooKeeper应用程序分为两个单元,一个维护连接,另一个监视数据。
1.1 主要API
- create: 创建结点;
- delete: 删除结点;
- exists: 判断结点是否存在;
- get data: 读取结点数据;
- set data: 写入结点数据;
- get children: 获取结点的子结点;
- sync: 数据同步;
1.2 Zookeeper 开发组件
Document: http://zookeeper.apache.org/doc/r3.4.11/
ZooKeeper 3.4.11 API: https://zookeeper.apache.org/doc/r3.4.11/api/index.html
是 zookeeper 自带的组件,繁琐且不可靠:
- 会话超时异常时,重新连接繁琐;
- watcher 是一次性的,需要额外编码把一次性订阅改为持久订阅;
- 节点数据是二进制,对象数据都需要转换为二进制保存。
1.2.1 pom.xml
1 | <!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper --> |
1.2.2 ZookeeperClient.java
1 | public class ZookeeperClient { |
1.2.3 运行结果
1 | 15:33:59.215 INFO [ZookeeperClient.java:24] - *** 观察者事件: path=null, type=None |
1.3 zkClient 开发组件
是对官方 ZooKeeper API 作了封装,避免了官方 API 的缺点。
https://github.com/sgroschupf/zkclient
1.3.1 pom.xml
1 | <dependency> |
1.3.2 ZookeeperClient.java
1 | public class ZookeeperClient { |
1.3.3 运行结果
1 | 16:17:21.904 INFO [ZookeeperClient.java:42] - 子节点列表: [childNode1, childNode2] |
2.ZooKeeper 在 dubbo 中的应用
ZooKeeper 作为配置中心,提供服务地址的登记和查询;
consumer 订阅服务(订阅事件,动态感知服务地址变化);
provider 注册服务(创建 zk 节点);
Dubbo 在 ZooKeeper 中的存储结构如下(增加 consumer 分支是为了便于统计服务消费情况):
- 第1级:根节点(configcenter),持久节点;
- 第2级: 各个服务名称(serviceName),持久节点;
- 第3级(扩充): 用于对节点分类(nodeType),区分 provider/consumer,持久节点;
- 第4级[provider]: 特定 serviceName 的提供者地址列表(provideAddress),非持久节点,provider 下线时该节点会自动删除,并自动通知 consumer;
- 第4级[consumer]: 特定 serviceName 的消费者地址列表(consumerAddress),非持久节点,consumer 下线时该节点会自动删除。
2.1 ServiceProvider.java
ServiceProvider 向注册中心(ZooKeeper集群)注册服务的关键代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27public class ServiceProvider {
private static Log logger = LogFactory.getLog(ServiceProvider.class);
private static final String rootPath = "/configcenter";
private static final String servicePath = rootPath + "/serviceName";
private static final String zookeeperList = "centos:2181,centos:2182,centos:2183";
public static void main(String args[]) throws UnknownHostException {
ZkClient zk = new ZkClient(zookeeperList);
// 确保根节点存在
if (!zk.exists(rootPath)) {
zk.createPersistent(rootPath);
}
// 确保服务节点存在
if (!zk.exists(servicePath)) {
zk.createPersistent(servicePath);
}
// 向服务节点注册自身IP
String serviceIp = InetAddress.getLocalHost().getHostAddress().toString();
zk.createEphemeral(servicePath + "/" + serviceIp);
// 关闭连接
zk.close();
}
}
2.2 ServiceConsumer.java
ServiceConsumer 从注册中心(ZooKeeper集群)获取服务提供者地址列表的关键代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27public class ServiceConsumer {
private static Log logger = LogFactory.getLog(ServiceConsumer.class);
private static final String servicePath = "/configcenter/serviceName";
private static final String zookeeperList = "centos:2181,centos:2182,centos:2183";
private static List<String> providerList = null; // 本地缓存的服务提供者地址列表
public static void main(String args[]) throws UnknownHostException {
ZkClient zk = new ZkClient(zookeeperList);
// 检查服务节点存在性
if (zk.exists(servicePath)) {
providerList = zk.getChildren(servicePath);
} else {
throw new RuntimeException("[" + servicePath + "] not exist.");
}
// 订阅子节点改变事件
zk.subscribeChildChanges(servicePath, new IZkChildListener() {
public void handleChildChange(String parentPath, List<String> childList) throws Exception {
providerList = childList;
}
});
// 关闭连接
zk.close();
}
}