【中间件】Spring整合ZooKeeper根底应用介绍

ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,广泛应用于分布式系统中,比方有用它做配置核心,注册核心,也有应用它来实现分布式锁的,作为高并发技术栈中不可或缺的一个根底组件,接下来咱们将看一下,zk应该怎么玩,能够怎么玩

本文作为第一篇,将次要介绍基于zk-client的根本应用姿态,顺次来理解下zk的基本概念

<!-- more -->

I. 筹备

1. zk环境装置

用于学习试点目标的体验zk性能,装置比较简单,能够参考博文: 210310-ZooKeeper装置及初体验

wget https://mirrors.bfsu.edu.cn/apache/zookeeper/zookeeper-3.6.2/apache-zookeeper-3.6.2-bin.tar.gztar -zxvf apache-zookeeper-3.6.2-bin.tar.gzcd apache-zookeeper-3.6.2-bin# 前台启动bin/zkServer.sh start-foreground

2. 我的项目环境

本文演示的是间接应用apache的zookeeper包来操作zk,与是否是SpringBoot环境无关

外围依赖

<!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper --><dependency>    <groupId>org.apache.zookeeper</groupId>    <artifactId>zookeeper</artifactId>    <version>3.7.0</version>    <exclusions>        <exclusion>            <groupId>org.slf4j</groupId>            <artifactId>slf4j-log4j12</artifactId>        </exclusion>    </exclusions></dependency>

版本阐明:

  • zk: 3.6.2
  • SpringBoot: 2.2.1.RELEASE

II. ZK应用姿态

1. zk根本知识点

首先介绍下zk的几个次要的知识点,如zk的数据模型,四种常说的节点

1.1 数据模型

zk的数据模型和咱们常见的目录树很像,从/开始,每一个层级就是一个节点

每个节点,蕴含数据 + 子节点

留神:EPHEMERAL节点,不能有子节点(能够了解为这个目录下不能再挂目录)

zk中常说的监听器,就是基于节点的,一般来讲监听节点的创立、删除、数据变更

1.2 节点

  • 长久节点 persistent node
  • 长久程序节点 persistent sequental
  • 长期节点 ephemeral node
  • 长期程序节点 ephemeral sequental

留神:

  • 节点类型一经指定,不容许批改
  • 长期节点,当会话完结,会主动删除,且不能有子节点

2. 节点创立

接下来咱们看一下zk的应用姿态,首先是创立节点,当然创立前提是得先拿到zkClient

初始化连贯

private ZooKeeper zooKeeper;@PostConstructpublic void initZk() throws IOException {    // 500s 的会话超时工夫    zooKeeper = new ZooKeeper("127.0.0.1:2181", 500_000, this);}

节点创立办法,上面别离给出两种不同的case

@Servicepublic class NodeExample implements Watcher {    /**     * 创立节点     *     * @param path     */    private void nodeCreate(String path) {        // 第三个参数ACL 示意访问控制权限        // 第四个参数,管制创立的是长久节点,长久程序节点,还是长期节点;长期程序节点        // 返回 the actual path of the created node        // 单节点存在时,抛异样 KeeperException.NodeExists        try {            String node = zooKeeper.create(path + "/yes", "保留的数据".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);            System.out.println("create node: " + node);        } catch (KeeperException.NodeExistsException e) {            // 节点存在            System.out.println("节点已存在: " + e.getMessage());        } catch (Exception e) {            e.printStackTrace();        }        // 带生命周期的节点        try {            Stat stat = new Stat();            // 当这个节点上没有child,且1s内没有变动,则删除节点            // 实测抛了异样,未知起因            String node = zooKeeper.create(path + "/ttl", ("now: " + LocalDateTime.now()).getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_WITH_TTL, stat, 1000);            System.out.println("ttl nod:" + node + " | " + stat);            // 创立已给监听器来验证            zooKeeper.exists(path + "/ttl", (e) -> {                System.out.println("ttl 节点变更: " + e);            });        } catch (KeeperException.NodeExistsException e) {            System.out.println("节点已存在: " + e.getMessage());        } catch (Exception e) {            e.printStackTrace();        }    }}

节点创立,外围在于 zooKeeper.create(path + "/yes", "保留的数据".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);

  • 当节点已存在时,再创立会抛异样 KeeperException.NodeExistsException
  • 最初一个参数,来决定咱们创立的节点类型
  • todo: 下面实例中在指定ttl时,没有胜利,暂未找到起因,待解决

3. 节点存在判断

判断节点是否存在,比拟常见了(比方咱们在创立之前,可能会先判断一下是否存在)

/** * 判断节点是否存在 */private void checkPathExist(String path) {    try {        // 节点存在,则返回stat对象; 不存在时,返回null        // watch: true 示意给这个节点增加监听器,当节点呈现创立/删除 或者 新增数据时,触发watcher回调        Stat stat = zooKeeper.exists(path + "/no", false);        System.out.println("NoStat: " + stat);    } catch (Exception e) {        e.printStackTrace();    }    try {        // 判断节点是否存在,并监听 节点的创立 + 删除 + 数据变更        // 留神这个事件监听,只会触发一次,即单这个节点数据变更屡次,只有第一次能拿到,之后的变动,须要从新再注册监听        Stat stat = zooKeeper.exists(path + "/yes", this);        System.out.println("YesStat: " + stat);    } catch (Exception e) {        e.printStackTrace();    }}

留神

外围用法: zooKeeper.exists(path + "/yes", this);

  • 当节点存在时,返回Stat对象,蕴含一些根本信息;如果不存在,则返回null
  • 第二个参数,传入的是事件回调对象,咱们的测试类NodeExmaple 实现了接口 Watcher, 所以间接传的是this
  • 注册事件监听时,须要留神这个回调只会执行一次,即触发之后就没了;前面再次批改、删除、创立节点都不会再被接管到

4. 子节点获取

获取某个节点的所有子节点,这里返回的是以后节点的一级子节点

/** * 获取节点的所有子节点, 只能获取一级节点 * * @param path */private void nodeChildren(String path) {    try {        // 如果获取胜利,会监听 以后节点的删除,子节点的创立和删除,触发回调事件, 这个回调也只会触发一次        List<String> children = zooKeeper.getChildren(path, this, new Stat());        System.out.println("path:" + path + " 's children:" + children);    } catch (KeeperException e) {        System.out.println(e.getMessage());    } catch (Exception e) {        e.printStackTrace();    }}

5. 数据获取与批改

节点上是能够存储数据的,在创立的时候,能够加上数据;前期能够读取,也能够批改

/** * 设置数据,获取数据 * * @param path */public void dataChange(String path) {    try {        Stat stat = new Stat();        byte[] data = zooKeeper.getData(path, false, stat);        System.out.println("path: " + path + " data: " + new String(data) + " : " + stat);        // 依据版本准确匹配; version = -1 就不须要进行版本匹配了        Stat newStat = zooKeeper.setData(path, ("new data" + LocalDateTime.now()).getBytes(), stat.getVersion());        System.out.println("newStat: " + stat.getVersion() + "/" + newStat.getVersion() + " data: " + new String(zooKeeper.getData(path, false, stat)));    } catch (Exception e) {        e.printStackTrace();    }}

在设置数据时,能够指定版本,当version > 0时,示意依据版本准确匹配;如果为-1时,则只有节点门路对上就成

6. 事件监听

监听次要是针对节点而言,后面在判断节点是否存在、批改数据时都能够设置监听器,然而他们是一次性的,如果咱们心愿短暂无效,则能够应用上面的addWatch

public void watchEvent(String path) {    try {        // 留神这个节点存在        // 增加监听, 与 exist判断节点是否存在时增加的监听器 不同的在于,触发之后,仍然无效还会被触发, 只有手动调用remove才会勾销        // 感知: 节点创立,删除,数据变更 ; 创立子节点,删除子节点        // 无奈感知: 子节点的子节点创立/删除, 子节点的数据变更        zooKeeper.addWatch(path + "/yes", new Watcher() {            @Override            public void process(WatchedEvent event) {                System.out.println("事件触发 on " + path + " event:" + event);            }        }, AddWatchMode.PERSISTENT);    } catch (Exception e) {        e.printStackTrace();    }    try {        // 留神这个节点不存在        // 增加监听, 与 exist 不同的在于,触发之后,仍然无效还会被触发, 只有手动调用remove才会勾销        // 与后面的区别在于,它的子节点的变动也会被监听到        zooKeeper.addWatch(path + "/no", new Watcher() {            @Override            public void process(WatchedEvent event) {                System.out.println("事件触发 on " + path + " event:" + event);            }        }, AddWatchMode.PERSISTENT_RECURSIVE);    } catch (Exception e) {        e.printStackTrace();    }    // 移除所有的监听    //zooKeeper.removeAllWatches(path, WatcherType.Any, true);}

下面给出了两种case,

  • AddWatchMode.PERSISTENT: 示意只关怀以后节点的删除、数据变更,创立,一级子节点的创立、删除;无奈感知子节点的子节点创立、删除,无奈感知子节点的数据变更
  • AddWatchMode.PERSISTENT_RECURSIVE: 相当于递归监听,改节点及其子节点的所有变更都监听

7. 节点删除

最初再介绍一个基本功能,节点删除,只有子节点都不存在时,能力删除以后节点(和linux的rmdir相似)

/** * 删除节点 */public void deleteNode(String path) {    try {        // 依据版本限定删除, -1 示意不须要管版本,path匹配就能够执行;否则须要版本匹配,不然就会抛异样        zooKeeper.delete(path, -1);    } catch (Exception e) {        e.printStackTrace();    }}

8. 小结

本文次要介绍的是java侧对zookeeper的基本操作姿态,能够算是zk的入门,理解下节点的增删改,事件监听;

当然个别更加举荐的是应用Curator来操作zk,相比拟于apache的jar包,应用姿态更加顺滑,前面也会做比照介绍

II. 其余

0. 我的项目

  • 工程:https://github.com/liuyueyi/spring-boot-demo
  • 我的项目源码: https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/410-zookeeper-basic

1. 一灰灰Blog

尽信书则不如,以上内容,纯属一家之言,因集体能力无限,不免有疏漏和谬误之处,如发现bug或者有更好的倡议,欢送批评指正,不吝感谢

上面一灰灰的集体博客,记录所有学习和工作中的博文,欢送大家前去逛逛

  • 一灰灰Blog集体博客 https://blog.hhui.top
  • 一灰灰Blog-Spring专题博客 http://spring.hhui.top