zk源码阅读11:watch之模型以及client端存储

1.摘要

前面讲完了ACL,QUOTAS,DataNode等等,最后讲一下WatchManager,讲完了就可以讲DataTree了
watchManager涉及watch机制,内容较多,又要针对watch进行展开了

本节讲解

Watcher相关类简介,类图说明
Watcher的意义,通知状态(keeperState)与事件类型(EventType)
WatchedEvent 和 WatcherEvent 描述zk检测到变化的事件,以及对应用于网络传输的封装类
ClientWatchManager接口以及实现类ZKWatchManager :client端完成根据Event找到需要触发的watches
WatcherSetEventPair 将Event以及对应需要触发的watches集合进行组合绑定

2.简介

UML图如下,红色线代表内部类


watcher相关类图

主要的类简介如下:

Watcher,接口类型,其定义了process方法,另外定义内部类Event,再包含内部类KeeperState和EventType来描述Event发生时zk的状态以及对应event类型
WatchedEvent,代表zk上一个Watcher能够回应的变化,包含了变化事件的类型,zk状态以及变化影响的znode的path
WatcherEvent : 是WatchedEvent用于网络传输的封装类
ClientWatchManager:接口,根据Event得到需要通知的watcher
ZKWatchManager为ClientWatchManager的实现

下面进行源码讲解

3.Watcher

Watcher是什么

ZK中引入Watcher机制来实现分布式的通知功能
ZK允许客户端向服务端注册一个Watcher监听,当服务点的的指定事件触发监听时,那么服务端就会向客户端发送事件通知,以便客户端完成逻辑操作(即客户端向服务端注册监听,并将watcher对象存在客户端的Watchermanager中
服务端触发事件后,向客户端发送通知,客户端收到通知后从wacherManager中取出对象来执行回调逻辑)

特性

一次性:一旦一个watcher被触发,ZK都会将其从相应的的存储中移除,所以watcher是需要每注册一次,才可触发一次。
客户端串行执行:客户端watcher回调过程是一个串行同步的过程
轻量:watcher数据结构中只包含:通知状态、事件类型和节点路径

在ZooKeeper中,接口类Watcher定义了事件通知相关的逻辑,包含了KeeperState和EventType两个枚举类,分别代表通知状态和事件类型。

类图如下


Watcher类图

简单介绍上面类图就是

Watcher接口拥有process函数,用于处理回调
内部类Event又包含内部类KeeperState以及EventType
KeeperState用于记录Event发生时的zk状态(通知状态)
EventType用于记录Event的类型

3.1方法process

//回调函数实现该函数,表示根据event执行的行为
abstract public void process(WatchedEvent event);

3.2内部类Event

包含KeeperState和EventType两个内部类,通过枚举类实现
方法很简单,就是int值与对应枚举类型的转换
两者的枚举类型以及两者之间的关系,触发条件可以参考《paxos到zk》中的图

KeeperState与EventType一览表

4.WatchedEvent 和 WatcherEvent

WatchedEvent :代表zk上一个Watcher能够回应的变化,包含了变化事件的类型,zk状态以及变化影响的znode的path
WatcherEvent : 是WatchedEvent用于网络传输的封装类

WatchedEvent 类图如下


WatchedEvent类图

三个成员变量很好的解释了WatchedEvent的意义,即事件的类型,zk状态以及变化影响的znode的path
方法基本都好理解,涉及WatcherEvent 有一个构造方法和一个getWrapper方法
这里稍微强调一下 getWrapper方法

   /**
     *  Convert WatchedEvent to type that can be sent over network
     */
    //转化成可供网络传输,序列化的WatcherEvent
    public WatcherEvent getWrapper() {
        return new WatcherEvent(eventType.getIntValue(), 
                                keeperState.getIntValue(), 
                                path);
    }
}

WatcherEvent实现了Record接口,可以理解为WatchedEvent用于网络传输的封装类

5.ClientWatchManager接口和实现类ZKWatchManager

ClientWatchManager接口用户根据Event得到需要通知的watcher
ZKWatchManager为ClientWatchManager的实现

ClientWatchManager接口只有一个函数,源码分析如下

    //ClientWatchManager负责根据Event得到需要通知的watcher,该manager本身并不进行通知
    public Set<Watcher> materialize(Watcher.Event.KeeperState state,
        Watcher.Event.EventType type, String path);

默认实现类ZKWatchManager,在Zookeeper类中,源码分析如下

private static class ZKWatchManager implements ClientWatchManager {
        private final Map<String, Set<Watcher>> dataWatches =
            new HashMap<String, Set<Watcher>>();//针对内容的watch
        private final Map<String, Set<Watcher>> existWatches =
            new HashMap<String, Set<Watcher>>();//针对exist API相关的watch
        private final Map<String, Set<Watcher>> childWatches =
            new HashMap<String, Set<Watcher>>();//针对getChildren API相关的watch

        private volatile Watcher defaultWatcher;//client传递的,默认的watcher实现

        final private void addTo(Set<Watcher> from, Set<Watcher> to) {
            if (from != null) {
                to.addAll(from);
            }
        }

        /* (non-Javadoc)
         * @see org.apache.zookeeper.ClientWatchManager#materialize(Event.KeeperState, 
         *                                                        Event.EventType, java.lang.String)
         */
        @Override
        public Set<Watcher> materialize(Watcher.Event.KeeperState state,
                                        Watcher.Event.EventType type,
                                        String clientPath)
        {
            Set<Watcher> result = new HashSet<Watcher>();

            switch (type) {
            case None://eventType是null
                // 则所有dataWatches,existWatches,childWatches都需要被通知,???为什么要这样干
                result.add(defaultWatcher);//添加默认watcher
                boolean clear = ClientCnxn.getDisableAutoResetWatch() &&
                        state != Watcher.Event.KeeperState.SyncConnected;//获取clear标记

                synchronized(dataWatches) {
                    for(Set<Watcher> ws: dataWatches.values()) {
                        result.addAll(ws);
                    }
                    if (clear) {
                        dataWatches.clear();
                    }
                }

                synchronized(existWatches) {
                    for(Set<Watcher> ws: existWatches.values()) {
                        result.addAll(ws);
                    }
                    if (clear) {
                        existWatches.clear();
                    }
                }

                synchronized(childWatches) {
                    for(Set<Watcher> ws: childWatches.values()) {
                        result.addAll(ws);
                    }
                    if (clear) {
                        childWatches.clear();
                    }
                }

                return result;
            case NodeDataChanged:
            case NodeCreated:
                //如果节点内容变化或者创建
                synchronized (dataWatches) {
                    addTo(dataWatches.remove(clientPath), result);//从dataWatches中移除,并且添加到result中
                }
                synchronized (existWatches) {
                    addTo(existWatches.remove(clientPath), result);//从existWatches中移除,并且添加到result中
                }
                break;
            case NodeChildrenChanged:
                synchronized (childWatches) {
                    addTo(childWatches.remove(clientPath), result);
                }
                break;
            case NodeDeleted:
                synchronized (dataWatches) {
                    addTo(dataWatches.remove(clientPath), result);
                }
                // XXX This shouldn't be needed, but just in case
                synchronized (existWatches) {
                    Set<Watcher> list = existWatches.remove(clientPath);
                    if (list != null) {
                        addTo(existWatches.remove(clientPath), result);
                        LOG.warn("We are triggering an exists watch for delete! Shouldn't happen!");
                    }
                }
                synchronized (childWatches) {
                    addTo(childWatches.remove(clientPath), result);
                }
                break;
            default://默认处理
                String msg = "Unhandled watch event type " + type
                    + " with state " + state + " on path " + clientPath;
                LOG.error(msg);
                throw new RuntimeException(msg);
            }
            //返回结果
            return result;
        }
    }

该方法在事件发生后,返回需要被通知的Watcher集合。
是根据已经注册的watches(分为三类,data,children,exist),根据path找到对应的watches,得到一个result集合进行返回
这里留下个疑问

watches的注册是在哪里完成,这个后面再讲
为什么碰到case None,所有watches都要被触发,这个目前不是很理解

6.WatcherSetEventPair

WatcherSetEventPair 将Event以及对应需要触发的watches集合进行组合绑定

这个类在ClientCnxn中,代码很简单

    private static class WatcherSetEventPair {
        private final Set<Watcher> watchers;//事件触发需要被通知的watches集合
        private final WatchedEvent event;//事件

        public WatcherSetEventPair(Set<Watcher> watchers, WatchedEvent event) {
            this.watchers = watchers;
            this.event = event;
        }
    }

7.思考

Watcher.Event.KeeperState

这个可以叫成通知状态,也可以理解为事件发生时的zk状态

watcher特性中,"一次性"在client端的体现

ZooKeeper.ZKWatchManager#materialize 中可以看到
被触发的watches从相应的类别(data,children,exist)中删除了,所以在client端是一次性的

为什么需要WatcherSetEventPair 这个类

因为watcher接口process函数需要event参数
那么在ClientWatchManager完成了根据event找到对应的watchers之后
就可以直接调用watcher.process(event)了

但是!!!由于ClientCnxn.EventThread是异步处理的,通过生产消费完成
在processEvent的函数中,要取出一个数据结构Object,既包含watchers集合,又要包含event,所以就把两者组合在一起出现了WatcherSetEventPair 这个类

watcher特性中,"一次性"在server端的体现

在下面几讲WatchManager会讲

ZooKeeper.ZKWatchManager#materialize 里面三个watches的注册是如何完成的

这一块的代码只有三个watches的remove操作
这个在watch机制中会讲

8.问题

ZooKeeper.ZKWatchManager#materialize 为什么碰到case None,所有watches都要被触发

这个目前不是很理解,不知道为什么要这样设计

9.refer

概念
http://www.cnblogs.com/leesf456/p/6286827.html
http://blog.csdn.net/qianshangding0708/article/details/50084155
http://blog.csdn.net/u012291108/article/details/59698624
《paxos到zk》第7章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容