Zookeeper 允许客户端向服务端的某个 Znode 注册一个 Watcher 监听,当服务端的一些指定事件触发了这个 Watcher,服务端会向指定客户端发送一个事件通知来实现分布式的通知功能,然后客户端根据 Watcher 通知状态和事件类型做出业务上的改变。
~
本篇内容包括:关于观察者模式、Zookeeper 事件监听和通知机制、Zookeeper 工作流程
文章目录
一、关于观察者模式
1、观察者模式
观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新。观察者模式属于行为型模式,行为型模式关注的是对象之间的通讯,观察者模式就是观察者和被观察者之间的通讯。观察者模式有一个别名叫“发布-订阅模式”,或者说是“订阅-发布模式”,订阅者和订阅目标是联系在一起的,当订阅目标发生改变时,逐个通知订阅者。
- 作用:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
- 优点:
- 观察者和被观察者是抽象耦合的
- 建立一套触发机制
- 缺点
- 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
- 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
- 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
2、发布-订阅模式
发布-订阅模式并不属于 24 种基本的设计模式,起初只是观察者模式的一个别称,但是经过时间的沉淀,似乎他已经强大了起来,已经独立于观察者模式,成为另外一种不同的设计模式。
现在的发布订阅模式中,称为发布者的消息发送者不会将消息直接发送给订阅者,这意味着发布者和订阅者不知道彼此的存在。在发布者和订阅者之间存在第三个组件,称为消息代理或调度中心或中间件,它维持着发布者和订阅者之间的联系,过滤所有发布者传入的消息并相应地分发它们给订阅者。
- 作用:发布-订阅模式是前端常用的一种设计模式,现在主流的 MVVM 框架,都大量使用了此设计模式,其主要作用有以下两点:
- 可以实现模块间通信
- 可以在一定程度上实现异步编程
- 优点:
- 支持简单的广播通信,当对象状态发生改变时,会自动通知已经订阅过的对象(我们日常工作中也经常使用到,比如我们的 ajax 请求,请求有 success 和 error 的回调函数,我们可以订阅 ajax 的 success 和 error 事件。我们并不关心对象在异步运行的状态,我们只关心 success 的时候或者 error 的时候我们要做点我们自己的事情就可以了~)。
- 发布者与订阅者耦合性降低,发布者只管发布一条消息出去,它不关心这条消息如何被订阅者使用,同时,订阅者只监听发布者的事件名,只要发布者的事件名不变,它不管发布者如何改变
- 缺点
- 创建订阅者需要消耗一定的时间和内存。
- 虽然可以弱化对象之间的联系,如果过度使用的话,反而使代码不好理解及代码不好维护等等。
3、Zookeeper 中的观察者模式
zookeeper 从设计模式上来看,是一个基于观察者模式设计(或者说“发布-订阅模式”更为贴切)的分布式服务管理框架,它负责存储和管理大家都关心的数据,然后接受观察者的注册,一旦这些数据的状态发生了变化,Zookeeper 就将负责通知已经在 Zookeeper 上注册的观察者做出相应的反应,从而实现集群中类似 Master/slave 管理模式。
二、Zookeeper 事件监听和通知机制
1、Zookeeper Watcher 机制
Zookeeper 允许客户端向服务端的某个 Znode 注册一个 Watcher 监听,当服务端的一些指定事件触发了这个 Watcher,服务端会向指定客户端发送一个事件通知来实现分布式的通知功能,然后客户端根据 Watcher 通知状态和事件类型做出业务上的改变。
Watcher 基于 Zookeeper 上创建的 Znode 节点,可以对这些节点绑定监听事件,比如可以监听节点数据变更、节点删除、子节点状态变更等事件,通过这个事件机制,可以基于 Zookeeper 实现分布式锁,发布订阅(多个订阅者同时监听某一个主题对象,当这个主题对象自身状态发生变化时,会通知所有订阅者)等功能。
2、Watcher 特性
当数据发生变化的时候, zookeeper 会产生一个 watcher 事件,并且会发送到客户端。但是客户端只会收到一次通知。如果后续这个节点再次发生变化,那么之前设置 watcher 的客户端不会再次收到消息。(Watcher 是一次性的操作,当然,可以通过循环监听去达到永久监听效果)。
- 一次性:watcher 是一次性的,一旦触发就会被移除,再次使用时需要重新注册;
- 客户端顺序回调:watcher 回调是顺序串行执行的,只有回调后客户端才能看到最新的数据状态,一个 watcher 回调逻辑不应太多。以免影响其他回调 watcher 执行;
- 轻量级:WatchEvent 是最小的通信单位,结构上只包含通知状态、事件类型和节点路径,并不会告诉姐点变化的前后具体内容;
- 实效性:watcher 只有在当前 session 彻底失效时才会无效,若在 session 有效期内快速重连成功,则 watcher 依然存在,仍可接收到通知。
三、Zookeeper 工作流程
ZooKeeper 的 Watcher 机制,总的来说可以分为三个过程:客户端注册 Watcher、服务器处理 Watcher 和客户端回调 Watcher 客户端。
-
客户端注册 Watcher 到服务端;
-
服务端发生数据变更;
-
服务端通知客户端数据变更;
-
客户端回调 Watcher 处理变更应对逻辑;
1、客户端注册 Watcher
Zookeeper 中注册 watcher的 接口大概有如下几个:
- 建立 Zookeeper 连接时传入的 watcher;
- 通过 gtData、exists、getChildren; 来设置watcher,而它们又各有同步和异步两种形式。Zookeeper 的所有读操作都可以设置 watch 监视点: getData, getChildren, exists. 写操作则是不能设置监视点的。
监视有两种类型:数据监视点和子节点监视点。创建、删除或者设置znode都会触发这些监视点。exists,getData 可以设置数据监视点。getChildren 可以设置子节点变化。而可能监测的事件类型有: None、NodeCreated、NodeDataChanged、NodeDeleted、NodeChildrenChanged。
None // 客户端连接状态发生变化的时候 会收到None事件
NodeCreated // 节点创建事件
NodeDeleted // 节点删除事件
NodeDataChanged // 节点数据变化
NodeChildrenChanged // 子节点被创建 删除触发该事件
# 客户端注册流程:
- 调用 getData()/getChildren()/exist()三个 API,传入 Watcher 对象
- 标记请求 request,封装 Watcher 到 WatchRegistration
- 封装成 Packet 对象,发服务端发送 request
- 收到服务端响应后,将 Watcher 注册到 ZKWatcherManager 中进行管理
- 请求返回,完成注册。
2、服务器处理 Watcher
# 服务端接收 Watcher 并存储
接收到客户端请求,处理请求判断是否需要注册 Watcher,需要的话将数据节点的节点路径和 ServerCnxn(ServerCnxn 代表一个客户端和服务端的连接,实现了 Watcher 的 process 接口,此时可以看成一个 Watcher 对象)存储在WatcherManager 的 WatchTable 和 watch2Paths 中去。
# Watcher 触发
以服务端接收到 setData() 事务请求触发 NodeDataChanged 事件为例:
- 封装 WatchedEvent:将通知状态(SyncConnected)、事件类型(NodeDataChanged)以及节点路径封装成一个 WatchedEvent 对象
- 查询 Watcher:从 WatchTable 中根据节点路径查找 Watcher
- 没找到:说明没有客户端在该数据节点上注册过 Watcher
- 找到:提取并从 WatchTable 和 Watch2Paths 中删除对应 Watcher(从这里可以看出 Watcher 在服务端是一次性的,触发一次就失效了)
# 调用 process 方法来触发 Watcher
这里 process 主要就是通过 ServerCnxn 对应的 TCP 连接发送 Watcher 事件通知。
3、客户端回调 Watcher
客户端 SendThread 线程接收事件通知,交由 EventThread 线程回调 Watcher。