Watch事件监听
curator客户端使用watch
- 一次性监听示例,实际上这种情况基本上不会使用。
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
- 只是监听某一个特定的节点
- 只是监听某一个特定的节点
- PathChildrenCache
- 监控一个ZNode的子节点
- 监控一个ZNode的子节点
- TreeCache:
- 可以监控整个树上的所有节点,类似于PathChildrenCache和NodeCache的组合
- 效果同上,差别在更换路径。
在curator(5)版本以下使用NodeCache、PathChildrenCache、TreeCache进行节点事件的监听,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默认方式
- 指定Id的方式
- auth已经认证通过的用户的方式
- 测试用户的权限,需要退出重新连接
- digest用户密码模式
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);
}
}