3-Zookeeper基础应用和实战

Zookeeper命令操作

Zookeeper数据模型

ZooKeeper 是一个树形目录服务,其数据模型和Unix的文件系统目录树很类似,拥有一个层次化结构。

Zookeeper这里面的每一个节点都被称为: ZNode,每个节点上都会保存自己的数据和节点信息。

节点可以拥有子节点,同时也允许少量(1MB)数据存储在该节点之下。

节点可以分为四大类:

  • PERSISTENT 持久化节点
  • EPHEMERAL 临时节点:-e
  • PERSISTENT_SEQUENTIAL持久化顺序节点:-s
  • EPHEMERAL_SEQUENTIAL 临时顺序节点:-es

Zookeeper服务端常用命令

  • 查看服务

    • ls / 查看目录
    • ls /zookeeper 查看子节点
    • ls -R / 查看所有
  • 创建

    • create /xxx
    • create /xxx xxx 创建并且写入数据
    • create /xxx/xxx 创建子集
  • 设置值

    • set /xxx xxxx
  • 删除值

    • delete /xxx
    • deleteall /xxx 删除父级下的所有信息
  • 临时节点

    • 在会话结束后,会自动的删除掉。
    • 添加 -e 表示临时节点
  • 顺序节点

    • 创建出来的节点,根据先后的顺序,会在节点之后带上一个数值,越往后值越大。
    • create -s /xxx
  • 创建临时顺序节点

    • create -es /xxx
  • 查看详细信息

    • ls / -s
      • 身份标识
        • cZxid = 0x0
          • 节点的创建事务ID(ZooKeeper通过全局递增的事务ID追踪所有操作)。
        • mZxid = 0x0
          • 节点的最后修改事务ID (与 cZxid 相同,说明节点自创建后未被修改过)。
      • 时间戳
        • ctime = Thu Jan 01 08:00:00 CST 1970
          • 节点的创建时间 (Unix时间戳起点,即 epoch time,可能表示未记录真实时间或示例占位值)。
        • mtime = Thu Jan 01 08:00:00 CST 1970
          • 节点的最后修改时间(与创建时间相同,说明未被修改过)。
      • 父子关系
        • pZxid = 0x17
          • 父节点的事务ID(0x17 是父节点最后一次修改的事务ID)。
      • 版本控制
        • cversion = 12
          • 子节点的修改次数(该节点的子节点被更新过12次)。
        • dataVersion = 0
          • 节点数据的版本号(此处为0,说明节点未存储数据)。
        • aclVersion = 0
          • 权限控制列表的版本号(默认权限)。
      • 其他属性
        • ephemeralOwner = 0x0
          • 临时节点的所有者会话ID(0x0 表示非临时节点,是持久节点)。
        • dataLength = 0
          • 节点存储的数据长度(无数据)。
        • numChildren = 4
          • 当前节点的子节点数量(根目录下共有4个子节点)。

Zookeeper JavaAPI操作

Curator介绍

Curator是Netflix公司开源的一套zookeeper客户端框架,Curator是对Zookeeper支持最好的客户端框架。Curatoi封装了大部分Zookeeper的功能,比如Leader选举、分布式锁等,减少了技术人员在使用Zookeeper时的底层细节开发工作。

Curator框架主要解决了三类问题:

  • 封装ZooKeeper Client与ZooKeeper Server之间的连接处理(提供连接重试机制等)。
  • 提供了一套Fluent风格的AP!,并且在Java客户端原生API的基础上进行了增强(创捷多层节点、删除多层节点等)。
  • 提供ZooKeeper各种应用场景(分布式锁、leader选举、共享计数器、分布式队列等)的抽象封装。

创建Maven项目

pom.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.dwl</groupId>
    <artifactId>zk-client</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>5.3.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>5.3.0</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.21</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.21</version>
        </dependency>

    </dependencies>
</project>
java 复制代码
package com.dwl;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.CuratorEvent;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * ZooKeeper Curator 客户端操作示例(含生产级最佳实践)
 * <p>
 * 核心设计原则:
 * 1. 单例模式保证客户端唯一性(通过静态初始化块实现线程安全初始化)
 * 2. 显式连接状态管理(监听连接状态变化)
 * 3. 网络异常自动恢复机制(指数退避重试策略)
 * 4. 资源生命周期管控(自动关闭连接池)
 * <p>
 * 关键配置说明:
 * - 会话超时时间 > 3倍网络延迟 + 最大操作耗时(示例设置为15秒)
 * - 重试策略采用指数退避算法(初始3秒,最多重试10次)
 * - 连接字符串支持集群模式(示例为单节点)
 *
 * @Version 1.0.0
 * @Date 2025
 * @Author By Dwl
 */
public class CuratorTest {
    /* 定义重试策略:指数退避算法
     * 参数详解:
     * - baseSleepTimeMs: 初始重试间隔(3秒)
     * - maxRetries: 最大重试次数(10次)
     * 退避规则:
     * 3s → 6s → 12s → ... → 最终间隔 3 * 2^(10-1) = 1536s (~25分钟)
     * 总重试时间上限:约25分钟(防止无限重试)
     */
    static RetryPolicy policy = new ExponentialBackoffRetry(3000, 10);

    // 单例客户端实例(线程安全初始化)
    private static final CuratorFramework CLIENT;

    static {
        try {
            // 构建客户端实例(推荐使用构建器模式)
            CLIENT = CuratorFrameworkFactory.builder()
                    // 连接字符串(支持多节点集群:host1:port1,host2:port2)
                    .connectString("127.0.0.1:2181")

                    // 会话超时时间(关键参数)
                    // - 建议值:2~10分钟(根据业务容忍度调整)
                    // - 必须 > 连接超时时间,否则会触发会话过期
                    .sessionTimeoutMs(15_000)

                    // 连接超时时间(TCP握手超时)
                    // - 建议值:5~30秒(网络差时适当增大)
                    // - 必须 < 会话超时时间
                    .connectionTimeoutMs(10_000)

                    // 重试策略(指数退避)
                    .retryPolicy(policy)

                    // 构建客户端实例
                    .build();

            // 注册连接状态监听器(处理网络闪断)
            CLIENT.getConnectionStateListenable().addListener((c, newState) -> {
                switch (newState) {
                    case SUSPENDED -> System.out.println("⚠️ 与 ZooKeeper 断开连接(可能网络抖动)");
                    case RECONNECTED -> System.out.println("✅ 重新连接成功(自动恢复中)");
                    case LOST -> System.err.println("❗️ 会话已丢失(需人工介入检查)");
                }
            });

            // 启动客户端(单例初始化必须线程安全)
            CLIENT.start();

            // 阻塞等待初始连接(重要:防止未就绪时操作)
            // 超时时间应根据实际部署环境调整
            if (!CLIENT.blockUntilConnected(30, TimeUnit.SECONDS)) {
                throw new RuntimeException("⚠️ ZooKeeper 连接超时,初始化失败");
            }

        } catch (Exception e) {
            throw new RuntimeException("初始化 ZooKeeper 客户端失败", e);
        }
    }

    // 优雅关闭客户端(释放连接池资源)
    public static void shutdown() {
        if (CLIENT != null) {
            CLIENT.close();
            System.out.println("✅ ZooKeeper 客户端已关闭");
        }
    }

    // 全局访问入口(防御性编程)
    public static CuratorFramework getClient() throws InterruptedException {
        if (CLIENT == null) {
            throw new IllegalStateException("ZooKeeper client 未初始化");
        }
        // 添加运行时状态检查(防御性编程)
        checkClientHealth();
        return CLIENT;
    }

    // 健康检查辅助方法
    private static void checkClientHealth() throws InterruptedException {
        boolean state = CLIENT.getZookeeperClient().blockUntilConnectedOrTimedOut();
        if (!state) {
            throw new IllegalStateException("❌ ZooKeeper 客户端处于不可用状态。");
        }
    }

    public static void main(String[] args) throws Exception {
        // 测试不同操作
        // theFirstMethod();
        // theSecondWay();
        // groundlessCreateNode();
        // createNodeData();
        // createNodeSetType();
        // getData();
        // getDataSub();
        // getNodeStatus();
        // updateData();
        // updateDataVersion();
        // deleteData();
        // deleteDataContainSub();
        // mustBeDeleteSuccessfully();
        executeAfterDeletion();
    }


    /**
     * 第一种连接方式:基础连接模式
     * <p>
     * 特点说明:
     * - 显式调用 start() 启动连接
     * - 阻塞等待连接结果
     * <p>
     * 使用场景:
     * - 简单脚本工具
     * - 非生产环境测试
     * <p>
     * 注意事项:
     * ❌ 不要在生产环境使用这种方式(缺乏优雅关闭机制)
     */
    public static void theFirstMethod() throws InterruptedException {
        // 显式启动客户端(非单例模式)
        CuratorFramework client = CuratorFrameworkFactory.newClient(
                "127.0.0.1:2181",
                new ExponentialBackoffRetry(1000, 3)
        );
        client.start();

        try {
            if (client.blockUntilConnected(10, TimeUnit.SECONDS)) {
                System.out.println("✅ 临时连接成功(非推荐方式)");
            }
        } finally {
            client.close(); // 必须显式关闭
        }
    }

    /**
     * 第二种连接方式:构建器模式(推荐生产使用)
     * <p>
     * 核心优势:
     * ✅ 支持命名空间隔离(相当于逻辑分组)
     * ✅ 内置连接池管理(提升并发性能)
     * ✅ 支持多种连接状态监听(实时感知网络状态)
     * <p>
     * 关键参数:
     * - namespace:逻辑隔离空间(相当于独立ZooKeeper实例)
     * - 自动重试机制:基于ExponentialBackoffRetry策略
     * <p>
     * 生命周期管理:
     * 使用 try-with-resources 确保资源释放(自动调用close())
     */
    public static void theSecondWay() {
        CuratorFramework client = CuratorFrameworkFactory.builder()
                .connectString("127.0.0.1:2181")
                .sessionTimeoutMs(60_000)
                .connectionTimeoutMs(15_000)
                .retryPolicy(policy)
                .namespace("dev") // 逻辑隔离命名空间
                .build();

        try (CuratorFramework ignored = client) { // 自动关闭资源
            client.start();
            if (client.blockUntilConnected(10, TimeUnit.SECONDS)) {
                System.out.println("✅ 开发环境连接成功");
                // 执行CRUD操作...
            }
        } catch (Exception e) {
            System.err.println("❌ 操作失败: " + e.getMessage());
        }
    }

    /**
     * 创建持久化节点(基础操作)
     * <p>
     * 节点类型说明:
     * - PERSISTENT:持久节点(默认模式,会话结束后不删除)
     * - EPHEMERAL:临时节点(会话结束时自动删除)
     * - SEQUENCE:顺序节点(节点名自动追加单调递增序号)
     * <p>
     * 核心API说明:
     * - creatingParentsIfNeeded():自动创建父节点(避免NoNodeException)
     * - forPath(path):指定节点路径(支持绝对路径和相对路径)
     * <p>
     * 防御性编程:
     * ✅ 操作前检查连接状态(通过blockUntilConnectedOrTimedOut())
     * ✅ 捕获并处理特定异常(如NodeExistsException)
     */
    public static void groundlessCreateNode() throws Exception {
        CuratorFramework client = getClient();
        // 创建节点没有指定数据,默认将客户端ip作为存储。
        if (client.getZookeeperClient().blockUntilConnectedOrTimedOut()) {
            String path = client.create()
                    .creatingParentsIfNeeded() // 自动创建父节点
                    .withMode(CreateMode.PERSISTENT) // 显式指定节点类型
                    .forPath("/dwl_node");
            System.out.println("✅ 创建节点成功: " + path);
        }
    }

    /**
     * 创建带数据的节点
     * <p>
     * 数据操作规范:
     * - 数据长度建议 < 1MB(ZooKeeper最佳实践)
     * - 使用字节数组保证序列化一致性(避免字符串编码问题)
     * <p>
     * 注意事项:
     * ❌ 避免存储敏感信息(可通过ACL控制访问)
     * ❌ 注意数据版本控制(使用version参数实现乐观锁)
     */
    public static void createNodeData() throws Exception {
        CuratorFramework client = getClient();

        if (client.getZookeeperClient().blockUntilConnectedOrTimedOut()) {
            byte[] data = "config_data".getBytes(); // 使用UTF-8编码字节数组
            String path = client.create()
                    .creatingParentsIfNeeded()
                    .withMode(CreateMode.PERSISTENT)
                    .forPath("/dwl_config", data);
            System.out.println("✅ 创建带数据节点成功: " + path);
        }
    }

    /**
     * 创建临时节点(会话感知)
     * <p>
     * 典型应用场景:
     * - 服务注册发现(临时节点自动注销)
     * - 分布式锁(临时有序节点)
     * <p>
     * 特性说明:
     * - 会话失效时自动删除(通过sessionTimeoutMs控制生命周期)
     * - 不允许创建子节点(临时节点必须是叶子节点)
     * <p>
     * 最佳实践:
     * ✅ 配合监听器实现服务健康监测(节点消失触发事件)
     * ✅ 结合临时有序节点实现公平锁(顺序保证)
     */
    public static void createNodeSetType() throws Exception {
        CuratorFramework client = getClient();

        if (client.getZookeeperClient().blockUntilConnectedOrTimedOut()) {
            String path = client.create()
                    .withMode(CreateMode.EPHEMERAL) // 显式声明临时节点
                    .forPath("/dwl_ephemeral");
            System.out.println("✅ 创建临时节点成功: " + path);
        }
    }

    /**
     * 获取节点数据
     * <p>
     * 数据操作规范:
     * - 返回字节数组需按约定反序列化(示例中简单转为字符串)
     * - 处理空节点场景(返回null时增加判空逻辑)
     * <p>
     * 异常处理:
     * ❗️ 若节点不存在会抛出KeeperException$NoNodeException
     * ❗️ 网络异常会抛出InterruptedException
     */
    public static void getData() throws Exception {
        CuratorFramework client = getClient();

        // 获取原始字节数组(需根据业务场景反序列化)
        byte[] data = client.getData().forPath("/case");
        System.out.println("节点数据: " + new String(data));
    }

    /**
     * 获取子节点列表
     * <p>
     * 数据操作规范:
     * - 返回节点名称列表(不包含路径前缀)
     * - 处理空目录场景(返回空列表)
     * <p>
     * 序列化建议:
     * ✅ 对于复杂数据结构,推荐使用JSON/XML序列化
     * ✅ 结合自定义Schema验证数据格式
     */
    public static void getDataSub() throws Exception {
        CuratorFramework client = getClient();

        // 获取指定路径下的直接子节点(不包括递归子节点)
        List<String> children = client.getChildren().forPath("/");
        System.out.println("子节点列表: " + Arrays.toString(children.toArray()));
    }

    /**
     * 获取节点元数据(Stat信息)
     * <p>
     * Stat结构说明:
     * - czxid:创建事务ID
     * - mzxid:最后修改事务ID
     * - ctime:创建时间(毫秒)
     * - mtime:最后修改时间(毫秒)
     * - version:数据版本号
     * - cversion:子节点版本号
     * <p>
     * 版本控制:
     * ✅ 更新数据时需校验version参数(实现乐观锁)
     * ✅ 删除节点时也需校验version参数
     */
    public static void getNodeStatus() throws Exception {
        CuratorFramework client = getClient();

        Stat stat = new Stat();
        client.getData().storingStatIn(stat).forPath("/case");

        System.out.println("节点元数据:");
        System.out.println("创建时间: " + stat.getCtime());
        System.out.println("最后修改时间: " + stat.getMtime());
        System.out.println("数据版本: " + stat.getVersion());
    }

    /**
     * 更新节点数据(覆盖式写入)
     * <p>
     * 更新策略:
     * - 直接替换现有数据(无版本校验)
     * - 适用于可覆盖的非关键数据
     */
    public static void updateData() throws Exception {
        CuratorFramework client = getClient();
        client.setData().forPath("/case", "case".getBytes());
        getData();
    }

    /**
     * 带版本控制的更新操作
     * <p>
     * 乐观锁实现:
     * 1. 获取当前数据版本号
     * 2. 提交更新时校验版本号
     * 3. 版本不一致时抛出异常(需业务处理)
     */
    public static void updateDataVersion() throws Exception {
        CuratorFramework client = getClient();
        Stat stat = new Stat();
        client.getData().storingStatIn(stat).forPath("/case");
        int version = stat.getVersion();
        client.setData().withVersion(version).forPath("/case", "case_version".getBytes());

        getData();
    }

    /**
     * 删除单个节点(不包含子节点)
     * <p>
     * 删除规则:
     * - 只有叶子节点可删除
     * - 非空节点删除需设置deletingChildrenIfNeeded()
     */
    public static void deleteData() throws Exception {
        CuratorFramework client = getClient();
        client.delete().forPath("/case_1");
        getDataSub();
    }

    /**
     * 递归删除节点及其子节点
     * <p>
     * 安全删除:
     * ✅ 避免孤儿节点残留
     * ✅ 重要节点删除需二次确认
     */
    public static void deleteDataContainSub() throws Exception {
        CuratorFramework client = getClient();
        client.delete().deletingChildrenIfNeeded().forPath("/case_2");
        getDataSub();
    }

    /**
     * 强制删除保障操作
     * <p>
     * 保障机制:
     * - guaranteed(): 服务端保证最终删除(即使网络闪断)
     * - inBackground(): 异步执行回调(解耦操作)
     */
    public static void mustBeDeleteSuccessfully() throws Exception {
        CuratorFramework client = getClient();
        client.delete().guaranteed().forPath("/app_10000000004");
        getDataSub();
    }

    /**
     * 异步删除回调处理
     * <p>
     * 异步场景:
     * - 非关键路径删除操作
     * - 需要与其他操作并行执行
     * <p>
     * 回调处理:
     * ✅ 记录操作日志
     * ✅ 发送通知事件
     */
    public static void executeAfterDeletion() throws Exception {
        CuratorFramework client = getClient();
        client.delete().guaranteed().inBackground((CuratorFramework framework, CuratorEvent event) -> {
            System.out.println("我被删除了");
            System.out.println("事件类型: " + event.getType());
            System.out.println("路径: " + event.getPath());
        }).forPath("/app_10000000004"); // 阻塞等待异步完成(演示用)
        getDataSub();
    }
}

Watch事件监听

ZooKeeper 允许用户在指定节点上注册一些Watcher,并且在一些特定事件触发的时候,ZooKeeper 服务端会将事件通知到感兴趣的客户端上去,该机制是 ZooKeeper 实现分布式协调服务的重要特性。

ZooKeeper 中引入了Watcher机制来实现了发布/订阅功能能,能够让多个订阅者同时监听某一个对象,当一个对象自身状态变化时,会通知所有订阅者。

zkCli客户端使用watch

添加 -w 参数可实时监听节点与子节点的变化,并且实时收到通知。非常适合保障分布式情况下的数据一致性。

其使用方式如下

  • Is -w path
    • 监听子节点的变化(增,删)[监听目录]
  • get -w path
    • 监听节点数据的变化
  • stat -w path
    • 监听节点属性的变化

Zookeeper事件类型

  • NodeCreated:节点创建

  • NodeDeleted:节点删除

  • NodeDataChanged:节点数据变化

  • NodeChildrenChanged:子节点列表变化

  • DataWatchRemoved:节点监听被移除

  • ChildWatchRemoved:子节点监听被移除

  • 一次性监听,监听节点数据的变化。 get -w path

  • 监听子节点的变化 Is -w path

相关推荐
bobz9658 分钟前
supervisord 的使用
后端
大道无形我有型9 分钟前
📖 Spring 事务机制超详细讲解(哥们专属)
后端
Re27510 分钟前
springboot源码分析--自动配置流程
spring boot·后端
Piper蛋窝13 分钟前
Go 1.2 相比 Go1.1 有哪些值得注意的改动?
后端·go
努力的搬砖人.16 分钟前
java爬虫案例
java·经验分享·后端
编程在手天下我有16 分钟前
深度剖析:架构评估的常用方法与应用
架构·软件开发·信息技术·架构评估
海风极客28 分钟前
一文搞懂JSON和HJSON
前端·后端·面试
南雨北斗29 分钟前
2.单独下载和配置PHP环境
后端
海风极客30 分钟前
一文搞懂Clickhouse的MySQL引擎
后端·面试·架构
都叫我大帅哥32 分钟前
遍历世界的通行证:迭代器模式的导航艺术
java·后端·设计模式