Zookeeper的watch机制是如何工作的?

2024-08-23 18:52
文章标签 工作 机制 zookeeper watch

本文主要是介绍Zookeeper的watch机制是如何工作的?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

ZooKeeper Watch 概述

ZooKeeper Watch 机制类似于 Java 设计模式中的观察者模式或者监听模式,唯一的不同是不再基于线程间通信,而是基于进程间通信。

ZooKeeper Watch 机制是指,客户端在所有的读命令上告知服务端:这个节点或者子节点变化时通知我,具体来说,支持的写操作有:

  • getData

  • getChildren

  • exists

例如,我们在命令行可以输入 get -w /foo,其中 -w 参数就是用于告知 ZooKeeper 服务端,当前客户端想在 /foo 节点上设置一个监听器。

注意事项:写操作不支持任何形式的 watch 注册。

另一方面,ZooKeeper 支持的事件监听类型与对应的注册方法有:

  • NodeCreated 节点创建:exits()

  • NodeDataChanged 节点数据改变:exits()getData()

  • NodeDeleted 节点删除:exits()getData()getChildren()

  • NodeChildrenChanged 子节点改变:getChildren()

注意事项:自节点数据的改变并不会引发 NodeChildrenChanged 子节点改变事件。

ZooKeeper Watch 机制的两个细节:

  • wactch 是一次性触发的(除了永久递归 watch),如果客户端需要在一个 watch 通知后继续收到相同节点的 watch 通知,那么必须再次注册 watch 一次

  • 服务端发给客户端的 watch 通知并不包含具体的节点数据,其起到的作用非常存粹:告知客户端其关注的节点发生了 watch 事件

关于 ZooKeeper Watch ,我们需要解决如下模型的实现:

模型如下图所示:

  • 服务端

    • 如何为带有 watch 的读请求进行事件注册;

    • 在节点的写操作发送时,如何触发事件,将事件通知发送给客户端;

  • 客户端

    • 命令的发送、注册用与序列化发送;

    • 事件的监听与回调;

模型如下图所示:

这里的要点是:无论是客户端还是服务端,只有将 Watcher 进行注册,才能在事件发送时进行回调,否则不进行回调。

Watcher实现原理 

client端连接后会注册一个事件,然后客户端会保存这个事件,通过zkWatcherManager保存客户端的事件注册,通知服务端Watcher为true,然后服务端会通过WahcerManager会绑定path对应的事件。如下图:

一个Watcher工作主要包含三步:客户端注册watcher、服务端处理watcher和客户端回调watcher事件。

客户端注册watcher 

在通过客户端接口调用时,可以指定一个watcher接口,以接受服务器事件的回调。在注册watcher接口后,客户端首先会对当前客户端请求request进行标记,将其设置为“使用watcher监听”,同时会封装一个watcher的注册信息watchRegistration对象,用于暂时保存数据数据节点和watcher的对象关系。由于zookeeper中的最小通讯单元为packet,因此,在clientCnxn中watchregistration又会被封装到Packet中,然后放入发送队列中等待客户端发送。随后,客户端会想服务端发送请求,并等待请求返回。完成请求之后,客户端的SendThread线程的readresponse方法负责接收服务端的请求,并将接收到的packet中的watcher注册到ZKWatcherManager中,并最终保存到dataWatchers中,用于服务端事件的回调的时候获取对应节点的对应watcher事件。

用户调用exists/getData/getChildren注册监听以后,会做几个事情

1.将请求数据封装为packet,添加到outgoingQueue队列中

2.SendThread这个线程会执行数据发送操作,主要是将outgoingQueue队列中的数据发送到服务端

3.通过clientCnxnSocket.doTransport(to,pendingQueue,ClientCnxn.this);其中ClientCnxnSocket是zookeeper客户端和服务端的连接通信的封装,有两个具体的实现类ClientCnxnSocketNetty和ClientCnxnSocketNIO;具体使用哪一个类来实现发送,是在初始化过程是在实例化Zookeeper的时候设置的,默认是ClientCnxnSocketNIO这个类

4.基于第3步,最终会在ClientCnxnSocketNetty方法中执行sendPkt将请求的数据包发送到服务端

服务端处理watcher 

其实客户端注册的watcher并不会传递到服务端,只是在客户端进行了watcher的保存,所以服务端接受到watch注册事件之后,需要将该事件进行封装,在服务端也进行保存。

ServerCnxn存储

ServerCnxn是服务端与客户端进行网络交互的一个NIO接口,代表了客户端与服务端的连接。其底层采用netty实现。所以,在接受到注册请求之后,服务端会将ServerCnxn对象和数据阶段路径保存到WatchManager的watchTable和watch2Paths中。方便事件触发时的调用。

watcher触发

当指定的节点发生相关的事件时,通过调用WatchManager的triggerWatch方法触发相关的事件。其通过将节点信息和事件类型进行封装成为watchedevent,并查找到到对应节点的注册的watcher,然后分别调用watcher的回调函数process。而在process函数中其实就是通过封装的ServerCnxn向客户端发送watchedevent数据请求。具体的watcher业务处理则在客户端处理。

客户端回调watcher事件 

SendThread是客户端开启的与服务端建立TCP长连接的线程,它会一直保持连接状态,采用心跳监测的方式确保与服务端的连接存活。

在客户端的SendThread中,当接收到服务端请求之后,会将请求反序列化成watchedevent对象,并将watchedevent对象转换为watcherevent对象,最后将watcherevent对象添加到EventThread线程,完成对watcher的回调。

EventThread是客户端的一个专门处理watcher时间的线程,其保持了一个待处理事件的队列。它根据传递的事件的类型和节点信息,从客户端的ZKWatcherManager中取出相关的watcher,将其添加到EventThread事件队列中,并在去run方法中不断取出watcher事件进行处理。

这里需要注意是:(1)事件注册是一次性的,因为在每次处理事件之后,就会将相应的watcher注册删除;(2)客户端接收到的服务端watcher事件中并不包含事件更改的具体内容,只是告知发生了这样一个watcher事件,所以,客户端在接收到watcher事件之后,需要在回调函数process中对服务端的数据进行重新获取,才能获得更改的具体内容。

ZooKeeper Watch 机制的特性总结

ZooKeeper 的 Watch 有如下几个特性需要注意:

  • 一次性

    无论是服务端还是客户端,一旦一个 Watcher 被触发,ZooKeeper都会将其从相应的存储中移除。因此,开发人员在 Watcher 的使用上要记住的一点是需要反复注册。这样的设计有效地减轻了服务端的压力。试想,如果注册一个 Watcher 之后一直有效,那么,针对那些更新非常频繁的节点,服务端会不断地向客户端发送事件通知,这无论对于网络还是服务端性能的影响都非常大。

    需要注意的是:ZooKeeper 在 3.6.0 版本中引入了永久 Watcher 机制,利用这个机制可以避免反复注册 Watcher。

  • 客户端串行执行

    客户端 Watcher 回调的过程是一个串行同步的过程,这为我们保证了顺序,同时,需要开发人员注意的一点是,千万不要因为一个 Watcher 的处理逻辑影响了整个客户端的 Watcher 回调。

  • 轻量

    WatchedEvent 是 ZooKeeper 整个 Watcher 通知机制的最小通知单元,这个数据结构中只包含三部分内容:通知状态、事件类型和节点路径。也就是说,Watcher 通知非常简单,只会告诉客户端发生了事件,而不会说明事件的具体内容。例如针对 NodeDataChanged 事件,ZooKeeper 的 Watcher 只会通知客户端指定数据节点的数据内容发生了变更,而对于原始数据以及变更后的新数据都无法从这个事件中直接获取到,而是需要客户端主动重新去获取数据。这也是ZooKeeper 的 Watcher机制的一个非常重要的特性。

    另外,客户端向服务端注册 Watcher 的时候,并不会把客户端真实的 Watcher 对象传递到服务端,仅仅只是在客户端请求中使用 boolean 类型属性进行了标记,同时服务端也仅仅只是保存了当前连接的 ServerCnxn 对象。如此轻量的 Watcher 机制设计,在网络开销和服务端内存开销上都是非常廉价的。

这篇关于Zookeeper的watch机制是如何工作的?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1100221

相关文章

C++如何通过Qt反射机制实现数据类序列化

《C++如何通过Qt反射机制实现数据类序列化》在C++工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作,所以本文就来聊聊C++如何通过Qt反射机制实现数据类序列化吧... 目录设计预期设计思路代码实现使用方法在 C++ 工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作。由于数据类

SpringRetry重试机制之@Retryable注解与重试策略详解

《SpringRetry重试机制之@Retryable注解与重试策略详解》本文将详细介绍SpringRetry的重试机制,特别是@Retryable注解的使用及各种重试策略的配置,帮助开发者构建更加健... 目录引言一、SpringRetry基础知识二、启用SpringRetry三、@Retryable注解

SpringKafka错误处理(重试机制与死信队列)

《SpringKafka错误处理(重试机制与死信队列)》SpringKafka提供了全面的错误处理机制,通过灵活的重试策略和死信队列处理,下面就来介绍一下,具有一定的参考价值,感兴趣的可以了解一下... 目录引言一、Spring Kafka错误处理基础二、配置重试机制三、死信队列实现四、特定异常的处理策略五

Go 语言中的select语句详解及工作原理

《Go语言中的select语句详解及工作原理》在Go语言中,select语句是用于处理多个通道(channel)操作的一种控制结构,它类似于switch语句,本文给大家介绍Go语言中的select语... 目录Go 语言中的 select 是做什么的基本功能语法工作原理示例示例 1:监听多个通道示例 2:带

Java使用Curator进行ZooKeeper操作的详细教程

《Java使用Curator进行ZooKeeper操作的详细教程》ApacheCurator是一个基于ZooKeeper的Java客户端库,它极大地简化了使用ZooKeeper的开发工作,在分布式系统... 目录1、简述2、核心功能2.1 CuratorFramework2.2 Recipes3、示例实践3

java中反射(Reflection)机制举例详解

《java中反射(Reflection)机制举例详解》Java中的反射机制是指Java程序在运行期间可以获取到一个对象的全部信息,:本文主要介绍java中反射(Reflection)机制的相关资料... 目录一、什么是反射?二、反射的用途三、获取Class对象四、Class类型的对象使用场景1五、Class

kotlin中的模块化结构组件及工作原理

《kotlin中的模块化结构组件及工作原理》本文介绍了Kotlin中模块化结构组件,包括ViewModel、LiveData、Room和Navigation的工作原理和基础使用,本文通过实例代码给大家... 目录ViewModel 工作原理LiveData 工作原理Room 工作原理Navigation 工

Nginx之upstream被动式重试机制的实现

《Nginx之upstream被动式重试机制的实现》本文主要介绍了Nginx之upstream被动式重试机制的实现,可以通过proxy_next_upstream来自定义配置,具有一定的参考价值,感兴... 目录默认错误选择定义错误指令配置proxy_next_upstreamproxy_next_upst

Spring排序机制之接口与注解的使用方法

《Spring排序机制之接口与注解的使用方法》本文介绍了Spring中多种排序机制,包括Ordered接口、PriorityOrdered接口、@Order注解和@Priority注解,提供了详细示例... 目录一、Spring 排序的需求场景二、Spring 中的排序机制1、Ordered 接口2、Pri

MySQL 缓存机制与架构解析(最新推荐)

《MySQL缓存机制与架构解析(最新推荐)》本文详细介绍了MySQL的缓存机制和整体架构,包括一级缓存(InnoDBBufferPool)和二级缓存(QueryCache),文章还探讨了SQL... 目录一、mysql缓存机制概述二、MySQL整体架构三、SQL查询执行全流程四、MySQL 8.0为何移除查