zookeeper的Watch机制

本文深入解析Zookeeper的Watch机制,探讨其一次性触发器特性、异步通知机制及数据监视和子节点监视的区别。总结了各种操作触发的事件类型,并提供了一个实现永久监听的示例代码。

1.  zookeeper的Watch机制

      一个zk的节点可以被监控,包括这个目录中存储的数据的修改,子节点目录的变化,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的集中管理,集群管理,分布式锁等等。

       watch机制官方说明:一个Watch事件是一个一次性的触发器,当被设置了Watch的数据发生了改变的时候,则服务器将这个改变发送给设置了Watch的客户端,以便通知它们。

2. zookeeper机制的特点 

   1)  一次性的触发器(one-time trigger)    

       当数据改变的时候,那么一个Watch事件会产生并且被发送到客户端中。但是客户端只会收到一次这样的通知,如果以后这个数据再次发生改变的时候,之前设置Watch的客户端将不会再次收到改变的通知,因为Watch机制规定了它是一个一次性的触发器。           

         当设置监视的数据发生改变时,该监视事件会被发送到客户端,例如,如果客户端调用了 getData("/znode1", true) 并且稍后 /znode1 节点上的数据发生了改变或者被删除了,客户端将会获取到 /znode1 发生变化的监视事件,而如果 /znode1 再一次发生了变化,除非客户端再次对 /znode1 设置监视,否则客户端不会收到事件通知。

  2)发送给客户端(Sent to the client)     

       这个表明了Watch的通知事件是从服务器发送给客户端的,是异步的,这就表明不同的客户端收到的Watch的时间可能不同,但是ZooKeeper有保证:当一个客户端在看到Watch事件之前是不会看到结点数据的变化的。例如:A=3,此时在上面设置了一次Watch,如果A突然变成4了,那么客户端会先收到Watch事件的通知,然后才会看到A=4。

       Zookeeper 客户端和服务端是通过 Socket 进行通信的,由于网络存在故障,所以监视事件很有可能不会成功地到达客户端,监视事件是异步发送至监视者的,Zookeeper 本身提供了保序性(ordering guarantee):即客户端只有首先看到了监视事件后,才会感知到它所设置监视的 znode 发生了变化(a client will never see a change for which it has set a watch until it first sees the watch event). 网络延迟或者其他因素可能导致不同的客户端在不同的时刻感知某一监视事件,但是不同的客户端所看到的一切具有一致的顺序。

   3)被设置Watch的数据(The data for which the watch was set)

         这意味着 znode 节点本身具有不同的改变方式。你也可以想象 Zookeeper 维护了两条监视链表:

       数据监视和子节点监视(data watches and child watches) 

       getData() and exists() 设置数据监视,getChildren() 设置子节点监视。 或者,你也可以想象 Zookeeper 设置的不同监视返回不同的数据,getData() 和 exists() 返回 znode 节点的相关信息,而 getChildren() 返回子节点列表。

       因此, setData() 会触发设置在某一节点上所设置的数据监视(假定数据设置成功),而一次成功的 create() 操作则会出发当前节点上所设置的数据监视以及父节点的子节点监视。一次成功的 delete() 操作将会触发当前节点的数据监视和子节点监视事件,同时也会触发该节点父节点的child watch。

3.各种watch触发的情况总结

       可以注册watcher的方法:getData、exists、getChildren。

       可以触发watcher的方法:create、delete、setData。连接断开的情况下触发的watcher会丢失。

       一个Watcher实例是一个回调函数,被回调一次后就被移除了。如果还需要关注数据的变化,需要再次注册watcher。

         New ZooKeeper时注册的watcher叫default watcher,它不是一次性的,只对client的连接状态变化作出反应。

      什么样的操作会产生什么类型的事件:

 

 event For “/path”event For “/path/child”
create(“/path”)EventType.NodeCreated
delete(“/path”)EventType.NodeDeleted
setData(“/path”)EventType.NodeDataChanged
create(“/path/child”)EventType.NodeChildrenChanged(getChild)EventType.NodeCreated
delete(“/path/child”)EventType.NodeChildrenChanged(getChild)EventType.NodeDeleted
setData(“/path/child”)EventType.NodeDataChanged

      事件类型与watcher的对应关系:

 

event For “/path”

Default Watcher

exists(“/path”)

getData(“/path”)

getChildren(“/path”)
EventType.None
EventType.NodeCreated  
EventType.NodeDeleted  
EventType.NodeDataChanged  
EventType.NodeChildrenChanged   

   本表总结:exits和getData设置数据监视,而getChildren设置子节点监视

      操作与watcher的对应关系:  

 

 exits("/path")getData(“/path”)getChildren(“/path”)exits("/path/child")getData(“/path/child”)getChildren(“/path/child”)
create(“/path”)会报错   
delete(“/path”)√(这个要注意)   
setData(“/path”)    
create(“/path/child”)   
delete(“/path/child”)  
setData(“/path/child”)    
       

值得注意的是:getChildren("/path")监视/path的子节点,如果(/path)自己删了,也会触发NodeDeleted事件。

4.实现永久监听

       由于zookeeper是一次性监听,所以我们必须在wather的process方法里面再设置监听。一个方法如下:

       以下逻辑是实现的是生产者和消费者模型,消费者监听某一路径下面子节点的变化,当生产者有消息发送过来的时候,在该节点下面创建一个子节点,然后把消息放到该子节点里面,这会触发消费者的process方法被调用,然后消费者取到该节点下面的子节点(顺便设置一个再监听该节点的子节点),然后取出子节点的内容,做处理,然后删除该子节点。

public void process(WatchedEvent event) {
   // TODO Auto-generated method stub
   if (event.getState() == KeeperState.SyncConnected) {
      System.out.println("watcher received event");
      countDownLatch.countDown();
   }
   System.out.println("回调watcher1实例: 路径" + event.getPath() + " 类型:"+ event.getType());
   // 事件类型,状态,和检测的路径
   EventType eventType = event.getType();
   KeeperState state = event.getState();
   String watchPath = event.getPath();
   switch (eventType) {
      case NodeCreated:
        break;
      case NodeDataChanged:
        break;
      case NodeChildrenChanged:
         try {
              //处理收到的消息
              handleMessage(watchPath);
         } catch (UnsupportedEncodingException e) {
              // TODO Auto-generated catch block
              e.printStackTrace();
         } catch (KeeperException e) {
             // TODO Auto-generated catch block
             e.printStackTrace();
         } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
         }
        break;
       default:
         break;
       }
}

public void handleMessage(String watchPath) throws KeeperException,InterruptedException, UnsupportedEncodingException {
    System.out.println("收到消息");
    //再监听该子节点
    List<String> Children = this.getChildren(watchPath);
    for (String a : Children) {
        String childrenpath = watchPath + "/" + a;
        byte[] recivedata = this.getData(childrenpath);
        String recString = new String(recivedata, "UTF-8");
        System.out.println("receive the path:" + childrenpath + ":data:"+ recString);
        //做完了之后,删除该节点
        this.deletNode(childrenpath, -1);
    }
}

public List<String> getChildren(String path) throws KeeperException,InterruptedException {
     //监听该节点子节点的变化情况
     return this.zooKeeper.getChildren(path, this);
}
public Stat setData(String path, byte[] data, int version)throws KeeperException, InterruptedException {
     return this.zooKeeper.setData(path, data, version);
}
<think>好的,用户想了解ZooKeeper中的Watch机制,包括工作原理、使用方法和示例。首先,我需要回忆一下ZooKeeper的基础知识,尤其是Watch机制的相关内容。记得Watch机制ZooKeeper实现分布式协调的核心功能之一,它允许客户端在数据节点(znode)发生变化时接收通知。 接下来,我需要整理Watch机制的工作原理。根据已有的引用资料,比如引用[1]提到每次监视到事件后需要再次注册,这说明Watch是一次性的,触发后需要重新设置。引用[4]也提到通过注册Watcher来监控锁的状态,这涉及到事件类型和通知机制。因此,工作原理部分应该包括一次性触发、事件类型、异步通知和客户端处理这几个方面。 然后是使用方法,用户可能想知道如何在代码中注册Watcher。引用[2]和[3]提到使用Python的kazoo库,比如通过exists方法添加Watch,并在回调函数中处理事件。需要分步骤说明,比如注册方式、事件类型和处理逻辑,同时强调需要重新注册Watch。 示例部分,可以用Python的kazoo库来展示,比如监控节点数据变化。代码示例应该包括连接ZooKeeper、定义回调函数、注册Watch,并保持连接活跃。引用[3]中的代码片段可以作为参考,但需要确保语法正确,并添加注释说明。 还要考虑用户可能遇到的问题,比如Watch丢失的情况,或者如何保证数据一致性,这些在引用[2]和[4]中都有涉及。需要提醒用户注意事项,如一次性特性、网络延迟和事件顺序性。 最后生成相关问题,覆盖原理、应用场景、与回调的关系、丢失处理以及与其他系统的对比,这样用户可以有进一步学习的路径。同时,要确保引用标记正确,比如在提到需要重新注册Watch的地方添加[^1]和[^4]。 总结一下,回答的结构应该分为原理、使用方法、示例、注意事项和相关问题,每个部分引用对应的资料来支持内容,确保信息准确可靠。同时,保持语言简洁明了,符合用户的中文需求,避免使用复杂术语,必要时提供代码示例帮助理解。</think>### ZooKeeper Watch机制解析 #### 一、核心原理 1. **一次性触发** Watch监听在znode节点变更后仅触发一次,后续变更需重新注册[^1]。例如监控节点`/service`的数据变化,首次数据更新会触发通知,但后续变化需再次调用`exists()`或`getData()`重新设置Watch。 2. **事件类型与异步通知** 支持节点创建($\text{NodeCreated}$)、删除($\text{NodeDeleted}$)、数据变更($\text{NodeDataChanged}$)、子节点变化($\text{NodeChildrenChanged}$)四类事件。通知通过独立的回调线程异步传递。 3. **通知与状态同步机制** 事件通知仅包含节点路径和事件类型,不携带变更内容,客户端需主动查询新数据[^2]。例如: ```python def watcher_callback(event): if event.type == 'CHANGED': new_data = zk.get(event.path, watch=watcher_callback)[0] # 重新注册Watch ``` #### 二、使用方法 1. **注册方式** - 通过`exists(path, watch=True)`监控节点存在性 - 通过`getData(path, watch=True)`监控数据变化 - 通过`getChildren(path, watch=True)`监控子节点变化 2. **Python实现示例** ```python from kazoo.client import KazooClient zk = KazooClient(hosts='127.0.0.1:2181') zk.start() def data_change_watcher(event): print(f"节点 {event.path} 数据已变更,事件类型:{event.type}") # 必须重新注册Watch zk.get(event.path, watch=data_change_watcher) # 初始注册Watch zk.get('/config/server1', watch=data_change_watcher) ``` #### 三、典型应用场景 1. **分布式锁实现** 通过监控`/lock`节点的子节点变化,实现锁竞争通知。 2. **服务发现系统** 监控`/services`子节点变化,实时更新可用服务列表。 3. **配置中心** 监控`/config`节点数据变更,实现配置热更新。 #### 四、注意事项 1. **事件丢失风险** 在Watch触发到重新注册期间发生的变更可能无法捕获,需配合版本号校验: ```python data, stat = zk.get('/config', watch=watcher) # 后续操作使用stat.version进行版本验证 ``` 2. **网络分区处理** 会话过期($\text{SessionExpired}$)事件需特殊处理,此时所有Watch将被清除。 $$ \text{事件可靠性} = \begin{cases} \text{强保障} & \text{在watch生效期间} \\ \text{需补偿机制} & \text{会话中断后} \end{cases} $$
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值