一、序言
之前分享过一篇关于Curotor的基本应用【基础篇】详解Zookeeper客户端Curator,如果对Curator没有了解的可以看看,本文分享关于Curator的一些高级特性,在监听和leader选举上的应用。
二、监听、缓存
原生Zookeeper通过注册Watcher来进行事件监听,而Watcher只能单次注册,如果需要多次使用则需要反复注册。Cache是Curator中对事件监听的包装,可以看作是在本地缓存了事件监听,从而实现反复注册监听。Curator提供了三种Watcher(Cache)来监听结点的变化。
2.1 PathChildrenCache
PathChildrenCache监听一个节点(Znode)下面所有子节点的变化,当一个子节点增加, 更新,删除时,PathChildrenCache可以监听到子节点的变化,同时获取子节点的数据和状态,而状态的更变将通过PathChildrenCacheListener。
// 监听路径子节点的变化
PathChildrenCache watcher = new PathChildrenCache(zkClient, getPath(), true);
PathChildrenCacheListener childrenCacheListener = (framework, event) -> {
if (event.getType() != CHILD_UPDATED || !getPath().equals(event.getData().getPath())) {
return;
}
initDataSource();
System.out.println("数据源更新完毕");
System.out.println("子节点事件类型:" + event.getType() + " | 路径:" + (null != event.getData() ? event.getData().getPath() : null));
};
watcher.getListenable().addListener(childrenCacheListener);
watcher.start();
2.2 NodeCache
NodeCache与PathChildrenCache类似,但它是监听某个节点的变化,当节点发送变化(增加,删除,内容修改),NodeCache同样可以监听到节点的变化。
// 监听节点变化
NodeCache cache = new NodeCache(zkClient, getPath());
NodeCacheListener nodeCacheListener = () -> {
ChildData data = cache.getCurrentData();
if (null != data) {
System.out.println("节点数据:" + new String(cache.getCurrentData().getData()));
} else {
System.out.println("节点被删除!");
}
};
cache.getListenable().addListener(nodeCacheListener);
cache.start();
2.2 TreeCache
TreeCache可以看成是NodeCache和PathChildrenCache的集合,监听的是整棵数的变化,包含当前节点和子节点的变化。
// 监听整棵树的变化
TreeCache treeCache = new TreeCache(zkClient, getPath());
TreeCacheListener treeCacheListener = (framework, event) -> {
System.out.println("事件类型:" + event.getType() + " | 路径:" + (null != event.getData() ? event.getData().getPath() : null));
};
treeCache.getListenable().addListener(treeCacheListener);
treeCache.start();
三、leader选举
这里的leader选举不是指zookeeper内部的leader选举,而是指基于zookeeper实现应用层面的leader选举,比如有一个任务,在多节点环境下只允许一个节点处理,而其他节点收到相关的任务都要交给指定的那个节点执行,这里就可以基于zookeeper选出一个节点做为leader,而其他节点则是coordinator。
选举算法开始执行后, 每个节点最终会得到一个唯一的节点作为任务leader,leader负责写操作,然后通过Zab协议实现follower的同步,leader或者follower都可以处理读操作。除此之外, 选举还经常会发生在leader意外宕机的情况下,新的leader要被选举出来。
Curator有两种leader选举的策略,分别是LeaderSelector和LeaderLatch,前者是所有存活的客户端不间断的轮流做Leader。后者是一旦选举出Leader,除非有客户端挂掉重新触发选举,否则不会交出领导权。
3.1 LeaderSelector
LeaderSelector核心构造方法有两个
public LeaderSelector(CuratorFramework client, String mutexPath,LeaderSelectorListener listener)
public LeaderSelector(CuratorFramework client, String mutexPath, ThreadFactory threadFactory, Executor executor, LeaderSelectorListener listener)
可以通过start()启动LeaderSelector,一旦启动,当节点取得领导权时会调用takeLeadership()。
举一个LeaderSelector的使用例子:
public class LeaderSelectorAdapter extends LeaderSelectorListenerAdapter implements Closeable {
private String name;
private LeaderSelector leaderSelector;
public LeaderSelectorAdapter(CuratorFramework client, String path, String name){
this.name = name;
leaderSelector = new LeaderSelector(client, path, this);
leaderSelector.autoRequeue();
}
public void start() {
leaderSelector.start();
}
@Override
public void close() throws IOException{
leaderSelector.close();
}
@Override
public void takeLeadership(CuratorFramework curatorFramework) throws Exception {
final int waitSeconds = (int) (5 * Math.random()) + 1;
System.out.println(name + " is now the leader. Waiting " + waitSeconds + " seconds...");
try {
TimeUnit.SECONDS.sleep(waitSeconds);
} catch (InterruptedException e) {
System.err.println(name + " was interrupted.");
Thread.currentThread().interrupt();
} finally {
System.out.println(name + " release leadership.\n");
}
}
}
/**
* 创建zk客户端
*/
private CuratorFramework createClient(){
CuratorFramework client = CuratorFrameworkFactory.builder().connectString(zkAddr)
.sessionTimeoutMs(50000)
.connectionTimeoutMs(30000)
.retryPolicy(new ExponentialBackoffRetry(10000, 5))
.namespace("leaderSelector")
.build();
return client;
}
/**
* 轮询创建leader
*/
private void initLeaderSelector() throws Exception{
List<CuratorFramework> clients = new ArrayList<>();
List<LeaderSelectorAdapter> leaders = new ArrayList<>();
try {
for (int i = 0; i < 10; i++) {
CuratorFramework client = createClient();
clients.add(client);
LeaderSelectorAdapter selectorAdapter = new LeaderSelectorAdapter(client, "/node2", "client#" + i);
leaders.add(selectorAdapter);
client.start();
selectorAdapter.start();
}
new BufferedReader(new InputStreamReader(System.in)).readLine();
}finally {
for (LeaderSelectorAdapter leader : leaders) {
leader.close();
}
for (CuratorFramework client : clients) {
client.close();
}
}
}
同时,LeaderLatch实例可以增加ConnectionStateListener来监听网络连接问题。当网络连接出现异常,leader不再认为自己还是leader。当连接重连后LeaderLatch会删除先前的ZNode然后重新创建一个,所以推荐使用ConnectionStateListener来处理网络抖动问题。
LeaderSelector必须小心连接状态的改变。如果实例成为leader, 它应该响应SUSPENDED或LOST。 当SUSPENDED状态出现时, 实例必须假定在重新连接成功之前它可能不再是leader了。 如果LOST状态出现, 实例不再是leader,takeLeadership方法返回。
推荐处理方式是当收到SUSPENDED或LOST时抛出CancelLeadershipException异常.。这会导致LeaderSelector实例中断并取消执行takeLeadership方法的异常。LeaderSelectorListenerAdapter.stateChanged提供了推荐的处理逻辑。
3.2 LeaderLatch
LeaderLatch有两个构造函数:
public LeaderLatch(CuratorFramework client, String latchPath)
public LeaderLatch(CuratorFramework client, String latchPath, String id)
可以通过start()启动LeaderLatch,一旦启动,LeaderLatch会和其它使用相同latchPath的其它LeaderLatch通信,最终只有一个会被选举为leader。hasLeadership()可以查看LeaderLatch实例是否为leader:
leaderLatch.hasLeadership( ); // 返回true说明当前实例是leader
LeaderLatch类似JDK的CountDownLatch,在请求成为leadership会block(阻塞),一旦不使用LeaderLatch了,必须调用close方法。 如果它是leader,会释放leadership,其它的参与者则会继续选举出一个leader。
举一个LeaderLatch的使用例子:
/**
* 多节点下每个节点轮询当leader
*/
private void initLeaderLatch() throws Exception {
List<CuratorFramework> clients = new ArrayList<>();
List<LeaderLatch> latches = new ArrayList<>();
try {
for (int i = 0; i < 10; i++){
CuratorFramework client = createClient();
clients.add(client);
LeaderLatch latch = new LeaderLatch(client, "/node", "client#" + i);
latch.addListener(new LeaderLatchListener() {
@Override
public void isLeader() {
System.out.println("i am leader");
}
@Override
public void notLeader() {
System.out.println("i am not leader");
}
});
latches.add(latch);
client.start();
latch.start();
}
Thread.sleep(10000);
LeaderLatch currentLeader = null;
for (LeaderLatch latch : latches) {
if (latch.hasLeadership()) {
currentLeader = latch;
}
}
System.out.println("current leader is " + currentLeader.getId());
currentLeader.close();
Thread.sleep(10000);
for (LeaderLatch latch : latches) {
if (latch.hasLeadership()) {
currentLeader = latch;
}
}
System.out.println("current leader is " + currentLeader.getId());
}finally {
for (LeaderLatch latch : latches) {
if (latch.getState() != null && latch.getState() != LeaderLatch.State.CLOSED){
latch.close();
}
}
for (CuratorFramework client : clients) {
if(client != null){
client.close();
}
}
}
}
四、结尾
以上就是关于Curotor在监听和leader选举上的应用,后续还会继续整理在分布式锁和分布式队列中的应用。如果你对文章内容有什么疑问或者不清楚的地方,欢迎私信或留言交流~
相关链接: