Curator
Curator中提供了Zookeeper各种应用场景(分布式锁、Master选举机制和分布式计算器等)的抽象封装。
项目组件:
名称 | 描述 |
---|---|
Recipes | Zookeeper典型应用场景的实现,这些实现是基于Curator Framework。 |
Framework | Zookeeper API的高层封装,大大简化Zookeeper客户端编程,添加了例如Zookeeper连接管理、重试机制等。 |
Utilities | 为Zookeeper提供的各种实用程序。 |
Client | Zookeeper client的封装,用于取代原生的Zookeeper客户端(ZooKeeper类),提供一些非常有用的客户端特性。 |
Errors | Curator如何处理错误,连接问题,可恢复的例外等。 |
创建节点
(1)创建一个初始内容为空的节点:client.create().forPath(path);
Curator默认创建的是持久节点,内容为空。
(2)创建一个包含内容的节点:client.create().forPath(path,"我是内容".getBytes());
(3)创建临时节点,并递归创建父节点client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(path);
在递归创建父节点时,父节点为持久节点。
删除节点
(1)删除一个子节点client.delete().forPath(path);
(2)删除节点并递归删除其子节点client.delete().deletingChildrenIfNeeded().forPath(path);
(3)指定版本进行删除client.delete().withVersion(1).forPath(path);
如果此版本已经不存在,则删除异常,异常信息如下。
(4)强制保证删除一个节点client.delete().guaranteed().forPath(path);
只要客户端会话有效,那么Curator会在后台持续进行删除操作,直到节点删除成功。比如遇到一些网络异常的情况,此guaranteed的强制删除就会很有效果。
读取数据
读取节点数据内容API相当简单,Curator提供了传入一个Stat,使用节点当前的Stat替换到传入的Stat的方法,查询方法执行完成之后,Stat引用已经执行当前最新的节点Stat。
// 普通查询
client.getData().forPath(path);
// 包含状态查询
Stat stat = new Stat();
client.getData().storingStatIn(stat()).forPath(path);
更新数据
更新数据,如果未传入version参数,那么更新当前最新版本,如果传入version则更新指定version,如果version已经变更,则抛出异常。
// 普通更新
client.setData().forPath(path,"新内容".getBytes());
// 指定版本更新
client.setData().withVersion(1).forPath(path);
版本不一致异常信息:
org.apache.zookeeper.KeeperException$BadVersionException: KeeperErrorCode = BadVersion for
异步接口
在使用以上针对节点的操作API时,我们会发现每个接口都有一个inBackground()方法可供调用。此接口就是Curator提供的异步调用入口。对应的异步处理接口为BackgroundCallback。此接口指提供了一个processResult的方法,用来处理回调结果。其中processResult的参数event中的getType()包含了各种事件类型,getResultCode()包含了各种响应码。重点说一下inBackground的以下接口:public T inBackground(BackgroundCallback callback, Executor executor);
此接口就允许传入一个Executor实例,用一个专门线程池来处理返回结果之后的业务逻辑。
源码分析
LeaderLatch
LeaderLatch的方式,是以一种抢占的方式来决定选主。类似非公平锁的抢占,所以多节点是一个随机产生主节点的过程。
使用
LeaderLatch创建好之后,必须执行:leaderLatch.start();
这样,才能让leaderLatch开始参与选主过程。由于LeaderLatch是一个不断抢占的过程,所以需要调用:public boolean hasLeadership()
来检测当前参与者是否选主成功。这个方法是非阻塞的(立即返回),其结果只代表调用时的选主结果。所以,可以轮询此方法,或者当执行完本地逻辑后,需要执行分布式任务前检擦此方法。不过,类似JDK中的CountDownLatch,LeaderLatch也提供了阻塞方法:
public void await()
throws InterruptedException,
EOFException
这个方法,会阻塞,直到选主成功。
方法2 为了避免方法1的长时间选主失败
public boolean await(long timeout,
TimeUnit unit)
throws InterruptedException
这个方法会根据参数中指定的时间,作为等待的期限。到期后,返回选主结果。
对于LeaderLatch实例,无论是否轩主成功,最后都应该调用:
leaderLatch.close();
这样,才会把当前参与者的信息从选主分组中移除出去。如果,当前参与者是主,还会释放主的资格。避免死锁。
4. 错误处理
在实际使用中,必须考虑链接问题引起的主身份丢失问题。 例如:当hasLeadership()返回true,之后链接出问题。 强烈建议:使用LeaderLatch时为其添加一个ConnectionStateListener
LeaderLatch实例会添加一个ConnectionStateListener来监听当前zk链接。 如果,链接不可用(SUSPENDED)则LeaderLatch会认为自己不在是主,等到链接恢复可用时,才可继续。 如果,链接断开(LOST),则LeaderLatch会认为自己不在是主,等到链接重新建立后,删除之前的参与者信息,然后重新参与选主。
成员变量
log : caurtor依赖slf4j
client : zk客户端(curator-framework提供)
latchPath : 分组路径(zk中的path)
id : 参与者ID
state
内部枚举
状态
LATENT 休眠
STARTED 已启动
CLOSED 已关闭
使用AtomicReference原子化包装
hasLeadership
是否为主
使用AtomicBoolean原子化包装
ourPath
使用AtomicReference原子化包装
listeners
一组LeaderLatchListener监听器
closeMode
内部枚举
LeaderLatch关闭方式
SILENT : 静默关闭,不触发相关监听器
NOTIFY_LEADER :关闭时触发监听器
startTask
异步Future
使用AtomicReference原子化包装
listener
链接状态监听器
参见 : 4.</