什么是 ZK Watcher

基于 ZK 的应用程序的一个常见需求是需要知道 ZK 集合的状态。为了达到这个目的,一种方法是 ZK 客户端定时轮询 ZK 集合,检查系统状态是否发生了变化。然而,轮询并不是一种高效的方式,尤其是在状态变化的发生频率很低的时候

因此,ZK 提供了一种通过通知客户端感兴趣的具体时间来避免轮询造成的性能问题的方式,即设置 Watcher 的方式。通过设置 Watcher,ZK 客户端可以对指定的 znode 注册一个通知请求,在 znode 发生变化时收到一个单次的通知。例如,在 znode 被删除时向 Watcher 发送节点被删除的通知

应用 ZK Watcher 的代码通常遵循如下的框架

zk.exists("myZnode", myWatcher, existsCallback, null);

Watcher myWatcher new Watcher() {
public void process(WatchedEvent event) {
// process the watch event
}
} StatCallback existsCallback = new StatCallback() {
public void processResult(int rc, String path, Object ctx, Stat stat) {
// process the result of the exists call
}
}

上面的代码框架中以 exists 操作为例,展示了异步调用 ZK 操作并注册 Watcher 的一般用法

WatchedEvent 的分类

Watcher 的使用一个重要的内容就是了解 Watcher 如何设置以及何时触发,并不是所有的 ZK 操作都可以设置 Watcher,Watcher 也不是会被所有事件触发

抛开被重载的连接状态的 WatchedEvent,业务过程中会遇到的 WatchedEvent 分为以下几种

  • NodeCreated - 可以通过 exists 调用设置 Watcher,在 znode 从无到有创建的时候被触发
  • NodeDeleted - 可以通过 exists 或者 getData 调用设置 Watcher,在 znode 被删除时触发
  • NodeDataChanged - 可以通过 exists 或者 getData 调用设置 Watcher,在 znode 数据发生变化时触发
  • NodeChildrenChanged - 可以通过 getChildren 调用设置 Watcher,在 znode 的直接子节点创建或删除时触发
  • DataWatchRemoved - 在 exists 或者 getData 设置的 Watcher 被删除时触发对应的 Watcher
  • ChildWatchRemoved - 在 getChildren 设置的 Watcher 被删除时触发对应的 Watcher

可以看到,只有 exists 和 getData 和 getChildren 三种操作能够设置 Watcher

注意,getData 创建的 Watcher 不会接收到 NodeCreated 事件,这是因为 getData 在节点不存在的时候会抛出 KeeperException.NoNodeException 异常,而不会设置 Watcher

Watcher 机制的实现与生命周期

从应用程序的角度来讲,注册完 Watcher 之后只要等待事件被触发即可,无需关心 ZK 是怎么实现这个过程的。不过了解 ZK 的具体实现机制有助于我们在面对错误或者异常的时候更好的理解问题的出处以及针对性的排查问题

Watcher 机制的实现最重要的问题就是 Watcher 究竟是注册在哪里的,以及 Watcher 究竟是如何触发的。这两个问题很难分开来解释,因此下文会一并分析

原本讲解原理的部分最好是结合对应的源代码摘要来讲解,但是 ZK 的源码实在是难以阅读,贴在这里不但不能帮助理解,恐怕会让读者更加一头雾水。我会从伪代码的粒度介绍代码逻辑并附上对应的源文件位置,有兴趣的同学可以自行阅读,祝身体健康

Watcher 机制的实现要从注册讲起,ZK 客户端在执行 exists 或 getData 或 getChildren 操作的时候,可以设置一个自定义的 Watcher 或者通过 flag 复用创建客户端时设置的 Watcher。后者实践中比较少用,不做过多介绍。这个 Watcher 会被打包成 Packet 放进 ClientCnxnEventThread 中,在对应的操作完成时登记到客户端的 Watches 集合里。在服务端,对应的 GetDataRequest 等请求有一个是否设置了 Watcher 的 flag,服务端由此来判断是否要设置相应的 Watcher。这里,ZK 为 ServerCnxn 实现了 Watcher 接口。ServerCnxn 是每个服务端上对于客户端的连接对象,它的 process(WatchedEvent) 方法就是将对应的 WatchedEvent 打包为 WatcherEvent 然后发送给客户端

Watcher 成功设置后需要关心的就是 Watcher 的触发了,本质上 Watcher 是在 ZK 集合发生状态变化的时候在客户端回调对应处理逻辑的。但是 ZK 集合发生状态变化要以服务端的状态为准,服务器维护了 ZK 集合的状态,这主要是由 ZKDatabaseDataTree 来实现的。当服务器判断发生了需要出发 Watcher 的状态变化时,服务器会遍历异动节点上对应的 Watcher,在这里就是对应的客户端连接,回调它们的 process(WatchedEvent) 方法。如上所述,这就向客户端发送了一个对应的 WatchedEvent

上面介绍的 Watcher 注册和触发的过程实际上就囊括了 Watcher 的整个生命周期,即 Watcher 的生命周期由对应操作在客服端成功时开始,到触发后结束。也就是说,Watcher 是单次触发的,触发之后还想再次监听对应节点的状态需要重新设置 Watcher。Watcher 的生命周期结束还有另一个触发条件,即 session 被关闭或过期。此外,在 3.5.0 之后的版本中,ZK 能够主动执行 removeWatches 操作来移除不再感兴趣的节点。

Watcher 的错误处理

如前所述,Watcher 是一种轻量级的相应变化的通知机制。由于其功能简单,在实际应用当中为了构建更加复杂的语义,我们需要对 Watcher 在一些故障条件下的响应做对应的讨论。

其中第一个是单次触发和 WatchedEvent 携带的信息带来的问题。由于 Watcher 是单次触发的,所以我们可能会丢掉在前一个 Watcher 触发后到后一个 Watcher 重新设置之前的事件。通常来说这不是问题,因为 ZK 的目标是实现一个分布式环境下对状态达成共识的存储,而不保证每个事件都被客户端记录和处理。重新设置 Watcher 时附带的动作足以保证我们同步了当时的最新状态。因此,我们虽然漏掉了事件,但是那充其量只是一个中间状态,ZK 提供的保证是关于一段时间内的最终状态的。但是换个角度讲,由于 WatchedEvent 只包含了【事件发生了】这个信息,所以任何新的状态都需要重新从 ZK 集合上获取,这是 ZK 为了实现的简单在当初做的一个 trade-off

其中第二个是关于 CONNECTIONLOSS 异常的。严格来说这并不是 Watcher 应该关心的事情,因为操作由于 CONNECTIONLOSS 失败时 Watcher 是无法被成功设置上去的。CONNECTIONLOSS 异常意味着客户端和正在连接的服务器断开连接,由于 ZK 服务端有若干个服务器,在这种情况下客户端会尝试连接其他的服务器。但是在这种情况下,由于 Watcher 没有被成功设置,因此在重新连接成功后,应当重试刚才的操作,以正确的设置 Watcher。此外,此前已经成功设置的 Watcher 不会受到这种连接移动的影响,这是因为客户端重连服务端时会将所有 Watcher 重新发送一遍,服务端比对 znode 状态和 zxid 的相对值,推断出需要触发的 Watcher 进行触发,其他 Watcher 正常设置

小结

ZK 的 Watcher 机制正常流程还是比较顺畅的,但是 Watcher 触发后需要主动再次拉去状态这一点还是比较麻烦的,而且 ZK 的操作会出现各种各样诡异的异常。关于 ZK 在网络延迟或分区的情况下各种异常的处理,会有单独的一篇文章来介绍。此外,ZK 的源代码对身体有害,建议除了催吐最好不要闲着没事去看

ZK Watcher 的原理和实现的更多相关文章

  1. zk的watcher机制的实现

    转载:https://www.ibm.com/developerworks/cn/opensource/os-cn-apache-zookeeper-watcher/ http://blog.csdn ...

  2. Zookeeper——Watcher原理详解

    文章目录 引言 正文 一.如何注册监听 二.如何触发监听事件 三.事件类型有哪些 四.Watcher可以被无限次触发么?为什么要这么设计? 五.Watcher实现原理 1. 客服端发送请求 a. 初始 ...

  3. Zookeeper原理与Curator使用

    近期打算实现一个基于Zookeeper的分布式的集群状态一致性控制, 对Zookeeper的原理不太了解, 正好学习一下, 网上找到了几篇文章, 先贴在这边, 等我熟读官方文档后, 再来补充自己的见解 ...

  4. zookeeper系列之异步通知模式-Watcher

    1 watcher种类和事件种类 Watcher种类 1. zookeeper实例化时注入的默认Watcher 2. dataWatchers 一个Map<string Set<Watch ...

  5. Java分布式锁看这篇就够了

    ### 什么是锁? 在单进程的系统中,当存在多个线程可以同时改变某个变量(可变共享变量)时,就需要对变量或代码块做同步,使其在修改这种变量时能够线性执行消除并发修改变量. 而同步的本质是通过锁来实现的 ...

  6. curator 分布式锁InterProcessMutex

    写这篇文章的目的主要是为了记录下自己在zookeeper 锁上踩过的坑,以及踩坑之后自己的一点认识; 从zk分布式锁原理说起,原理很简单,大家也应该都知道,简单的说就是zookeeper实现分布式锁是 ...

  7. zookeeper项目使用几点小结

    背景 前段时间学习了zookeeper后,在新的项目中刚好派上了用场,我在项目中主要负责分布式任务调度模块的开发,对我自己来说是个不小的挑战. 分布式的任务调度,技术上我们选择了zookeeper,具 ...

  8. Zookeeper 入门第一篇

    转载原文地址: ZooKeeper学习总结 第一篇:ZooKeeper快速入门 ZooKeeper学习总结 第二篇:ZooKeeper深入探讨 ZooKeeper学习第一期---Zookeeper简单 ...

  9. 基于zookeeper实现分布式配置中心(一)

    最近在学习zookeeper,发现zk真的是一个优秀的中间件.在分布式环境下,可以高效解决数据管理问题.在学习的过程中,要深入zk的工作原理,并根据其特性做一些简单的分布式环境下数据管理工具.本文首先 ...

随机推荐

  1. 【openmp】for循环的break问题

    问题描述:在用openmp并行化处理for循环的时候,便无法在for循环中用break语句,那么我们如何实现这样的机制呢?在stackoverflow上看到一个不错的回答总结一下. volatile ...

  2. Spark安装与部署

    1.首先安装scala(找到合适版本的具体地址下载) 在/usr/local/目录下 wget https://www.scala-lang.org/download/**** 2.安装spark ( ...

  3. springboot整合solr

    上一篇博客中简要写了solr在windows的安装与配置,这一篇接上文写一下springboot整合solr,代码已经上传到github,传送门. 1.新建core并配置schema 上篇博客中已经有 ...

  4. SpringBoot中快速实现邮箱发送

    前言 在许多企业级项目中,需要用到邮件发送的功能,如: 注册用户时需要邮箱发送验证 用户生日时发送邮件通知祝贺 发送邮件给用户等 创建工程导入依赖 <!-- 邮箱发送依赖 --> < ...

  5. Redis集群环境下的键值空间监听事件实现方案

    一直想记录工作中遇到的问题和解决的方法,奈何没有找到一方乐土,最近经常反思,是否需要记录平时的点滴,后台还是决定下定决心记录一些,以便以后用到的时候找不着,实现这样的一个功能主要也是业务所需要的. 需 ...

  6. DOM的选择器

    这几天学习了DOM的选择器,现在来进行一下总结分类. 1.DOM里的元素节点选择器 元素节点选择器包括id,class,name,tagname,高级,关系. 1.1 id选择器 id:返回的是单个对 ...

  7. 1.Sentinel源码分析—FlowRuleManager加载规则做了什么?

    最近我很好奇在RPC中限流熔断降级要怎么做,hystrix已经1年多没有更新了,感觉要被遗弃的感觉,那么我就把眼光聚焦到了阿里的Sentinel,顺便学习一下阿里的源代码. 这一章我主要讲的是Flow ...

  8. 吃货眼中的sqlalchemy外键和连表查询

    前言 使用数据库一个高效的操作是连表查询,一条查询语句能够查询到多个表的数据.在sqlalchem架构下的数据库连表查询更是十分方便.那么如何连表查询?以及数据库外键对连表查询有没有帮助呢?本篇文章就 ...

  9. 约瑟夫环问题:有n个人围成一圈,顺序排号。从第一个人开始报数(从1到3报数),凡报到3的人退出圈子,问最后留下的是原来第几号的那位。

    首先,我最大的学习来源不是百度而是我群友~~在这里表白一波我热爱学习的群友们!然后今天群里突然有人提出了题目的这个问题:有n个人围成一圈,顺序排号.从第一个人开始报数(从1到3报数),凡报到3的人退出 ...

  10. 怎样用QT在windows环境通过编程卸载installShield打包的程序

    通常情况下,如果是一个完备的软件,我们可以通过启动软件的uninstaller.exe之类的程序来完成卸载.但是使用installShield打包的程序,本身可能并不含有这类卸载程序.此时,我们可以通 ...