Java实现基于Zookeeper的配置管理中心

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

使用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("节点已创建");
  1. 当zk中没有数据的时候,应该进行阻塞等待节点创建;而为了达成更好的封装性,将阻塞的步骤封装到了myWatcher; 阻塞效果使用CountDownLatch实现
  2. 其次,之后需要获取数据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的创建。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值