声明:本文转载自http://shift-alt-ctrl.iteye.com/blog/1847320,转载请务必声明。

Watcher机制:目的是为ZK客户端操作提供一种类似于异步获得数据的操作.

1)在创建Zookeeper实例时,允许接收一个watcher参数,此参数将会赋值给watchMnanger.defaultWatcher,成为当前客户端的默认Watcher.需要注意此watcher和其他watcher不同,此wather主要是响应"与链接状态转换"有关的事件(比如,"建立链接","链接关闭"等,参见KeeperState).此默认watcher有zk client本地持有且生命周期伴随整个zookeeper实例,而不是"一次触发即消亡",当Client收到EventType,NONE类型的消息时,则会触发这个"默认wather"被执行..(参见:消息类型)

2)ZKWatchManager是客户端watcher管理器,负责跟踪多种watcher,watcher被分为dataWatches,existWatches,childWatches.每种类型的watcher将会被存在各自的Map中(key为path,value为Set<Watcher>,由此可见,在一个path上一种类型操作重复注册同一个watcher对象,事实上只会生效一次,不同的watcher对象是可以的).记住:这些watcher只是一些存根,由ZKWatchManager负责管理,并不会随请求发送给server,而只会发给server此请求类型是否注册了watch(源码:request.setWatch(boolean))

3)对于setData,exist,getChildren操作,都可以接收boolean类型的watcher标识和Watcher对象,boolean类型告知请求使用defaultWatcher对象注册事件.

4)在ZKDatabase中,包括一个DataTree,此dataTree持有对nodes以及相关的watcher的数据.在server端,WatcherManager是管理client注册的watcher,它只管理dataWatches和childWatches,没有对exist类型的watch.其数据结构为HashSet<path,Set<Watcher>>,和ZKWatchManager一致.(对于exist类型的请求,sever端将其watch加入dataWatches中,这个很好理解)

5)请求到达server之后,在FinalRequestProcessor中,将会处理各种请求,如果检测到request.getWatch()为true,即请求要求注册watch,那么将会把ServerCnxn和path关联起来,加入到WatherManager相应的列表中.

6)客户端的请求响应之后,由SendThread.readResponse()处理响应,如果响应code为成功且此请求中注册了watch,那么将会把此wath添加到响应的watch列表中。

7)ServerCnxn(抽象类)实现了Watcher接口,每个client在server端都对应一个ServerCnxn,此类(子类)是client请求/响应的处理器,不过所有的请求最终还是由一个线程负责通信。在ServerCnxn处理请求时出现异常或者client关闭,将会导致ServerCnxn调用close()方法,此方法中有个分支操作就是从DataTree中的两种watches列表中删除其关联的watch。

8)WatcherManager是server端watch管理器,此类包含2个不同的数据结构用来存储watch以方便查询,其中一个是watch2path为HashMap<Watcher, HashSet<String>>;另一个是watchTable为HashMap<String, HashSet<Watcher>>。其实这2个map保存的数据一样,只是查询的场景不同;这2个map将会被同时操作。

9)DataTree持有2个WatchManager对象,分别为dataWatches用于管理注册data操作的watch,childWatches用于管理注册child操作的watch。

10)WatchManager中还有一个很重要的操作,trigerWatch(String path,EvenType type),当server接受到例如createNode/deleteNode/setData等操作时,将会操作ZKDatabase来操作DataTree中的数据,当然dataTree的数据改动,将会触发相应patch(节点)上的watch(有可能一个操作会导致多种watch被触发),trigerWatch就是在这些时机下被调用。此操作中就是从watchManager中将相应path下注册的watch移除,并依次调用watch.process()。此process()做了一件事情,就是向client发送一个nofication消息,此消息中包含一个WatchEvent对象,此对象封装了事件的类型/path等。

11)客户端接受到nofication,并反序获取WatchEvent,然后和server端的watcherManager一样,ZKWatcherManager根据event类型,从相应的一个或多个watches列表中分别移除相应path的watch,并将这些“移除”的watches再次封装成一个WatcherSetEventPair,此对象持有event和watches集合。最后将此pair加入event队列。

12)client的EventThread将会不断轮询,从event队列中获取pair,并遍历pair中关联的watcher,依次调用watcher的process()方法。。当然此watcher的process方法是client用户自己实现的,因为watcher对象是client用户在实例化zookeeper时包括各种操作时交付给zookeeper的。所以用户应该根据自己的需要,在client受到event时做自己的处理。

F1.Watch生命周期

  1. Zookeeper提供了如下几种可以"注册watch"的操作:exist,getChildren,getData;而对于create,setData,delete是有可能触发"watcher"的操作.
  2. 客户端并不会把用户创建的watcher对象传递给Server,而是传递给server一个标记(boolean值)告知server此请求所涉及到的patch上是否有watcher..
  3. 对于client端请求是队列话的,即一个操作阻塞直到server端响应.(异步操作稍后介绍,它不阻塞)
  4. Server对Client的每个请求的响应体中,都会明确告知此次响应的类型(是正常操作响应还是"事件",操作对应的xid,结果类型,错误信息等等);如果响应体中没有错误信息且其他校验正常的话,我们认为此次请求被正确的执行了.
  5. 可能考虑到在Client与Server端传递wath对象所带来的程序复杂度,ZK采取了"分制"的方式,在Client端和Server端分别采取了不同的技巧来保存Watch列表;(参见上述)
  6. Server在接收Client请求时,会检测此次request体中是否持有watcher信息,如果有,则会导致Server端的watcher列表中新增一个此path关联的watch,只有exist/getChildren/getData会导致此操作.记住watcher信息将会被保存在ZKDatabase中(内存中,而非持久,ZKDatabase会持久Session/ACL/Data).
  7. 那么对于create/setData/delete请求,将会触发watcher列表的检测,比如create操作,创建一个path,在实际的数据存储结束后,将会在watch列表中遍历是否有此path所关联的watches,如果有,则依次触发.
  8. 触发watch其实很简单,对于server端而言,它持有了每个path所关联的watch列表,而且每个watch实例正是一个ServerCnxn对象(每个Client与Server的连接处理器,就是一个ServerCnxn对象),因为触发一个watcher将是便捷性将是显而易见的,直接将此watcher事件所对应的path/类型直接通过IO的方式发送出去;因此哪个Client注册了事件,将会被响应的ServerCnxn处理;集群中每个Server几乎会在同一时间向Client交付事件消息.可能因为网络的问题,不可确保他们能够在极短的时间差内都获得事件.
  9. "插队",是因为对于watcher事件,将不再和其他Client操作放在同一队列中,而是直接通过IO发送,因为ServerCnxn处理client响应是同步的(方法是同步方法),即事件信息将会在当前packet发送之后被立即发送.
  10. 事件一旦被server触发,将会在watcher列表中删除,因此watcher是一次性的(同一个path下的同一类型watcher).我们不能依赖wathcer来全权检测数据的变更,因为网络断开可能会导致事件通知的丢失;当事件被触发之后,server端将删除事件,即使client端再次注册watcher,那么"上一次事件"和"重新注册事件"这段事件内,仍然有可能数据已经变更.(备注:Watcher watch = watchTable.remove(path);watch.process();首先从watchtable中移除watch,然后再将watch信息发送给client端,即使在发送时网络异常,watch也不会再次put到watchTable中,事实上此时watch已经被消费.)
  11. Client接收到Event响应结果之后,将会把此消息体放在eventQueue中,等待EventThread去remove并触发.
  12. EventThread将event队列中的事件,逐个移除并处理,每移除一个event,都会导致Client本地维护的watcher列表删除相应的watcher(根据path和event类型决定),移除之后并获取到Client维护的watcher对象(此对象就是先前的操作中注册的watcher),watcher对象明确了回调方法,此时将会执行watcher.process(),那么调用者的业务方法将会在此刻被执行.[对于业务方法被执行,从整个周期中,我们可以认为是异步的].
  13. 对于节点的create操作,将会触发先前注册的"exist""getChildren"事件被触发;对于节点的delete操作,将会触发先前注册的"exsit""getChildren"事件被触发;对于节点的setData操作,将会触发先前注册的"getData"事件被触发......每个触发的事件都会包含事件的类型(比如:nodeCreate,nodeDelete等),对于用户自定义的watch.process()方法中可以根据事件类型做特定的处理.
  14. 对于Server端遇到session关闭,连接关闭等异常时,都会触发和此连接(ServerCnxn)关联的watch列表.
  15. 不过对于Client端却做了"弥补";"zookeeper.disableAutoWatchReset"这个系统参数的意义就是"是否关闭watch自动重置";如果此参数为false(即为开启"自动重置"),那么在Client端遇到连接异常(比如重连操作)时,都会将本地已有的watcher列表全部发送给Server(此操作称为"setWatches"),如果连接成功,那么新的server仍然会持有watcher列表,接下来事件将会被如期触发,就像网络异常根本就没发生一样..那么为什么ZK没有默认开启此参数呢?可能考虑到这是个双刃剑,Client有可能在网络异常时会做其他的操作(因为网络异常,最终也会触发一个本地的Event,Client可以在此Event中做自定义操作);也有可能在网络异常期间,Cluster中的数据已经被改变,极有可能这些事件中的部分事件已经被错过,即使接下来被触发,也将不能正确的反应目前的现状.如果你期望获得正确的结果,要么重新注册watcher,要么检测现有的数据是否已经改变.

Zookeeper客户端不仅提供了同步操作,还有异步操作,对于create/delete/exist/setData等,ZK分别提供了同步和异步方法,我们上述了解到的,都是同步操作,简单做如下列举:

public Stat exists(String path,Watcher watcher):同步方法,检测path是否存在,如果存在则返回节点的全信息,否则返回null.如果此后此path被创建或者删除,则触发watcher.

public void exist(String path,Watcher watcher,StatCallback cb,Object ctx):这个方法就是异步的,它需要指定一个StatCallback实例,以便在请求被处理之后,异步的执行callback操作.

我相信你一定知道如何将调用过程设计为"异步"[提示:异步即为操作队列话 + callback调用].

在Zookeeper中,同步方法样例:

  1. public ReplyHeader submitRequest(RequestHeader h, Record request,
  2. Record response, WatchRegistration watchRegistration)
  3. throws InterruptedException {
  4. ReplyHeader r = new ReplyHeader();
  5. //将请求加入队列,此队列将会被SendThread操作,并依此发送请求.
  6. Packet packet = queuePacket(h, r, request, response, null, null, null,
  7. null, watchRegistration);
  8. //直接阻塞当前请求
  9. synchronized (packet) {
  10. while (!packet.finished) {
  11. packet.wait();//此处阻塞,直到响应,响应被接受后,会对此packet.notify()调用.
  12. }
  13. }
  14. return r;//返回处理的结果
  15. }

那么对于异步操作,只调用queuePacket(....)将请求添加到队列,然后exist方法就直接返回了.不过在响应被成功接收后,会额外的检测此packet是否有callback,如果有,就立即执行:

  1. private void finishPacket(Packet p) {
  2. if (p.watchRegistration != null) {
  3. p.watchRegistration.register(p.replyHeader.getErr());
  4. }
  5. //此处就是检测callback
  6. if (p.cb == null) {
  7. synchronized (p) {
  8. p.finished = true;
  9. p.notifyAll();
  10. }
  11. } else {
  12. p.finished = true;
  13. eventThread.queuePacket(p);//将异步调用packet添加到事件队列,依此被处理.
  14. }
  15. }

到目前为止,watcher机制我们已经走到"头"了...

【转】Zookeeper-Watcher机制与异步调用原理的更多相关文章

  1. Zookeeper-Watcher机制与异步调用原理

    转载于:http://shift-alt-ctrl.iteye.com/blog/1847320 Watcher机制:目的是为ZK客户端操作提供一种类似于异步获得数据的操作. 1)在创建Zookeep ...

  2. Spring异步调用原理及SpringAop拦截器链原理

    一.Spring异步调用底层原理 开启异步调用只需一个注解@EnableAsync @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTI ...

  3. Direct3D Draw函数 异步调用原理解析

    概述 在D3D10中,一个基本的渲染流程可分为以下步骤: 清理帧缓存: 执行若干次的绘制: 通过Device API创建所需Buffer: 通过Map/Unmap填充数据到Buffer中: 将Buff ...

  4. ZooKeeper Watcher 机制

    前言 在 ZooKeeper 中,客户端可以向服务端注册一个监听器,监听某个节点或者其子节点列表,当监听对象发生变化时,服务端就会向指定的客户端发送通知,这是 ZooKeeper 中的 Watcher ...

  5. 9.4 dubbo异步调用原理

    9.1 客户端发起请求源码.9.2 服务端接收请求消息并发送响应消息源码.9.3 客户端接收响应信息(异步转同步的实现) 分析了dubbo同步调用的源码,现在来看一下dubbo异步调用. 一.使用方式 ...

  6. Zookeeper watcher机制

    一.watcher机制 1.针对每个节点的操作,都会有一个监督者-> watcher 2.当监控的某个对象(znode)发生了变化,则触发watcher事件 3.zk中的watcher是一次性的 ...

  7. Zookeeper Watcher 机制 -- 数据变更通知 ?

    Zookeeper 允许客户端向服务端的某个 Znode 注册一个 Watcher 监听,当服务 端的一些指定事件触发了这个 Watcher,服务端会向指定客户端发送一个事件通 知来实现分布式的通知功 ...

  8. Zookeeper Watcher 机制 -- 数据变更通知 ?

    Zookeeper 允许客户端向服务端的某个 Znode 注册一个 Watcher 监听,当服务 端的一些指定事件触发了这个 Watcher,服务端会向指定客户端发送一个事件通 知来实现分布式的通知功 ...

  9. dubbo异步调用原理 (1)

    此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 一.使用方式 服务提供方不变,调用方代码如下: 1     <dubbo:reference id=& ...

随机推荐

  1. golang环境搭建

    golang环境搭建 好久没写博客了,最近加班好厉害,加到自己都觉得不太适合这个行业了,每天头都是沉甸甸的,可惜今年注定不是收获的季节. 最近忙里偷闲在学习nodejs,赶巧看到golang的文章,一 ...

  2. TOGAF企业连续体和工具之企业连续体构成及架构划分

    TOGAF企业连续体和工具之企业连续体构成及架构划分 又回头看了之前文章的评论,本人也同样感慨这些文章的确像政治课本般的虚无缥缈,所以对费力看完却觉得无从下手的看官致以诚挚的歉意和理解,因为这个问题也 ...

  3. MongoDB学习之--增删改查(1)

    本文是对mongodb学习的一点笔记,主要介绍最简单的增删改操作,初学,看着API,有什么错误,希望大家指正:(使用官方驱动) 1.增 增加操作是最简单的,构造bsonDcument插入即可: 方式1 ...

  4. gcc编译器优化给我们带来的麻烦???

    gcc编译器优化给我们带来的麻烦??? 今天看到一个很有趣的程序,如下: ? 1 2 3 4 5 6 7 8 9 int main() {     const int a = 1;     int * ...

  5. Chrome新的语言API,让您的浏览器说话

    还记得曾经那个能识别语音的API吗?当时一直在想,如果能让浏览器替我念出文字就好了.现在chrome的最新版本Version 33 中您只要按F12然后在console里面输入这段代码,您就能听到浏览 ...

  6. JBoss + EJB3 + MySql : 开发第一个EJB

    JBoss开发Bean并不困难,而对于不知道的人来说,数据库配置才是比较棘手的问题.现在我们就来一步一步开发一个EJB3 + MySql的Bean. 一.MySql数据库的配置 1. 配置数据源 在 ...

  7. [Python]根据地址从maps文件中找相应的库名

    /proc/PID/maps提供了进程的memory layout,下面脚本根据给定地址找出相应的库名: #!/usr/bin/python from __future__ import print_ ...

  8. java 异常处理机制及说明。

    又抄袭了一篇文章,其实就是想保存到自己的博客中而已,文章出处:http://www.cnblogs.com/LilianChen/p/4639471.html 1. 如何捕获异常 try { 可能会出 ...

  9. 手机端H5 header定义样式

    <meta content="width=device-width,initial-scale=1.0, maximum-scale=1.0, user-scalable=0" ...

  10. MongoDB应用案例:使用 MongoDB 存储日志数据

    线上运行的服务会产生大量的运行及访问日志,日志里会包含一些错误.警告.及用户行为等信息,通常服务会以文本的形式记录日志信息,这样可读性强,方便于日常定位问题,但当产生大量的日志之后,要想从大量日志里挖 ...