一:Apache Curator简介
1. Curator主要从以下几个方面降低了zk使用的复杂性
- 重试机制:提供可插拔的重试机制, 它将给捕获所有可恢复的异常配置一个重试策略,并且内部也提供了几种标准的重试策略(比如指数补偿)
- 连接状态监控: Curator初始化之后会一直的对zk连接进行监听, 一旦发现连接状态发生变化, 将作出相应的处理
- zk客户端实例管理:Curator对zk客户端到server集群连接进行管理.并在需要的情况, 重建zk实例,保证与zk集群的可靠连接
- 各种使用场景支持:Curator实现zk支持的大部分使用场景支持(甚至包括zk自身不支持的场景),这些实现都遵循了zk的最佳实践,并考虑了各种极端情况
2. Curator主要解决了三类问题
- 封装ZooKeeper client与ZooKeeper server之间的连接处理
- 提供了一套Fluent风格的操作API
- 提供ZooKeeper各种应用场景(recipe, 比如共享锁服务, 集群领导选举机制)的抽象封装
3. Curator声称的一些亮点
- 日志工具
- 内部采用SLF4J 来输出日志
- 采用驱动器(driver)机制, 允许扩展和定制日志和跟踪处理
- 提供了一个TracerDriver接口, 通过实现addTrace()和addCount()接口来集成用户自己的跟踪框架
- 和Curator相比, 另一个ZooKeeper客户端——zkClient的不足之处
- 文档几乎没有
- 异常处理弱爆了(简单的抛出RuntimeException)
- 重试处理太难用了
- 没有提供各种使用场景的实现
- 对ZooKeeper自带客户端(ZooKeeper类)的”抱怨”
- 只是一个底层实现
- 要用需要自己写大量的代码
- 很容易误用
- 需要自己处理连接丢失, 重试等
4. Curator几个组成部分
- Client: 是ZooKeeper客户端的一个替代品, 提供了一些底层处理和相关的工具方法
- Framework: 用来简化ZooKeeper高级功能的使用, 并增加了一些新的功能, 比如管理到ZooKeeper集群的连接, 重试处理
- Recipes: 实现了通用ZooKeeper的recipe, 该组件建立在Framework的基础之上
- Utilities:各种ZooKeeper的工具类
- Errors: 异常处理, 连接, 恢复等
- Extensions: recipe扩展
二:基本示例
1. 基本操作
public class CuratorTest {
static final String connectString = "127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183";
static final int sessionTimeoutMs = 5000;
static int connectionTimeoutMs = 3000;
public static void main(String[] args) throws Exception {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
// namespace: 命名空间,即根节点,当多个应用使用同一个zk时能够避免冲突, 操作时不需要显式使用该根命名空间(根节点)
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString(connectString)
.sessionTimeoutMs(sessionTimeoutMs)
.connectionTimeoutMs(connectionTimeoutMs)
.retryPolicy(retryPolicy)
.namespace("myapp")
.build();
// 连接
client.start();
String nodePath = "/node1";
Stat stat = client.checkExists().forPath(nodePath);
if (stat != null) {
// 只能删除叶子节点, version=-1 表示不需要校验版本,如果版本不对会抛出异常(BadVersion)
// version版本相同才会更新
client.setData().withVersion(-1).forPath(nodePath, "node1 new value".getBytes());
byte[] bytes = client.getData().forPath(nodePath);
System.out.println("node1=" + new String(bytes));
// 读取节点数据,获取该节点的Stat
Stat stat1 = new Stat();
client.getData().storingStatIn(stat1).forPath(nodePath);
System.out.println("stat1=" + stat1);
client.delete().withVersion(-1).forPath(nodePath);
} else {
client.create().forPath(nodePath, "node1 default value".getBytes());
}
// creatingParentContainersIfNeeded递归创建节点
String result = client.create()
.creatingParentContainersIfNeeded()
.withMode(CreateMode.PERSISTENT)
.withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)
.forPath("/node1/node11/node111", "node111 init value".getBytes());
System.out.println(result);
client.setData().forPath("/node1/node11/node111", "node111 new value".getBytes());
client.create().withMode(CreateMode.PERSISTENT)
.withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)
.inBackground()
.forPath(nodePath + "/node2", "node2 init value".getBytes());
client.getChildren().forPath(nodePath).forEach(node -> {
try {
String fullPath = nodePath + "/" + node;
System.out.println(node + " = " + new String(client.getData().forPath(fullPath)));
} catch (Exception e) {
e.printStackTrace();
}
});
// 删除当前节点和子节点
// guaranteed是一个保障措施只要客户端会话有效那么Curator会在后台持续进行删除操作,直到删除节点成功。
client.delete().guaranteed().deletingChildrenIfNeeded().forPath("/node1");
Thread.sleep(1000);
// 不存在则创建,存在则更新
client.create().orSetData().forPath("/node3", "node3 new value".getBytes());
client.close();
}
}
2. 事务操作
public class CuratorTest {
static final String connectString = "127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183";
static final int sessionTimeoutMs = 5000;
static int connectionTimeoutMs = 3000;
public static void main(String[] args) throws Exception {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
// namespace: 根节点,操作时不需要显式使用该根命名空间(根节点)
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString(connectString)
.sessionTimeoutMs(sessionTimeoutMs)
.connectionTimeoutMs(connectionTimeoutMs)
.retryPolicy(retryPolicy)
.namespace("myapp")
.build();
// 连接
client.start();
// 定义事务操作
CuratorOp createCuratorOp = client.transactionOp().create().forPath("/node1", "node1 init value".getBytes());
CuratorOp setCuratorOp = client.transactionOp().setData().forPath("/node1", "node1 new value".getBytes());
CuratorOp delCuratorOp = client.transactionOp().delete().forPath("/node1");
// 返回值:为每个操作的结果
List<CuratorTransactionResult> results = client.transaction().forOperations(createCuratorOp, setCuratorOp, delCuratorOp);
for (CuratorTransactionResult result: results) {
System.out.println("执行结果:" + result.getForPath() + "\t" + result.getType() + "\t" + result.getError() + "\t" + result.getResultStat());
}
client.close();
}
}
3.监听
方式一:全局监听
// 只有inBackground()才能被监听到
CuratorListener listener = new CuratorListener() {
@Override
public void eventReceived(CuratorFramework client, CuratorEvent event) {
System.out.println(event.getType() + "\t" + event.getPath());
}
};
client.getCuratorListenable().addListener(listener);
// inBackground 后台执行,即异步执行
client.create().inBackground().forPath("/node1", "node1 value".getBytes());
client.setData().inBackground().forPath("/node1", "node1 new value".getBytes());
client.delete().inBackground().forPath("/node1");
// WATCHED null
// CREATE /node1
// SET_DATA /node1
// DELETE /node1
// CLOSING null
方式二:监听单个操作
BackgroundCallback callback = new BackgroundCallback() {
@Override
public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
System.out.println(event.getType() + "\t" + event.getPath());
}
};
// inBackground 后台执行,即异步执行
client.create().inBackground(callback).forPath("/node1", "node1 value".getBytes());
client.setData().inBackground(callback).forPath("/node1", "node1 new value".getBytes());
client.delete().inBackground(callback).forPath("/node1");
// CREATE /node1
// SET_DATA /node1
// DELETE /node1
方式三:TreeCache
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.0.1</version>
</dependency>
NodeCache 好像只能监控删除节点和修改节点,没有监控创建节点
final NodeCache cache = new NodeCache(client, "/node1");
NodeCacheListener listener = () -> {
ChildData data = cache.getCurrentData();
if (null != data) {
System.out.println("路径=" + data.getPath() + "\t data=" + new String(data.getData()));
} else {
System.out.println("节点被删除!");
}
};
cache.getListenable().addListener(listener);
cache.start();
client.create().forPath("/node1", "node1 value".getBytes());
client.setData().forPath("/node1", "node1 new value".getBytes());
client.delete().forPath("/node1");
cache.close();
// 路径=/node1 data=node1 new value
// 节点被删除!
PathChildrenCache 好像不能监控修改节点
PathChildrenCache cache = new PathChildrenCache(client, "/node1", true);
cache.start();
PathChildrenCacheListener cacheListener = (client1, event) -> {
System.out.println("事件类型:" + event.getType());
if (null != event.getData()) {
System.out.println("节点数据:" + event.getData().getPath() + " = " + new String(event.getData().getData()));
}
};
cache.getListenable().addListener(cacheListener);
client.create().creatingParentsIfNeeded().forPath("/node1/node11", "node1 value".getBytes());
client.setData().forPath("/node1/node11", "node1 new value".getBytes());
client.delete().deletingChildrenIfNeeded().forPath("/node1");
cache.close();
// 事件类型:CONNECTION_RECONNECTED
// 事件类型:CHILD_ADDED
// 节点数据:/node1/node11 = node1 new value
// 事件类型:CHILD_REMOVED
// 节点数据:/node1/node11 = node1 new value
TreeCache = PathCache + NodeCache 创建、修改、删除都能监控到
TreeCacheListener cacheListener = (client1, event) -> {
System.out.println("事件类型:" + event.getType() +
"\t路径:" + (null != event.getData() ? event.getData().getPath() : null));
};
TreeCache cache = new TreeCache(client, "/node1");
cache.start();
cache.getListenable().addListener(cacheListener);
client.create().creatingParentsIfNeeded().forPath("/node1/node11", "node1 value".getBytes());
client.setData().forPath("/node1/node11", "node1 new value".getBytes());
client.delete().deletingChildrenIfNeeded().forPath("/node1");
Thread.sleep(1000);
cache.close();
// 事件类型:INITIALIZED 路径:null
// 事件类型:NODE_ADDED 路径:/node1
// 事件类型:NODE_ADDED 路径:/node1/node11
// 事件类型:NODE_UPDATED 路径:/node1/node11
// 事件类型:NODE_REMOVED 路径:/node1/node11
// 事件类型:NODE_REMOVED 路径:/node1
ConnectionStateListener
监听连接状态,当连接丢失时去重新连接
public class CuratorTest {
static final String connectString = "127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183";
static final int sessionTimeoutMs = 5000;
static int connectionTimeoutMs = 3000;
static CountDownLatch countDownLatch = new CountDownLatch(1);
static CuratorFramework client;
static ConnectionStateListener connectionStateListener = new ConnectionStateListener() {
@Override
public void stateChanged(CuratorFramework client, ConnectionState newState) {
if (newState == ConnectionState.CONNECTED) {
System.out.println("连接成功");
countDownLatch.countDown();
} else if (newState == ConnectionState.LOST) {
System.out.println("连接丢失");
try {
System.out.println("重新初始化开始");
reInitClient();
System.out.println("重新初始化完毕");
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
public static void main(String[] args) throws Exception {
initClient();
client.close();
}
public static void initClient() throws Exception {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
// namespace: 根节点,操作时不需要显式使用该根命名空间(根节点)
client = CuratorFrameworkFactory.builder()
.connectString(connectString)
.sessionTimeoutMs(sessionTimeoutMs)
.connectionTimeoutMs(connectionTimeoutMs)
.retryPolicy(retryPolicy)
.namespace("myapp")
.build();
// 连接
client.start();
client.getConnectionStateListenable().addListener(connectionStateListener);
countDownLatch.await();
}
public static void reInitClient() throws Exception {
// 先关闭client
if (client != null) {
client.close();
client = null;
}
// 然后再初始化客户端
initClient();
}
}
CuratorWatcher 好像只能监听到setData
CuratorWatcher pathWatcher = new CuratorWatcher() {
@Override
public void process(WatchedEvent event) throws Exception {
if (event.getType() == Watcher.Event.EventType.NodeDataChanged) {
String path = event.getPath();
String value = new String(client.getData().forPath(path));
System.out.println(path + "=" +value);
}
System.out.println(event);
}
};
String path = "/node1";
client.create().forPath(path, "node1 value".getBytes());
String value = new String(client.getData().usingWatcher(pathWatcher).forPath(path));
System.out.println(path + "=" + value);
client.create().forPath(path + "/node11", "node1 value".getBytes());
client.setData().forPath(path, "node1 new value".getBytes());
client.delete().deletingChildrenIfNeeded().forPath(path);
Thread.sleep(5000);
// node1=node1 value
// node1=node1 new value
// WatchedEvent state:SyncConnected type:NodeDataChanged path:/node1