提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
使用Java实现基于Zookeeper的配置管理中心
客户端对于Zookeeper中的节点数据采用watch + callback的机制进行监控,每个事件每次被绑定一次;一旦事件被触发会调用相应的回调函数
整个代码的实现应用于异步请求的环节,调用的方法中都包含回调函数来完成这个需求
二、分布式配置中心模拟实现
可以熟悉下API,如何创建客户端,怎么获取数据:Zookeeper的API使用
pom文件中的依赖如下:
<!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper -->
<!--引入zookeeper的依赖-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
1. ZKUtils 工具类
该工具类用于获取客户端实例
public class ZKUtils {
private static ZooKeeper zk;
private static String connectString = "192.168.52.130:2181,192.168.52.131:281,192.138.52.132:2181/testRoot"; // 最后的末尾:/testRoot用于指定数据存储的前缀,理解成各命名空间即可
private static CountDownLatch cdl = new CountDownLatch(1);
public static ZooKeeper getZK() {
try {
zk = new ZooKeeper(connectString, 3000, new DefaultWatcher(cdl));
cdl.await(); // 同步阻塞,维护zk的连接创建成功
} catch (Exception e) {
e.printStackTrace();
}
// 1. 为了保证返回的zk一定建立连接,使用CountDownLatch
return zk;
}
}
2. DefaultWatcher
这里的DefaultWatcher模拟的最普通的Watcher,提供监听客户端连接的事件
public class DefaultWatcher implements Watcher {
private CountDownLatch cdl;
public DefaultWatcher(CountDownLatch cdl) {
this.cdl = cdl;
}
@Override
public void process(WatchedEvent watchedEvent) {
System.out.println(watchedEvent.toString());
Event.KeeperState state = watchedEvent.getState();
switch (state) {
case Unknown:
break;
case Disconnected:
break;
case NoSyncConnected:
break;
case SyncConnected:
cdl.countDown(); // 关键就这一行,修改CountDownLatch解除阻塞
break;
case AuthFailed:
break;
case ConnectedReadOnly:
break;
case SaslAuthenticated:
break;
case Expired:
break;
case Closed:
break;
}
}
}
3. MyConf
模拟配置文件, 使用了lombok注解
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class MyConf {
private String conf;
}
5. MyWatcher
这个也是自定义的Watcher,他实现了监听节点状态setData,获取数据getData时候涉及的所有回调接口,使用更加方便(实现三个接口),初始结构:
public class MyWatcher implements Watcher, AsyncCallback.StatCallback, AsyncCallback.DataCallback {
public MyWatcher(ZooKeeper zk) {
this.zk = zk;
}
// 给节点绑定一个事件
@Override
public void process(WatchedEvent watchedEvent) {
}
// 查看节点状态
@Override
public void processResult(int i, String s, Object o, Stat stat) {
}
// 获取节点数据的回调函数
@Override
public void processResult(int i, String s, Object o, byte[] bytes, Stat stat) {
}
}
后续继续完善这个类
4. 测试代码部分说明
创建连接:
@Before
public void connect() {
zk = ZKUtils.getZK();
System.out.println("connection finish...");
}
首先测试是否能够成功检测到结点的创建
MyWatcher myWatcher = new MyWatcher(zk); // 自定义的一个综合Watcher
MyConf myConf = new MyConf();
myWatcher.setMyConf(myConf);
//1. 开始创建的时候没有节点,应该进行阻塞
myWatcher.aWait();
// zk.exists("/testCont", myWatcher, myWatcher, "version-0");
System.out.println("节点已创建");
- 当zk中没有数据的时候,应该进行阻塞等待节点创建;而为了达成更好的封装性,将阻塞的步骤封装到了myWatcher; 阻塞效果使用CountDownLatch实现
- 其次,之后需要获取数据MyConf,因此在myWatcher中添加一个成员变量
public class MyWatcher implements Watcher, AsyncCallback.StatCallback, AsyncCallback.DataCallback {
private ZooKeeper zk;
private MyConf myConf;
private CountDownLatch cdl = new CountDownLatch(1);
public MyWatcher(ZooKeeper zk) {
this.zk = zk;
}
public void setMyConf(MyConf myConf) {
this.myConf = myConf;
}
// 给节点绑定一个事件
@Override
public void process(WatchedEvent watchedEvent) {
}
// 查看节点状态
@Override
public void processResult(int i, String s, Object o, Stat stat) {
// 如果stat不为空,说明有节点,可以直接取数据
if (stat != null) {
// 这里第二个参数还要传watcher,为了持续绑定事件
zk.getData("/testCont", this, this, "version-1");
}
}
// 获取节点数据的回调函数
@Override
public void processResult(int i, String s, Object o, byte[] bytes, Stat stat) {
myConf.setConf(new String(bytes));
}
public void aWait() {
try {
zk.exists("/testCont", this, this, "version-0");
// 如果不存在会一直阻塞
cdl.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
然后需要处理监听节点的创建,一旦节点的状态发生变化就需要执行对应的回调函数
// 给节点绑定一个事件
@Override
public void process(WatchedEvent watchedEvent) {
switch (watchedEvent.getType()) {
case None:
break;
case NodeCreated: // 节点创建
zk.getData("/testCont", this, this, "version-1");
cdl.countDown();
break;
case NodeDeleted: // 后续处理节点删除的情况
myConf.setConf("");
cdl = new CountDownLatch(1);
break;
case NodeDataChanged: // 节点修改
zk.getData("/testCont", this, this, "version-1");
break;
case NodeChildrenChanged:
break;
case DataWatchRemoved:
break;
case ChildWatchRemoved:
break;
case PersistentWatchRemoved:
break;
}
}
5. 完整代码
TestCofig类:
public class TestConfig {
ZooKeeper zk;
@Before
public void connect() {
zk = ZKUtils.getZK();
System.out.println("connection finish...");
}
@After
public void close() {
try {
zk.close();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Test
public void testContent() {
MyWatcher myWatcher = new MyWatcher(zk);
MyConf myConf = new MyConf();
myWatcher.setMyConf(myConf);
//1. 开始创建的时候没有节点,应该进行阻塞
myWatcher.aWait();
// zk.exists("/testCont", myWatcher, myWatcher, "version-0");
System.out.println("节点已创建");
// 下面的while循环是核心
while (true) {
if ("".equals(myConf.getConf())) { // 为空说明节点不存在
myWatcher.aWait();
}
System.out.println(myConf);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
MyWatcher类:
public class MyWatcher implements Watcher, AsyncCallback.StatCallback, AsyncCallback.DataCallback {
private ZooKeeper zk;
private MyConf myConf;
private CountDownLatch cdl = new CountDownLatch(1);
public MyWatcher(ZooKeeper zk) {
this.zk = zk;
}
public void setMyConf(MyConf myConf) {
this.myConf = myConf;
}
// 给节点绑定一个事件
@Override
public void process(WatchedEvent watchedEvent) {
switch (watchedEvent.getType()) {
case None:
break;
case NodeCreated:
zk.getData("/testCont", this, this, "version-1");
cdl.countDown();
break;
case NodeDeleted:
myConf.setConf("");
cdl = new CountDownLatch(1);
break;
case NodeDataChanged:
zk.getData("/testCont", this, this, "version-1");
break;
case NodeChildrenChanged:
break;
case DataWatchRemoved:
break;
case ChildWatchRemoved:
break;
case PersistentWatchRemoved:
break;
}
}
// 查看节点状态
@Override
public void processResult(int i, String s, Object o, Stat stat) {
// 如果stat不为空,说明有节点,可以直接取数据
if (stat != null) {
// 这里第二个参数还要传watcher,为了持续绑定事件
zk.getData("/testCont", this, this, "version-1");
}
}
// 获取节点数据的回调函数
@Override
public void processResult(int i, String s, Object o, byte[] bytes, Stat stat) {
myConf.setConf(new String(bytes));
}
public void aWait() {
try {
zk.exists("/testCont", this, this, "version-0");
// 如果不存在会一直阻塞
cdl.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果
总结
使用Zookeeper做为配置中心,结合自身的watcher和回调可以实现实时监听配置文件;
并且正是由于ZK的实时性,如果我们将获取数据的getPath换成getChildren,那么获取的就是整个子节点,这就是服务发现
因此:目前可以看出ZK的功能 -》 1. 配置中心 2. 服务发现
也就是我们使用dubbo的时候会使用zk作为注册中心,也是由于这个性质
其次,ZK也存在分布式锁的场景,这得益于他的连接会伴随一次session的创建。