Apache ZooKeeper Watcher 机制源码解释

来源:互联网 发布:网络节点分为三类 编辑:程序博客网 时间:2024/05/23 18:49

本文首先讲解了 Apache ZooKeeperWatcher 机制的使用方式,通过一个集群内部状态监听、触发动作的实例以及回调函数的基本知识,引发对于 Watcher 机制内部实现原理和源代码的讨论,然后通过对实现原理的解释让读者有一些工作方式的理解,接下来对源代码进行分析,进一步加深读者对 Watcher 机制的理解。
分布式系统从根本上来说就是不同节点上的进程并发执行,并且相互之间对进程的行为进行协调处理的过程。不同节点上的进程互相协调行为的过程叫做分布式同步。许多分布式系统需要一个进程作为任务的协调者,执行一些其他进程并不执行的特殊的操作,一般情况下哪个进程担当任务的协调者都无所谓,但是必须有一个进程作为协调者,自动选举出一个协调者的过程就是分布式选举。ZooKeeper 正是为了解决这一系列问题而生的。上一篇我们介绍了 ZooKeeper 服务启动原理和源代码剖析,这一讲我们来谈谈 Watcher 机制,首先介绍一个监控示例,然后我们再来聊聊 Watcher 机制原理。
ZooKeeper Watcher 机制
集群状态监控示例
为了确保集群能够正常运行,ZooKeeper 可以被用来监视集群状态,这样就可以提供集群高可用性。使用 ZooKeeper 的瞬时(ephemeral)节点概念可以设计一个集群机器状态检测机制:
1. 每一个运行了 ZooKeeper 客户端的生产环境机器都是一个终端进程,我们可以在它们连接到 ZooKeeper 服务端后在 ZooKeeper 服务端创建一系列对应的瞬时节点,可以用/hostname 来进行区分。
2. 这里还是采用监听(Watcher)方式来完成对节点状态的监视,通过对/hostname 节点的 NodeChildrenChanged 事件的监听来完成这一目标。监听进程是作为一个独立的服务或者进程运行的,它覆盖了 process 方法来实现应急措施。
3. 由于是一个瞬时节点,所以每次客户端断开时 znode 会立即消失,这样我们就可以监听到集群节点异常。
4.NodeChildrenChanged 事件触发后我们可以调用 getChildren 方法来知道哪台机器发生了异常。
清单 1.ClusterMonitor 类
import java.io.IOException;
import java.util.List;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;

public class ClusterMonitor implements Runnable{
private static String membershipRoot = “/Members”;
private final Watcher connectionWatcher;
private final Watcher childrenWatcher;
private ZooKeeper zk;
boolean alive = true;
public ClusterMonitor(String HostPort) throws IOException,InterruptedException,KeeperException{
connectionWatcher = new Watcher(){
@Override
public void process(WatchedEvent event) {
// TODO Auto-generated method stub
if(event.getType() == Watcher.Event.EventType.None &&
event.getState() == Watcher.Event.KeeperState.SyncConnected){
System.out.println(“\nconnectionWatcher Event Received:%s”+event.toString());
}
}
};

childrenWatcher = new Watcher(){
@Override
public void process(WatchedEvent event) {
// TODO Auto-generated method stub
System.out.println(“\nchildrenWatcher Event Received:%s”+event.toString());
if(event.getType()==Event.EventType.NodeChildrenChanged){
try{
//Get current list of child znode and reset the watch
List children = zk.getChildren(membershipRoot, this);
System.out.println(“Cluster Membership change,Members: “+children);
}catch(KeeperException ex){
throw new RuntimeException(ex);
}catch(InterruptedException ex){
Thread.currentThread().interrupt();
alive = false;
throw new RuntimeException(ex);
}
}
}
};

zk = new ZooKeeper(HostPort,2000,connectionWatcher);
//Ensure the parent znode exists
if(zk.exists(membershipRoot, false) == null){
zk.create(membershipRoot, “ClusterMonitorRoot”.getBytes(),
Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
//Set a watch on the parent znode
List children = zk.getChildren(membershipRoot, childrenWatcher);
System.err.println(“Members:”+children);
}

public synchronized void close(){
try{
zk.close();
}catch(InterruptedException ex){
ex.printStackTrace();
}
}

@Override
public void run() {
// TODO Auto-generated method stub
try{
synchronized(this){
while(alive){
wait();
}
}
}catch(InterruptedException ex){
ex.printStackTrace();
Thread.currentThread().interrupt();
}finally{
this.close();
}
}

public static void main(String[] args) throws IOException,InterruptedException,KeeperException{
if(args.length != 1){
System.err.println(“Usage:ClusterMonitor”);
System.exit(0);
}
String hostPort = args[0];
new ClusterMonitor(hostPort).run();
}

}
清单 2.ClusterClient 类
import java.io.IOException;
import java.lang.management.ManagementFactory;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;

public class ClusterClient implements Watcher,Runnable{
private static String membershipRoot = “/Members”;
ZooKeeper zk;
public ClusterClient(String hostPort,Long pid){
String processId = pid.toString();
try{
zk = new ZooKeeper(hostPort,2000,this);
}catch(IOException ex){
ex.printStackTrace();
}
if(zk!=null){
try{
zk.create(membershipRoot+’/’+processId, processId.getBytes(),
Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
}catch(KeeperException | InterruptedException ex){
ex.printStackTrace();
}
}
}

public synchronized void close(){
try{
zk.close();
}catch(InterruptedException ex){
ex.printStackTrace();
}
}

@Override
public void process(WatchedEvent event) {
// TODO Auto-generated method stub
System.out.println(“\nEvent Received:%s”+event.toString());
}

@Override
public void run() {
// TODO Auto-generated method stub
try{
synchronized(this){
while(true){
wait();
}
}
}catch(InterruptedException ex){
ex.printStackTrace();
Thread.currentThread().interrupt();
}finally{
this.close();
}
}

public static void main(String[] args){
if(args.length!=1){
System.err.println(“Usage:ClusterClient”);
System.exit(0);
}

String hostPort=args[0];
//Get the process id
String name = ManagementFactory.getRuntimeMXBean().getName();
int index = name.indexOf(‘@’);
Long processId = Long.parseLong(name.substring(0,index));
new ClusterClient(hostPort,processId).run();
}

}
清单 3.Eclipse 运行输出
childrenWatcher Event Received:%sWatchedEvent state:SyncConnected type:NodeChildrenChanged path:/Members
Cluster Membership change,Members: [dweref0000000009, test100000000003, dsdawqeqw0000000008,
test111110000000004, test22220000000005, dsda32130000000007, dsda0000000006, test10000000002]
我们通过 zkCli 方式对被监听的/Members 这个 ZNODE 操作,增加一个子节点,您会在 zkCli 里看到如清单 4 所示输出。
清单 4.ZKCli 创建 ZNode 子节点
[zk: localhost:2181(CONNECTED) 0] create -s /Members/dweref rew23rf
Created /Members/dweref0000000009 [zk: localhost:2181(CONNECTED) 4]
上面的示例我们演示了如何发起对于一个 ZNODE 的监听,当该 ZNODE 被改变后,我们会触发对应的方法进行处理,这类方式可以被用在数据监听、集群状态监听等用途。
回调函数
由于 Watcher 机制涉及到回调函数,所以我们先来介绍一下回调函数的基础知识。
打个比方,有一家旅馆提供叫醒服务,但是要求旅客自己决定叫醒的方法。可以是打客房电话,也可以是派服务员去敲门,睡得死怕耽误事的,还可以要求往自己头上浇盆水。这里,“叫醒”这个行为是旅馆提供的,相当于库函数,但是叫醒的方式是由旅客决定并告诉旅馆的,也就是回调函数。而旅客告诉旅馆怎么叫醒自己的动作,也就是把回调函数传入库函数的动作,称为登记回调函数(to register a callback function)。
乍看起来,回调似乎只是函数间的调用,但仔细一琢磨,可以发现两者之间的一个关键的不同:在回调中,我们利用某种方式,把回调函数像参数一样传入中间函数。可以这么理解,在传入一个回调函数之前,中间函数是不完整的。换句话说,程序可以在运行时,通过登记不同的回调函数,来决定、改变中间函数的行为。这就比简单的函数调用要灵活太多了。
回调实际上有两种:阻塞式回调和延迟式回调。两者的区别在于:阻塞式回调里,回调函数的调用一定发生在起始函数返回之前;而延迟式回调里,回调函数的调用有可能是在起始函数返回之后。我们来看一个简单的示例。
清单 5.Caller 类
public class Caller
{
public MyCallInterface mc;

public void setCallfuc(MyCallInterface mc)
{
this.mc= mc;
}

public void call(){
this.mc.method();
}
}
清单 6.MyCallInterface 接口
public interface MyCallInterface {
public void method();
}
清单 7.CallbackClass 类
public class CallbackClass implements MyCallInterface{
public void method()
{
System.out.println(“回调函数”);
}

public static void main(String args[])
{
Caller call = new Caller();
call.setCallfuc(new CallbackClass());
call.call();
}
}
清单 8. 运行结果
回调函数
原理及源代码解释
实现原理
ZooKeeper 允许客户端向服务端注册一个 Watcher 监听,当服务端的一些指定事件触发了这个 Watcher,那么就会向指定客户端发送一个事件通知来实现分布式的通知功能。
ZooKeeper 的 Watcher 机制主要包括客户端线程、客户端 WatchManager 和 ZooKeeper 服务器三部分。在具体工作流程上,简单地讲,客户端在向 ZooKeeper 服务器注册 Watcher 的同时,会将 Watcher 对象存储在客户端的 WatchManager 中。当 ZooKeeper 服务器端触发 Watcher 事件后,会向客户端发送通知,客户端线程从 WatchManager 中取出对应的 Watcher 对象来执行回调逻辑。如清单 9 所示,WatchManager 创建了一个 HashMap,这个 HashMap 被用来存放 Watcher 对象。
清单 9.WatchManager 类
private final HashMap

0 0
原创粉丝点击