ZooKeeper源码阅读系列-zk的Watcher机制一

278 阅读5分钟

「这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战」。

前言

原计划今天是安利好用的开源框架系列,但是最近被赶鸭子'上线',开源软件篇还没有写完,就继续源码解读。 ZooKeeper是使用得最频繁的分布式框架了,一直都感觉它功能强大,貌似无所不能,能当注册中心也能当分布式锁,那它的底层究竟是怎么设计和运行的,我也一直很好奇,所以今天来研究一下,zk的wather机制,篇幅会较长所以会分就几个章节进行解读。

ZooKeeper的Watcher机制,总的来说可以分为三个过程:
1. 客户端注册Watcher
2. 服务器处理Watcher
3. 客户端回调Watcher

整个过程涉及到的类图如下图所示: image.png 包含以下主要的方法和类:

1.Zookeeper:客户端的Main.class
2.ClientWatchManager:接口,表示客户端的Watcher管理者,其定义了materialized方法,需子类实现。
3.ZKWatchManager:Zookeeper的内部类,ClientWatchManager的实现类。
4.WatchRegistration: watcher的注册包装类。
5.ClientCnxn:客户端核心线程管理类。
6.Packet:client和server的通信协议。
首先我们看下Watch在Client的存储以及体现以及主要涉及到的类
Watcher:接口类型,其定义了process方法,需子类实现。
Event:接口类型,Watcher的内部类,无任何方法。
KeeperState:枚举类型,Event的内部类,表示Zookeeper所处的状态。
EventType:枚举类型,Event的内部类,表示Zookeeper中发生的事件类型。
WatchedEvent:表示对ZooKeeper上发生变化后的反馈,包含了KeeperState和EventType。
ClientWatchManager:接口类型,表示客户端的Watcher管理者,其定义了materialized方法,需子类实现。

在看之前要明白一个事情,Watcher是什么? ZK中引入Watcher机制来实现分布式的通知功能。 ZK允许客户端向服务端注册一个Watcher监听,当服务点的的指定事件触发监听时,那么服务端就会向客户端发送事件通知,以便客户端完成逻辑操作(即客户端向服务端注册监听,并将Watcher对象存在客户端的Watchermanager中服务端触发事件后,向客户端发送通知,客户端收到通知后从wacherManager中取出对象来执行回调逻辑) watcher的特点: 1.一次性:一旦一个Watcher被触发,ZK都会将其从相应的的存储中移除,所以Watcher是需要每注册一次,才可触发一次。 2. 客户端串行执行:客户端Watcher回调过程是一个串行同步的过程。 3. 轻量:Watcher数据结构中只包含:通知状态、事件类型和节点路径 接下来看看Watcher的类图: image.png watcher的源码解读

public interface Watcher {
    public interface Event {
     //用于记录Event发生时的zk状态(通知状态)
        public enum KeeperState { 
            @Deprecated
            //未知状态
            Unknown (-1),
            //客户端和服务端断开状态
            Disconnected (0), 
            @Deprecated
            //未同步链接
            NoSyncConnected (1),
             //客户端和服务端处于连接状态
            SyncConnected (3),
            //权限验证异常
            AuthFailed (4), 
             //只读
            ConnectedReadOnly (5),
            //SASL认证通过状态
            SaslAuthenticated(6),
            //会话过期
            Expired (-112);
         //省略......
         //用于记录event的类型
        public enum EventType {
            None (-1),
            NodeCreated (1),
            NodeDeleted (2),
            NodeDataChanged (3),
            NodeChildrenChanged (4);
            private final int intValue;
            public static EventType fromInt(int intValue) {
                switch(intValue) {
                 //客户端和服务器成功建立连接
                    case -1: return EventType.None;     
                    //watch监听对应节点被创建
                    case  1: return EventType.NodeCreated;
                    //watch 对应节点删除
                    case  2: return EventType.NodeDeleted;
                     // 监听对应节点数据内容发生改变
                    case  3: return EventType.NodeDataChanged;
                    /监听对应节点的子节点列表发生变化
                    case  4: return EventType.NodeChildrenChanged;/
                    default:
                        throw new RuntimeException("Invalid integer value for conversion to EventType");
                }
            }
        }
    }
    ////回调函数实现该函数,表示根据event执行的行为
    abstract public void process(WatchedEvent event);
}
Watcher的类总结:
1. process函数,用于处理回调
2. 内部类Event又包含内部类KeeperState以及EventType
3. KeeperState用于记录Event发生时的zk状态(通知状态)
4. EventType用于记录Event的类型

接着看下WatchedEvent类,WatchedEvent三个属性很好的体现了这个类的意思记录了哪个节点,zk的状态以及影响的事件类型,代码如下:

public class WatchedEvent {
    //zk服务的状态
    final private KeeperState keeperState; 
    //事件类型
    final private EventType eventType; 
    //节点的路径
    private String path; 
    //转换为WatcherEvent
    public WatcherEvent getWrapper() {
        return new WatcherEvent(eventType.getIntValue(),
                keeperState.getIntValue(),
                path);
    }
}

接着就是Watcher在Client存储的核心类ClientWatchManager接口的实现类ZKWatchManager,该方法在事件发生后,返回需要被通知的Watcher集合。在该方法中,首先会根据EventType类型确定相应的事件类型,然后根据事件类型的不同做出相应的操作,如针对None类型,即无任何事件,则首先会从三个键值对中删除clientPath对应的Watcher,然后将剩余的Watcher集合添加至结果集合;针对NodeDataChanged和NodeCreated事件而言,其会从dataWatches和existWatches中删除clientPath对应的Watcher,然后将剩余的Watcher集合添加至结果集合,从这个方法中也可看到Watcher的一次性,执行一次都删除了。

小结: 上面的类图画得有点丑,大家凑合看吧,还不太习惯画图,另外源码解读的也可能存在不到位或者理解错误的地方,还是得多看几遍代码。