4-zookeeper分布式锁与集群(一)

157 阅读18分钟

Watch事件监听

curator客户端使用watch

  • 一次性监听示例,实际上这种情况基本上不会使用。
    • image.png
    • image.png
package com.dwl;

import org.apache.curator.framework.CuratorFramework;
import org.apache.zookeeper.Watcher;

import java.util.concurrent.CountDownLatch;

import static com.dwl.CuratorTest.getClient;
import static com.dwl.CuratorTest.shutdown;

/**
 * @ClassName CuratorWatchTest
 * @Description Zookeeper节点监听测试类(V2.0)
 * <p>
 * 功能特性:
 * <ul>
 *   <li>双模式监听:支持一次性监听与自动重连监听</li>
 *   <li>稳健性增强:集成连接状态监控与异常恢复机制</li>
 *   <li>资源管理:实现优雅关闭与连接释放</li>
 * </ul>
 * </p>
 * <p>
 * 核心技术:
 * <ol>
 *   <li>Curator watched() API:实现自动重连的监听机制</li>
 *   <li>CountDownLatch:实现线程阻塞控制</li>
 *   <li>双层监听架构:基础监听+事件驱动监听</li>
 * </ol>
 * </p>
 * @Version 1.0.0
 * @Date 2025
 * @Author By Dwl
 */
public class CuratorWatchTest {

    public static void main(String[] args) throws Exception {
        oneListener();
    }

    /**
     * 带优雅关闭的节点监听测试
     * <p>
     * 执行时序:
     * <pre>
     * 1. 初始化客户端连接
     * 2. 获取初始节点数据
     * 3. 注册自动重连监听器
     * 4. 阻塞等待事件触发
     * 5. 事件处理与监听重建
     * 6. 执行优雅关闭
     * </pre>
     * </p>
     * <p>
     * 实现要点:
     * <ul>
     *   <li>使用watched()实现监听自动恢复</li>
     *   <li>双阶段监听确保事件捕获可靠性</li>
     *   <li>CountDownLatch实现线程可控阻塞</li>
     * </ul>
     * </p>
     *
     * @throws Exception Zookeeper操作异常
     */
    public static void oneListener() throws Exception {
        // 初始化Curator客户端(含重试策略)
        final CuratorFramework client = getClient();
        final CountDownLatch eventLatch = new CountDownLatch(1);

        // 阶段1:获取初始数据
        byte[] initialData = client.getData()
            .watched()  // 启用自动重连监听
            .forPath("/case");
        System.out.printf("[INIT] 节点初始数据: %s%n", new String(initialData));

        // 阶段2:注册事件驱动监听器
        client.getData().usingWatcher((Watcher) watcher -> {
            System.out.printf("[EVENT] 收到事件: %s | 路径: %s | 状态: %s%n",
                              watcher.getType(),
                              watcher.getPath(),
                              watcher.getState());

            try {
                // 事件处理逻辑
                byte[] updatedData = client.getData()
                    .watched()  // 保持自动重连
                    .forPath("/case");
                System.out.printf("[UPDATE] 新数据: %s%n", new String(updatedData));

                // 触发关闭流程
                eventLatch.countDown();
            } catch (Exception e) {
                System.err.printf("[ERROR] 监听重建失败: %s%n", e.getMessage());
                // 生产环境建议添加指数退避重试
                // retryWithBackoff(() -> oneListener());
            }
        }).forPath("/case");

        // 阶段3:阻塞等待事件触发
        eventLatch.await();

        // 阶段4:执行清理操作
        shutdown();
        System.out.println("[INFO] 连接已优雅关闭");
    }

    /**
     * 推荐的生产环境重试策略(示例)
     * <p>
     * 实现特性:
     * <ul>
     *   <li>指数退避重试</li>
     *   <li>最大重试次数限制</li>
     *   <li>线程中断响应</li>
     * </ul>
     * </p>
     */
    private static void retryWithBackoff(Runnable task) {
        int maxRetries = 5;
        long baseDelay = 1000; // 1秒

        for (int attempt = 0; attempt < maxRetries; attempt++) {
            try {
                Thread.sleep(baseDelay * (long) Math.pow(2, attempt));
                task.run();
                return;
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException("重试被中断", e);
            } catch (Exception e) {
                if (attempt == maxRetries - 1) {
                    throw new RuntimeException("重试失败", e);
                }
            }
        }
    }

}

Curator事件监听机制

ZooKeeper原生支持通过注册Watcher来进行事件监听,但是其使用并不是特别方便需要开发人员自己反复注册Watcher,比较繁琐。

Curator引入了Cache来实现对ZooKeeper服务端事件的监听。

ZooKeeper提供了三种Watcher:

  • NodeCache
    • 只是监听某一个特定的节点
      • image.png
      • image.png
  • PathChildrenCache
    • 监控一个ZNode的子节点
      • image.png
      • image.png
  • TreeCache:
    • 可以监控整个树上的所有节点,类似于PathChildrenCache和NodeCache的组合
    • 效果同上,差别在更换路径。

在curator(5)版本以下使用NodeCachePathChildrenCacheTreeCache进行节点事件的监听,5版本以上使用新的CuratorCache类进行监听,代码演示的版本为(5.3.0)。

package com.dwl;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.cache.CuratorCache;
import org.apache.zookeeper.Watcher;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import static com.dwl.CuratorTest.getClient;
import static com.dwl.CuratorTest.shutdown;
import static org.apache.curator.framework.recipes.cache.CuratorCache.Options.SINGLE_NODE_CACHE;
import static org.apache.curator.framework.recipes.cache.CuratorCacheListener.Type.NODE_CREATED;
import static org.apache.curator.framework.recipes.cache.CuratorCacheListener.Type.NODE_DELETED;

/**
 * @ClassName CuratorWatchTest
 * @Description Zookeeper节点监听测试类(V2.1)
 * <p>
 * 功能特性:
 * <ul>
 *   <li>双模式监听:支持单节点监听与子树监听(需Curator 5.0+)</li>
 *   <li>稳健性增强:集成连接状态监控与指数退避重试机制</li>
 *   <li>资源管理:实现监听器生命周期管理及优雅关闭</li>
 * </ul>
 * </p>
 * <p>
 * 核心技术:
 * <ol>
 *   <li>Curator watched() API:实现自动重连的监听机制</li>
 *   <li>事件去重:内置连续事件合并处理(5.x+)</li>
 *   <li>资源监控:集成连接状态监听与异常恢复策略</li>
 * </ol>
 * </p>
 * @Version 1.0.0
 * @Date 2025
 * @Author By Dwl
 * @see <a href="https://curator.apache.org/">Apache Curator官方文档</a>
 */
public class CuratorWatchTest {

    public static void main(String[] args) throws Exception {
//        oneListener();
//        curatorCacheTest();
//        pathChildrenCache("/node_cache");
        pathChildrenCache("/");
    }

    /**
     * 持久化监听测试(含自动恢复)
     * <p>
     * 执行流程:
     * <pre>
     * 1. 初始化Curator客户端(带重试策略)
     * 2. 获取节点初始数据(带自动重连监听)
     * 3. 注册事件驱动监听器(双阶段处理)
     * 4. 阻塞等待事件触发
     * 5. 事件处理与监听重建
     * 6. 执行优雅关闭
     * </pre>
     * </p>
     * <p>
     * 技术要点:
     * <ul>
     *   <li>使用watched()API实现监听自动恢复(5.x+)</li>
     *   <li>双层监听架构:基础监听+事件驱动监听</li>
     *   <li>CountDownLatch实现线程可控阻塞</li>
     * </ul>
     * </p>
     */
    public static void oneListener() throws Exception {
        // 初始化Curator客户端(含指数退避重试策略)
        final CuratorFramework client = getClient();
        final CountDownLatch eventLatch = new CountDownLatch(1);

        // 阶段1:获取初始数据(带自动重连)
        var ref = new Object() {
            byte[] initialData = client.getData()
                    .watched()  // 启用自动重连监听(5.x+)
                    .forPath("/case");
        };
        System.out.printf("[INIT] 节点初始数据: %s%n", new String(ref.initialData));

        // 阶段2:注册事件驱动监听器(双阶段处理)
        client.getData().usingWatcher((Watcher) watcher -> {
            System.out.printf("[EVENT] 收到事件: %s | 路径: %s | 状态: %s%n",
                    watcher.getType(),
                    watcher.getPath(),
                    watcher.getState());

            try {
                // 事件处理逻辑(带数据对比)
                byte[] updatedData = client.getData()
                        .watched()  // 关键点2:保持自动重连
                        .forPath("/case");

                // 数据变更验证
                if (!new String(updatedData).equals(new String(ref.initialData))) {
                    System.out.printf("[UPDATE] 新数据: %s%n", new String(updatedData));
                    ref.initialData = updatedData; // 更新基准数据
                }

                // 触发关闭流程
                eventLatch.countDown();
            } catch (Exception e) {
                System.err.printf("[ERROR] 监听重建失败: %s%n", e.getMessage());
                retryWithBackoff(() -> {
                    try {
                        oneListener();
                    } catch (Exception ex) {
                        throw new RuntimeException(ex);
                    }
                }); // 生产级重试
            }
        }).forPath("/case");

        // 阶段3:阻塞等待事件触发(带超时控制)
        if (!eventLatch.await(30, TimeUnit.SECONDS)) {
            System.err.println("[WARN] 监听超时,检查节点状态");
        }

        // 阶段4:执行清理操作(含连接验证)
        if (client.getZookeeperClient().blockUntilConnectedOrTimedOut()) {
            shutdown();
            System.out.println("[INFO] 连接已优雅关闭");
        } else {
            System.err.println("[ERROR] 连接异常,强制关闭");
        }
    }

    /**
     * 节点监听测试(CuratorCache实现)
     * <p>
     * 功能说明:
     * <ul>
     *   <li>监听指定路径的节点变化(创建/删除/数据变更)</li>
     *   <li>支持事件类型过滤与数据对比</li>
     *   <li>集成资源自动回收机制</li>
     * </ul>
     * <p>
     * 技术参数:
     * <table border="1">
     *   <tr><th>参数</th><th>说明</th></tr>
     *   <tr><td>SINGLE_NODE_CACHE</td><td>仅监听目标节点(非递归)</td></tr>
     * </table>
     * </p>
     */
    public static void curatorCacheTest() throws InterruptedException {
        // 1. 初始化Curator客户端(自动重试策略)
        CuratorFramework client = getClient();
        final CountDownLatch eventLatch = new CountDownLatch(3);

        // 构建CuratorCache(监听单节点)
        CuratorCache cache = CuratorCache.build(client, "/node_cache", SINGLE_NODE_CACHE);

        // 3. 注册智能事件过滤器(增强型监听器)
        cache.listenable().addListener((type, oldData, data) -> {
            System.out.println(" === 事件详情 ===");
            // 事件类型映射(增强可读性)
            String eventType = switch (type) {
                case NODE_CREATED -> "节点创建";
                case NODE_DELETED -> "节点删除";
                case NODE_CHANGED -> "数据变更";
            };

            // 数据展示增强(处理空值情况)
            String nodePath = data != null ? data.getPath() : oldData.getPath();
            String dataStr = (data != null && data.getData() != null)
                    ? new String(data.getData())
                    : "NULL";

            System.out.printf("事件类型: %s%n路径: %s%n数据: %s%n",
                    eventType, nodePath, dataStr);

            // 4. 事件处理逻辑(示例:仅处理数据变更)
            if (type == NODE_CREATED) {
                eventLatch.countDown();
            }
        });

        // 5. 启动缓存监听(带状态监控)
        try {
            cache.start();
            System.out.println("[INFO] 监听器已启动,路径: /node_cache");
            System.out.println("[INFO] 监听范围:当前节点");
        } catch (Exception e) {
            System.err.printf("[ERROR] 缓存启动失败: %s%n", e.getMessage());
            cache.close(); // 确保异常时释放资源
            throw e;
        }

        // 6. 阻塞等待事件(增强型等待)
        try {
            if (!eventLatch.await(180, TimeUnit.SECONDS)) {
                System.err.println("[WARN] 监听超时,检查节点状态");
            }
        } finally {
            cache.close();
            shutdown();
        }
    }


    /**
     * 子节点监听测试(PathChildrenCache模式)
     * <p>
     * 功能说明:
     * <ul>
     *   <li>监听指定路径的直接子节点变化</li>
     *   <li>支持三种核心事件类型处理</li>
     *   <li>集成连接状态监控</li>
     * </ul>
     * <p>
     * 执行流程:
     * <pre>
     * 1. 初始化Curator客户端
     * 2. 构建PathChildrenCache监听器
     * 3. 注册事件处理器(带事件分流)
     * 4. 启动监听服务
     * 5. 阻塞等待事件触发
     * 6. 执行资源回收
     * </pre>
     * </p>
     */
    public static void pathChildrenCache(String path) throws InterruptedException {
        // 1. 初始化Curator客户端(自动重试策略)
        CuratorFramework client = getClient();
        final CountDownLatch eventLatch = new CountDownLatch(10); // 示例需要3个事件

        // 2. 构建单节点缓存监听器(参数说明:监听路径,缓存模式)
        CuratorCache cache = CuratorCache.build(client, path);

        // 3. 注册智能事件处理器(带事件分流)
        cache.listenable().addListener((type, oldData, data) -> {
            System.out.println(" === 子节点事件 ===");
            // 事件类型映射
            String eventType = switch (type) {
                case NODE_CREATED -> "节点创建";
                case NODE_DELETED -> "节点删除";
                case NODE_CHANGED -> "数据变更";
                default -> "未知事件";
            };

            String nodePath = data != null ? data.getPath() : oldData.getPath();
            String dataStr = (data != null && data.getData() != null)
                    ? new String(data.getData())
                    : "NULL";

            System.out.printf("事件类型: %s%n路径: %s%n数据: %s%n",
                    eventType, nodePath, dataStr);

            // 4. 事件处理逻辑
            if (type.equals(NODE_CREATED)) {
                System.out.println("添加子节点创建处理逻辑");
            } else if (type.equals(NODE_DELETED)) {
                System.out.println("添加子节点删除处理逻辑");
            } else {
                System.out.println("添加子节点数据变更处理逻辑");
            }

            eventLatch.countDown(); // 触发事件完成信号
        });

        // 5. 启动缓存监听(带异常处理)
        try {
            cache.start();
            System.out.println("[INFO] 子节点监听器已启动,路径: /node_cache");
        } catch (Exception e) {
            System.err.printf("[ERROR] 缓存启动失败: %s%n", e.getMessage());
            cache.close(); // 确保异常时释放资源
            throw e;
        }

        // 6. 阻塞等待事件(增强型超时控制)
        try {
            if (!eventLatch.await(180, TimeUnit.SECONDS)) {
                System.err.println("[WARN] 监听超时,检查子节点状态");
                // 生产环境建议添加超时后的自动重试逻辑
            }
        } finally {
            cache.close();
            shutdown();
        }
    }

    /**
     * 生产级重试策略(指数退避)
     * <p>
     * 策略参数:
     * <ul>
     *   <li>最大重试次数:5次</li>
     *   <li>初始延迟:1秒</li>
     *   <li>最大延迟:16秒</li>
     *   <li>异常过滤:仅重试可恢复异常</li>
     * </ul>
     * </p>
     */
    private static void retryWithBackoff(Runnable task) {
        int maxAttempts = 5;
        long initialDelay = 1000; // 1秒

        for (int attempt = 1; attempt <= maxAttempts; attempt++) {
            try {
                Thread.sleep(initialDelay * (long) Math.pow(2, attempt - 1));
                task.run();
                return;
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException("重试被中断", e);
            } catch (Exception e) {
                if (attempt == maxAttempts) {
                    throw new RuntimeException("重试失败(次数:" + maxAttempts + ")", e);
                }
                System.err.printf("[WARN] 重试(%d/%d)失败: %s%n",
                        attempt, maxAttempts, e.getMessage());
            }
        }
    }

}

事务&异步操作演示

事务

CuratorFramework 的实例包含 inTransaction()接口方法,调用此方法开启一个 ZooKeeper 事务。 可以复合create、 setData、 check、and/or delete 等操作然后调用 commit 作为一个原子操作提交。

package com.dwl;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.api.transaction.CuratorOp;
import org.apache.curator.framework.api.transaction.CuratorTransactionResult;
import org.apache.curator.framework.api.transaction.OperationType;

import java.util.ArrayList;
import java.util.List;

import static com.dwl.CuratorTest.getClient;
import static com.dwl.CuratorTest.shutdown;


/**
 * @ClassName CuratorTransactionTest
 * @Description Zookeeper事务操作测试类
 * <p>
 * 功能说明:
 * <ul>
 *   <li>演示Curator事务API的使用方法</li>
 *   <li>实现原子性节点操作(创建/更新/删除)</li>
 *   <li>验证事务的ACID特性</li>
 * </ul>
 * <p>
 * 技术特性:
 * <ol>
 *   <li>原子性:所有操作要么全部成功,要么全部失败</li>
 *   <li>一致性:保证节点状态符合事务预期</li>
 *   <li>隔离性:事务执行期间其他操作不可见</li>
 *   <li>持久性:成功事务结果永久保存</li>
 * </ol>
 * @Version 1.0.0
 * @Date 2025
 * @Author By Dwl
 * @see <a href="https://curator.apache.org/apidocs/org/apache/curator/framework/api/transaction/package-summary.html">Curator事务API</a>
 */
public class CuratorTransactionTest {

    public static void main(String[] args) throws Exception {
        transactionTest();
    }

    /**
     * 事务操作测试方法
     * <p>
     * 执行流程:
     * <pre>
     * 1. 初始化Curator客户端(带重试策略)
     * 2. 构建事务操作集:
     *    - 创建节点/case(数据:"test")
     *    - 更新节点/case数据为"ca_test"
     *    - 删除节点/case_2(若存在)
     * 3. 提交事务并获取执行结果
     * 4. 解析事务结果状态
     * 5. 执行资源回收
     * </pre>
     * </p>
     * <p>
     * 事务特性验证:
     * <ul>
     *   <li>节点/case的最终状态取决于所有操作是否全部成功</li>
     *   <li>若任意操作失败,整个事务将回滚</li>
     * </ul>
     *
     * @throws Exception Zookeeper操作异常
     */
    public static void transactionTest() throws Exception {
        // 1. 初始化Curator客户端(自动重试策略)
        CuratorFramework client = getClient();

        try {
            // 2. 构建事务操作集(原子操作集合)
            List<CuratorOp> operations = new ArrayList<>() {{
                // 创建节点操作(如果节点不存在)
                add(client.transactionOp().create().forPath("/case", "test".getBytes()));
                // 更新节点数据操作(如果节点存在)
                add(client.transactionOp().setData().forPath("/case", "ca_test".getBytes()));
                // 删除节点操作(如果节点存在)
                add(client.transactionOp().delete().forPath("/case_2"));
            }};

            // 3. 提交事务(原子性执行)
            List<CuratorTransactionResult> results = client.transaction().forOperations(operations);

            // 4. 解析事务结果
            System.out.println("=== 事务执行结果 ===");
            for (CuratorTransactionResult result : results) {
                System.out.printf("操作路径: %s%n", result.getForPath());
                System.out.printf("操作类型: %s%n", getOperationType(result.getType()));
                System.out.printf("执行状态: %s%n%n", result.getError());
            }

            // 5. 验证最终节点状态
            verifyFinalState(client);

        } finally {
            // 6. 资源回收(含连接状态验证)
            if (client.getZookeeperClient().blockUntilConnectedOrTimedOut()) {
                shutdown();
                System.out.println("[INFO] 连接已优雅关闭");
            } else {
                System.err.println("[ERROR] 连接异常,强制关闭");
            }
        }
    }

    /**
     * 解析事务操作类型
     *
     * @param type 事务操作类型枚举
     * @return 可读的操作类型描述
     */
    private static String getOperationType(OperationType type) {
        return switch (type) {
            case CREATE -> "CREATE";
            case SET_DATA -> "SET_DATA";
            case DELETE -> "DELETE";
            default -> "UNKNOWN";
        };
    }

    /**
     * 验证最终节点状态
     *
     * @param client Curator客户端实例
     * @throws Exception Zookeeper操作异常
     */
    private static void verifyFinalState(CuratorFramework client) throws Exception {
        System.out.println("=== 最终节点状态验证 ===");
        // 检查/case节点状态
        if (client.checkExists().forPath("/case") != null) {
            byte[] data = client.getData().forPath("/case");
            System.out.printf("/case 节点数据: %s%n", new String(data));
        } else {
            System.out.println("/case 节点不存在");
        }

        // 检查/case_2节点状态
        if (client.checkExists().forPath("/case_2") != null) {
            System.err.println("[WARN] /case_2 节点未被成功删除");
        } else {
            System.out.println("/case_2 节点已删除");
        }
    }
}

异步操作演示

package com.dwl;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.api.BackgroundCallback;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.Watcher;

import java.util.concurrent.CountDownLatch;

import static com.dwl.CuratorTest.getClient;
import static com.dwl.CuratorTest.shutdown;

/**
 * @ClassName CuratorAsyncTest
 * @Description <h2>Curator 异步监听节点数据示例</h2>
 * <p>演示如何通过 Apache Curator 异步获取 ZooKeeper 节点数据,并通过回调机制处理结果。</p>
 * <p><b>注意:</b>本示例为一次性监听,若需持续监听节点变化,需结合 {@link Watcher} 实现。</p>
 * @Version 1.0.0
 * @Date 2025
 * @Author By Dwl
 */
public class CuratorAsyncTest {


    public static void main(String[] args) throws Exception {
        startWatching();
    }

    /**
     * <h3>启动异步监听方法</h3>
     * <p>执行异步获取节点数据操作,通过回调处理结果,使用 CountDownLatch 实现线程同步。</p>
     *
     * @throws Exception 可能抛出的中断异常或 ZooKeeper 操作异常
     */
    private static void startWatching() throws Exception {

        CuratorFramework client = getClient();
        /* 创建 CountDownLatch 用于阻塞主线程,等待异步回调完成
         * 计数器初始值为1:表示需要等待1个异步事件完成 */
        CountDownLatch eventLatch = new CountDownLatch(1);

        /*
         * <h4>定义异步回调处理器</h4>
         * <p>实现 {@link BackgroundCallback} 接口,处理异步操作结果</p>
         * <p><b>回调触发条件:</b></p>
         * <ul>
         *   <li>节点数据成功获取时({@code KeeperException.Code.OK}</li>
         *   <li>节点不存在、权限不足、连接丢失等异常时</li>
         * </ul>
         *
         * <p><b>潜在问题:</b></p>
         * <ul>
         *   <li>当 {@code event.getData()} 返回 null 时(如节点无数据),直接操作可能引发 NPE</li>
         *   <li>未处理会话过期({@code KeeperException.SessionExpiredException})等严重异常</li>
         * </ul>
         */
        BackgroundCallback callback = (curatorClient, event) -> {
            // 获取 ZooKeeper 操作结果状态码
            int resultCode = event.getResultCode();

            // 根据状态码处理不同结果
            if (resultCode == KeeperException.Code.OK.intValue()) {
                // 成功获取数据:提取字节数组并转换为字符串
                byte[] data = event.getData();
                // 防御性检查:防止 data 为 null 导致 NPE
                String dataStr = (data != null) ? new String(data) : "NULL_DATA";
                System.out.println("Async Data: " + dataStr);

                // 计数器减1,释放等待线程
                eventLatch.countDown();
            } else {
                // 错误处理分支
                System.err.println("Error Code: " + resultCode);

                // 注意:此处未调用 countDown(),若需处理错误后继续执行,需手动释放锁
            }
        };

        try {
            /*
             * <h4>触发异步操作</h4>
             * 调用 getData() 方法获取节点数据
             * 链式调用说明:
             * 1. inBackground(callback):指定异步回调处理器
             * 2. forPath("/my/node"):指定目标节点路径
             * <b>注意:</b>若节点不存在,将触发 KeeperException.NoNodeException */
            client.getData()
                    .inBackground(callback)  // 设置异步回调
                    .forPath("/my/node");    // 目标节点路径

        } catch (Exception e) {
            // 捕获同步调用异常(如路径非法)
            System.err.println("Synchronous operation failed: " + e.getMessage());
            eventLatch.countDown(); // 确保计数器释放
        }
        /*
         * <h4>线程同步控制</h4>
         * 主线程在此处阻塞,直到异步回调调用 countDown()
         * 超时机制缺失:若异步操作永久挂起,主线程将无限等待
         * 生产环境建议添加超时控制:
         * boolean completed = eventLatch.await(30, TimeUnit.SECONDS);*/
        eventLatch.await();

        // 可选:关闭客户端连接(根据业务需求决定)
        shutdown();
    }


}

Zookeeper权限控制

zk权限控制介绍

Zookeeper作为一个分布式协调框架,内部存储了一些分布式系统运行时的状态的数据,比如master选举、比如分布式锁。

对这些数据的操作会直接影响到分布式系统的运行状态。

因此,为了保证zookeeper中的数据的安全性,避免误操作带来的影响。

Zookeeper提供了一套ACL权限控制机制来保证数据的安全。

  • ACL权限控制,使用:scheme:id:perm来标识。
    • Scheme(权限模式),标识授权策略
    • ID(授权对象)
    • Permission:授予的权限

ZooKeeper的权限控制是基于每个znode节点的,需要对每个节点设置权限,每个znode支持设置多种权限控制方案和多个权限,子节点不会继承父节点的权限,客户端无权访问某节点,但可能可以访问它的子节点。

Scheme 权限模式

Zookeeper提供以下权限模式,所谓权限模式,就是使用什么样的方式来进行授权。

  • world: 默认方式,相当于全部都能访问
  • auth: 代表已经认证通过的用户
    • cli中可以通过 addauth digest user:pwd 来添加当前上下文中的授权用户。
  • digest: 即用户名:密码这种方式认证,这也是业务系统中最常用的。
    • 用 username:password 字符串来产生一个MD5串,然后该串被用来作为ACLID。
    • 认证是通过明文发送username:password 来进行的,当用在ACL时,表达式为username:base64,base64是password的SHA1摘要的编码。
  • ip: 通过ip地址来做权限控制
    • 比如 ip:192.168.1.1 表示权限控制都是针对这个ip地址的。
    • 也可以针对网段 ip:192.168.1.1/24,此时addr中的有效位与客户端addr中的有效位进行比对。

ID 授权对象

指权限赋予的用户或一个指定的实体,不同的权限模式下,授权对象不同。

Id ipId = new Id("ip",“192.168.58.100");
Id ANYONE_ID_UNSAFE = new Id("world","anyone");

Permission权限类型

指通过权限检查后可以被允许的操作 create/delete/read/write/admin

  • Create 允许对子节点 Create 操作
  • Read 允许对本节点 GetChildren 和 GetData 操作
  • Write 允许对本节点 SetData 操作
  • Delete 允许对子节点 Delete 操作
  • Admin 允许对本节点 setAcl 操作

权限模式(Schema)和授权对象主要用来确认权限验证过程中使用的验证策略

比如ip地址、digest:username:password,匹配到验证策略并验证成功后,再根据权限操作类型来决定当前客户端的访问权限。

在控制台实现操作

在Zookeeper中提供了ACL相关的命令

getAcl <path> 读取ACL权限
setAcl <path> <acl> 设置ACL权限
addauth <scheme> <auth> 添加认证用户
  • world默认方式
    • image.png
  • 指定Id的方式
    • image.png
    • image.png
  • auth已经认证通过的用户的方式
    • image.png
    • 测试用户的权限,需要退出重新连接
    • image.png
    • image.png
  • digest用户密码模式
    • image.png
package com.dwl.util;

import lombok.extern.slf4j.Slf4j;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

/**
 * @ClassName HashUtil
 * @Description 哈希工具类
 * <p>
 * 提供常见哈希算法(如SHA系列)的生成工具方法,并支持Base64编码转换
 * 注意:当前示例仅演示SHA1算法,实际使用应根据安全需求选择合适算法
 * @Version 1.0.0
 * @Date 2025
 * @Author By Dwl
 */
@Slf4j
public class HashUtil {

    /**
     * 示例程序:演示字符串的SHA1哈希计算及Base64编码
     * <p>
     * 输入字符串格式:"用户名:密码",当前示例使用硬编码值"dwl:dwl"
     * 实际生产环境中应通过安全的方式获取敏感信息
     *
     * @throws NoSuchAlgorithmException 当指定算法不可用时抛出(SHA1在JDK中默认可用)
     */
    public static void main(String[] args) throws NoSuchAlgorithmException {
        // 待哈希的原始字符串(示例使用明文密码,实际应使用加盐哈希)
        String user = "dwl:dwl";

        // 生成SHA1哈希摘要
        MessageDigest sha1Digest = MessageDigest.getInstance("SHA1");
        byte[] digest = sha1Digest.digest(user.getBytes());

        // 转换为Base64编码字符串
        String pw = Base64.getEncoder().encodeToString(digest);

        log.info("生成的哈希值: {}", pw);
    }

}

通过curator的方式进行认证

package com.dwl;

import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.state.ConnectionStateListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;

import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static org.apache.curator.framework.imps.CuratorFrameworkState.STARTED;

/**
 * @ClassName CuratorAclTest
 * @Description ZooKeeper ACL权限测试类(增强版)
 * <p>
 * 功能说明:
 * 1. 演示带认证的ZooKeeper节点创建和数据操作
 * 2. 展示ACL权限控制的实际应用
 * 3. 包含客户端连接状态监控和异常处理
 * <p>
 * 核心组件:
 * - Curator客户端:使用构建器模式配置连接参数
 * - DIGEST认证:采用SHA1+Base64的认证机制
 * - ACL控制:定义节点访问权限列表
 * <p>
 * 安全警告:
 * ⚠️ 生产环境必须:
 * 1. 使用TLS加密通信
 * 2. 避免硬编码凭据(应从Vault等安全存储获取)
 * 3. 使用更安全的SCRAM-SHA-256认证机制
 * @Version 1.0.0
 * @Date 2025
 * @Author By Dwl
 */
@Slf4j
public class CuratorAclTest {

    // 使用明确的常量定义替代魔法值
    private static final String CONNECT_STRING = "127.0.0.1:2181";
    private static final int SESSION_TIMEOUT_MS = 15_000;
    private static final int CONNECTION_TIMEOUT_MS = 10_000;
    private static final String AUTH_SCHEME = "digest";
    private static final String AUTH_SECRET = "dwl:dwl";
    private static final String NAMESPACE = "acl_code";
    private static final String NODE_PATH = "/secure_auth";
    private static final byte[] NODE_DATA = "sensitive_data".getBytes(StandardCharsets.UTF_8);

    // 使用线程安全的LazyHolder模式初始化客户端
    private static final CuratorFramework CLIENT_ACL = LazyHolder.CLIENT;

    // 静态内部类实现延迟初始化
    private static class LazyHolder {
        static final CuratorFramework CLIENT = buildCuratorClient();
    }

    /**
     * 构建配置完整的Curator客户端
     *
     * @return 配置完成的CuratorFramework实例
     */
    private static CuratorFramework buildCuratorClient() {
        return CuratorFrameworkFactory.builder()
                .connectString(CONNECT_STRING)
                .sessionTimeoutMs(SESSION_TIMEOUT_MS)
                .connectionTimeoutMs(CONNECTION_TIMEOUT_MS)
                .retryPolicy(new ExponentialBackoffRetry(1000, 3, 15000))
                .authorization(AUTH_SCHEME, AUTH_SECRET.getBytes(StandardCharsets.UTF_8))
                .namespace(NAMESPACE)
                .build();
    }

    static {
        initializeClient();
    }

    /**
     * 初始化ZooKeeper客户端并配置监听器
     *
     * @throws RuntimeException 如果初始化失败
     */
    private static void initializeClient() {
        try {
            CLIENT_ACL.getConnectionStateListenable().addListener(createConnectionListener());
            CLIENT_ACL.start();

            if (!CLIENT_ACL.blockUntilConnected(SESSION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
                throw new RuntimeException("❌ ZooKeeper连接超时,初始化失败");
            }

            log.info("✅ ZooKeeper客户端初始化完成。");

        } catch (Exception e) {
            handleInitializationFailure(e);
        }
    }

    /**
     * 创建连接状态监听器
     *
     * @return 配置好的ConnectionStateListener实例
     */
    private static ConnectionStateListener createConnectionListener() {
        return (client, newState) -> {
            try {
                String message = String.format("🔗 连接状态变更 [%s] → %s",
                        client.getZookeeperClient().getZooKeeper().getState(),
                        newState);

                log.warn(message);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }

            switch (newState) {
                case SUSPENDED -> log.warn("⚠️ 连接挂起 - 可能发生网络波动");
                case RECONNECTED -> log.info("✅ 连接恢复 - 自动重连成功");
                case LOST -> log.error("❗️ 会话丢失 - 需要重新认证");
                case READ_ONLY -> log.warn("ℹ️ 只读模式 - 临时分区容错状态");
            }
        };
    }


    public static void main(String[] args) throws Exception {
        configureAclNode();
    }

    /**
     * 配置带ACL权限的ZooKeeper节点
     *
     * @throws Exception 包含所有可能的操作异常
     */
    public static void configureAclNode() throws Exception {
        // 1. 创建认证标识(DIGEST算法)
        Id authId = createDigestIdentity(AUTH_SECRET);

        // 2. 配置ACL权限列表
        List<ACL> aclList = createAclList(authId);

        // 3. 执行节点操作(带完整异常处理)
        executeNodeOperations(aclList);
    }

    /**
     * 创建DIGEST认证标识
     *
     * @param secret 用户凭据(格式:username:password)
     * @return 生成的Id对象
     */
    private static Id createDigestIdentity(String secret) {
        try {
            String digest = DigestAuthenticationProvider.generateDigest(secret);
            log.debug("🔑 生成DIGEST凭证: {}", digest);
            return new Id("digest", digest);
        } catch (NoSuchAlgorithmException e) {
            throw new SecurityException("❌ DIGEST算法不可用", e);
        }
    }

    /**
     * 创建ACL权限列表
     *
     * @param authId 认证标识
     * @return 配置好的ACL列表
     */
    private static List<ACL> createAclList(Id authId) {
        List<ACL> aclList = new ArrayList<>();
        aclList.add(new ACL(
                ZooDefs.Perms.ALL,       // 允许所有操作
                authId                   // 绑定认证标识
        ));
        log.debug("📜 创建ACL列表: {}", aclList);
        return aclList;
    }

    /**
     * 执行节点操作流程
     *
     * @param aclList ACL列表
     */
    private static void executeNodeOperations(List<ACL> aclList) throws Exception {
        try (CuratorFramework client = CLIENT_ACL) {
            // 验证客户端状态
            validateClientState(client);

            // 创建持久化节点
            String createdPath = createSecureNode(client, aclList);
            log.info("📂 节点创建成功: {}", createdPath);

            // 读取节点数据
            readNodeData(client, createdPath);
        }
    }

    /**
     * 验证客户端连接状态
     *
     * @param client Curator客户端实例
     */
    private static void validateClientState(CuratorFramework client) {
        if (client.getState() != STARTED) {
            throw new IllegalStateException("❌ 客户端未连接");
        }
    }

    /**
     * 创建安全节点
     *
     * @param client  Curator客户端
     * @param aclList ACL权限列表
     * @return 创建的节点路径
     */
    private static String createSecureNode(CuratorFramework client, List<ACL> aclList) throws Exception {
        return client.create()
                .creatingParentsIfNeeded()
                .withMode(CreateMode.PERSISTENT)
                .withACL(aclList)
                .forPath(NODE_PATH, NODE_DATA);
    }

    /**
     * 读取节点数据
     *
     * @param client Curator客户端
     * @param path   节点路径
     */
    private static void readNodeData(CuratorFramework client, String path) throws Exception {
        byte[] data = client.getData()
                .forPath(path);
        log.info("📖 读取数据成功: {}", new String(data, StandardCharsets.UTF_8));
    }

    /**
     * 处理初始化失败异常
     *
     * @param e 异常对象
     */
    private static void handleInitializationFailure(Exception e) {
        String errorMsg = String.format("🚨 初始化失败: %s", e.getMessage());
        log.error(errorMsg, e);
        System.err.println(errorMsg);
        System.exit(1);
    }
}