Curator 操作 ZooKeeper
Curator包含几个包:
curator-framework:对zookeeper的底层api的一些封装
curator-client:提供一些客户端的操作,例如重试策略等
curator-recipes:封装一些高级特性,如:Cache事件监听、选举、分布式锁、分布式计数器、分布式Barrier等
zk 客户端对比:

pom依赖
<dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>4.0.0</version> <exclusions> <exclusion> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>4.0.0</version> <exclusions> <exclusion> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.9</version> </dependency>
创建会话
1.使用静态工程方法创建客户端
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); CuratorFramework client =CuratorFrameworkFactory.newClient(connectionInfo,5000,3000,retryPolicy);
newClient静态工厂方法包含四个主要参数:

Curator 框架通过 CuratorFrameworkFactory 以工厂模式和 builder 模式创建 CuratorFramework 实例。CuratorFramework 实例是线程安全的,工厂方法 newClient() 提供一个简单方式创建实例。而 Builder 提供了更多的参数控制。创建了一个 CuratorFramework 实例,必须调用它的 start() 启动,在应用退出时调用 close() 方法关闭。
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); CuratorFramework client = CuratorFrameworkFactory.builder() .connectString(connectionInfo) .sessionTimeoutMs(5000) .connectionTimeoutMs(5000) .retryPolicy(retryPolicy) .build();
3.创建包含隔离命名空间的会话
实现不同的Zookeeper业务之间的隔离,需要为每个业务分配一个独立的命名空间(NameSpace),即指定一个Zookeeper的根路径。例如当客户端指定独立命名空间为“/base”,那么该客户端对Zookeeper上的数据节点的操作都是基于该目录进行的。通过设置Chroot(命名空间)可以将客户端应用与Zookeeper服务端的一课子树相对应,在多个应用共用一个Zookeeper集群的场景下,这对于实现不同应用之间的相互隔离十分有意义。
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); CuratorFramework client = CuratorFrameworkFactory.builder().connectString(connectionInfo) .sessionTimeoutMs(5000) .connectionTimeoutMs(5000) .retryPolicy(retryPolicy) .namespace("base") .build();
启动客户端
当创建会话成功,得到client的实例然后可以直接调用其start( )方法:
client.start();
创建数据节点
Zookeeper的节点创建模式:
- PERSISTENT:持久化
- PERSISTENT_SEQUENTIAL:持久化并且带序列号
- EPHEMERAL:临时
- EPHEMERAL_SEQUENTIAL:临时并且带序列号
**创建一个节点,初始内容为空 **
client.create().forPath("path"); //注意:如果没有设置节点属性,节点创建模式默认为持久化节点,内容默认为空
创建一个节点,附带初始化内容
client.create().forPath("path","init".getBytes());
创建一个节点,指定创建模式(临时节点),内容为空
client.create().creatingParentsIfNeeded() // 若创建节点的父节点不存在则先创建父节点再创建子节点
.withMode(CreateMode.PERSISTENT) // 创建的是持久节点
.withACL(Ids.OPEN_ACL_UNSAFE) // 默认匿名权限,权限scheme id:'world,'anyone,:cdrwa
.forPath("/search/business/test","1".getBytes());
创建一个节点,指定创建模式(临时节点),附带初始化内容
client.create().withMode(CreateMode.EPHEMERAL).forPath("path","init".getBytes());
创建一个节点,指定创建模式(临时节点),附带初始化内容,并且自动递归创建父节点
client.create().creatingParentContainersIfNeeded().withMode(CreateMode.EPHEMERAL).forPath("path","init".getBytes());
creatingParentContainersIfNeeded()接口非常有用,因为一般情况开发人员在创建一个子节点必须判断它的父节点是否存在,如果不存在直接创建会抛出NoNodeException,使用creatingParentContainersIfNeeded()之后Curator能够自动递归创建所有所需的父节点。
删除一个节点
client.delete().forPath("path"); //注意,此方法只能删除叶子节点,否则会抛出异常。
删除一个节点,并且递归删除其所有的子节点
client.delete().deletingChildrenIfNeeded().forPath("path");
删除一个节点,强制指定版本进行删除
client.delete().withVersion("1000").forPath("path")
删除一个节点,强制保证删除
client.delete().guaranteed().forPath("path");
guaranteed()接口是一个保障措施,只要客户端会话有效,那么Curator会在后台持续进行删除操作,直到删除节点成功。
组合
client.delete().guaranteed().deletingChildrenIfNeeded().withVersion(1000).forPath("path")
读取一个节点的数据内容
client.getData().forPath("path") //此方法返的返回值是byte[ ];
读取一个节点的数据内容,同时获取到该节点的stat
Stat stat = new Stat(); client.getData().storingStatIn(stat).forPath("path");
更新一个节点的数据内容
client.setData() .withVersion(2) // 乐观锁
.forPath("/search/business/test","0".getBytes());
检查节点是否存在
// 判断是否存在,Stat就是对znode所有属性的一个映射,stat=null表示节点不存在
Stat stat = client.checkExists().forPath("/search/business/test");
注意:该方法返回一个Stat实例,用于检查ZNode是否存在的操作. 可以调用额外的方法(监控或者后台处理)并在最后调用forPath( )指定要操作的ZNode
获取某个节点的所有子节点路径
client.getChildren().forPath("path");
注意:该方法的返回值为List<String>,获得ZNode的子节点Path列表。 可以调用额外的方法(监控、后台处理或者获取状态watch, background or get stat) 并在最后调用forPath()指定要操作的父ZNode
事务
CuratorFramework的实例包含inTransaction( )接口方法,调用此方法开启一个ZooKeeper事务. 可以复合create, setData, check, and/or delete 等操作然后调用commit()作为一个原子操作提交。一个例子如下
client.inTransaction().check().forPath("path")
.and()
.create().withMode(CreateMode.EPHEMERAL).forPath("path","data".getBytes())
.and()
.setData().withVersion(10086).forPath("path","data2".getBytes())
.and()
.commit();
异步接口
Curator提供异步接口,引入BackgroundCallback接口用于处理异步接口调用之后服务端返回的结果信息。BackgroundCallback接口中一个重要的回调值为CuratorEvent,里面包含事件类型、响应吗和节点的详细信息。

响应码(#getResultCode())

一个异步创建节点的例子如下:
Executor executor = Executors.newFixedThreadPool(2); client.create() .creatingParentsIfNeeded() .withMode(CreateMode.EPHEMERAL) .inBackground((curatorFramework, curatorEvent) -> {
System.out.println(String.format("eventType:%s,resultCode:%s",curatorEvent.getType(),curatorEvent.getResultCode())); },executor) .forPath("path");
注意:如果#inBackground()方法不指定executor,那么会默认使用Curator的EventThread去进行异步处理。
缓存
// watcher事件,使用usingWatcher的时候,监听只会触发一次,监听完毕后就销毁 client.getData() .usingWatcher(new CuratorWatcher() { @Override public void process(WatchedEvent event) throws Exception { logger.info("触发watcher, path:{}", event.getPath()); } }) .forPath("/search/business/test");
Zookeeper原生支持通过注册Watcher来进行事件监听,但是开发者需要反复注册(Watcher只能单次注册单次使用)。Cache是Curator中对事件监听的包装,可以看作是对事件监听的本地缓存视图,能够自动为开发者处理反复注册监听。Curator提供三种Watcher(Cache)来监听结点的变化。
Path Cache
Path Cache用来监控一个ZNode的子节点. 当一个子节点增加, 更新,删除时, Path Cache会改变它的状态, 会包含最新的子节点, 子节点的数据和状态,而状态的更变将通过PathChildrenCacheListener通知。
实际使用时会涉及到四个类:
- PathChildrenCache
- PathChildrenCacheEvent
- PathChildrenCacheListener
- ChildData
通过下面的构造函数创建Path Cache:
public PathChildrenCache(CuratorFramework client, String path, boolean cacheData)
// watcher事件, NodeCache一次注册N次监听, 缺点:没法监听当前节点增删改操作,所以引出PathChildrenCache final NodeCache nodeCache = new NodeCache(client, "/search/business/test"); nodeCache.start(true); // 当zkServer与客户端链接的时候, NodeCache会把zk数据缓存到我们本地 if (nodeCache.getCurrentData() != null) { logger.info("节点初始化数据为:{}", new String(nodeCache.getCurrentData().getData())); } else { logger.info("节点初始化数据为空"); } nodeCache.getListenable().addListener(new NodeCacheListener() { @Override public void nodeChanged() throws Exception { if (nodeCache.getCurrentData() != null) { String re = new String(nodeCache.getCurrentData().getData()); logger.info("节点路径:{}, 节点数据:{}", nodeCache.getCurrentData().getPath(), re); } else { logger.info("当前节点被删除了"); } } });
// 监听父节点以下所有的子节点, 当子节点发生变化的时候(增删改)都会监听到 // 为子节点添加watcher事件 // PathChildrenCache监听数据节点的增删改 final PathChildrenCache childrenCache = new PathChildrenCache(client, "/search", true); // NORMAL:异步初始化, BUILD_INITIAL_CACHE:同步初始化, POST_INITIALIZED_EVENT:异步初始化,初始化之后会触发事件 childrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT); List<ChildData> childDataList = childrenCache.getCurrentData(); // 当前数据节点的子节点数据列表 childrenCache.getListenable().addListener(new PathChildrenCacheListener() { @Override public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception { if (event.getType().equals(PathChildrenCacheEvent.Type.INITIALIZED)) { logger.info("子节点初始化ok.."); } else if (event.getType().equals(PathChildrenCacheEvent.Type.CHILD_ADDED)) { logger.info("添加子节点, path:{}, data:{}", event.getData().getPath(), event.getData().getData()); } else if (event.getType().equals(PathChildrenCacheEvent.Type.CHILD_REMOVED)) { logger.info("删除子节点, path:{}", event.getData().getPath()); } else if (event.getType().equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)) { logger.info("修改子节点, path:{}, data:{}", event.getData().getPath(), event.getData().getData()); } } }); CloseableUtils.closeQuietly(client);//建议放在finally块
Recipes 模块主要有 Elections (选举)、Locks (锁)、Barriers (关卡)、Atomic (原子量)、Caches、Queues 等
参考:
https://www.jianshu.com/p/70151fc0ef5d
https://blog.csdn.net/smartbetter/article/details/53083816
浙公网安备 33010602011771号