通过前面的文章对Zookeeper在java中的使用有了初步的了解,但是不能仅仅止步于他的使用,去看看其中内部的原理,对以后的工作和使用都会有更好的了解。接下来主要来看一下在使用watcher通知时候,客户端和服务器都有哪些操作。
一、作用
使用zk可以作为发布订阅的服务器,使用watcher机制便可以实现发布订阅的功能,实现分布式通知的能力。
二、实现
1.示例
具体实现可以在之前的文章中进行查看,此处仅做简单的实例。在创建zk链接时候可以传入watcher,这个watcher将会作为全局默认的watcher,被存储在客户端。
来看下之前的示例代码:
/**
* 建立链接
*/
public ZooKeeper connectionZk(){
/**
* 创建zookeeper链接
*/
try {
ZooKeeper zooKeeper = new ZooKeeper(host+":"+port,5000,new ZkWatch());
System.out.println("链接成功:"+zooKeeper.getState());
return zooKeeper;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 创建节点后会通过此watch进行通知
*/
public class ZkWatch implements Watcher{
@Override
public void process(WatchedEvent watchedEvent) {
System.out.println("state:"+watchedEvent.getState());
}
}
2.相关状态介绍
可以看到上述示例中ZkWatch中实现了Watcher接口,并且重写了process()方法,那么在触发时候,会在系统内部进行调用自定义Wacher类中的process()。参数为WatchedEvent,这个参数类中包括KeeperState和EventType,来看下对应的状态和类型都有哪些
public class WatchedEvent {
private final KeeperState keeperState;
private final EventType eventType;
private String path;
}
通过表格来看下枚举值
| KeeperState | EventType | 触发条件 |
| SyncConnected(3) | None(-1) | 客户端与服务端成功建立会话 |
| NodeCreated(1) | 监听的数据节点被创建 | |
| NodeDelete(2) | 监听的数据节点被删除 | |
| NodeDataChanged(3) | 监听的数据节点发生变化 | |
| NodeChildChanged(4) | 监听的数据节点的子节点列表发生变更 | |
| Disconnected(0) | None(-1) | 客户端与服务端断开连接 |
| Expired(-122) | None(-1) | 会话超时 |
| AuthFailed | None(-1) | 权限错误 |
三、工作原理
来看下下面这张图,描述了从客户端注册watcher到watcher触发进行回调通知的流程。整体分为客户端(上半部分)和服务端(下半部分)。
1.客户端注册watcher
- 以
getData()方法调用时候传入watcher信息为例。在获取数据时候可以对当前节点进行使用watcher进行监听。 - 在
getData()方法中将watcher信息被包装到WatchRegistration进行调用。 - 接下来使用
ClientCnxn进行发送网络请求,在ClientCnxn中会将WatchRegistration打包成packet,然后放到发送队列outgoingQueue中,等待SendThread线程对其进行发送到服务端. - 通过
SendThread线程从发送队列中进行读取发送到服务端,当服务端返回成功,则将当前watcher信息存储到本地的ZKWatcherManager中进行保存. - 在本地中实际存储到Map中
2.服务端接收注册watcher
- 服务端接收到请求后,会在
FinalRequestProcessor.processRequest()方法中进行判断是否需要注册watcher,判断是否注册其实是判断客户端是否传入watcher信息。 - 如果需要存储则将当前的
ServerCnxn链接进行保存起来。保存到服务端WatcherManager中,服务端WatcherManager会包含两个map结构,分别保存不同的映射关系。 - 然后返回客户端成功信息
3.watcher触发回调
此处将介绍watcher的触发回调,将服务端和客户端的流程一起写在这里。以setData为例改变数据,触发通知流程。
- 服务端收到客户端的
setData()请求,进行同步设置数据。 - 当设置数据成功后,同步调用
WatcherManager.triggerWatch()方法进行发起回调请求。 - 在方法中将当前节点封装为
WatcheredEvent对象,然后将对应watcher信息从服务端watcher信中进行删除,此处说明每次发生调用都是一次性的。触发后便会失效。 - 通过之前存储的
ServerCnxn链接信息,将当前变更发送给客户端 - 客户端由
SendThread进行接收服务器的请求信息,进行反序列化还原WatcherEvent对象。然后放入pendingQueue阻塞队列中。 - 客户端存在
EventThread线程会对阻塞队列中进行获取回调数据。然后调用watcher.process()的方法。
四、总结
通过上面的总结分析,整体结构脉络有了初步的认识。
客户端一共有ZKWatcherManager管理客户端watcher信息、ClientCnxn管理客户端链接、SendThread用于从队列中发送请求到服务端,并且接受服务单的请求、EventThread用于处理接受到的请求进行回调watcher数据,并且有两个队列outgoingQuene和pengdingQueue分别用于发送数据存储和接受数据的存储。
服务端一共有WatcherManger管理注册和删除客户端的watcher信息。ServerCnxn管理服务端接收到请求链接。
Watcher整体逻辑可以看做是客户端的操作,服务端只是收到请求后再次从当前的链接进行通知到客户端,客户端进行回调。客户端对同一个path的回调采用轮训方式,也支持单独传入线程池进行处理。
继续加油