5.Watcher机制(二)WatchManager

简介: 本文深入分析ZooKeeper中WatchManager类的源码,介绍其核心属性watchTable与watch2Paths的映射关系,并详解size、addWatch、removeWatcher、triggerWatch及dumpWatches等同步方法的实现逻辑,揭示Watcher事件的注册、触发与管理机制。

一、前言

  前面已经分析了Watcher机制中的第一部分,即在org.apache.zookeeper下的相关类,接着来分析org.apache.zookeeper.server下的WatchManager类。

二、WatchManager源码分析

2.1 类的属性 

public class WatchManager {
    // Logger
    private static final Logger LOG = LoggerFactory.getLogger(WatchManager.class);
    // watcher表
    private final HashMap<String, HashSet<Watcher>> watchTable =
        new HashMap<String, HashSet<Watcher>>();
    // watcher到节点路径的映射
    private final HashMap<Watcher, HashSet<String>> watch2Paths =
        new HashMap<Watcher, HashSet<String>>();
}

说明:WatcherManager类用于管理watchers和相应的触发器。watchTable表示从节点路径到watcher集合的映射,而watch2Paths则表示从watcher到所有节点路径集合的映射。

2.2 核心方法分析

1. size方法

public synchronized int size(){
    int result = 0;
    for(Set<Watcher> watches : watchTable.values()) { // 遍历watchTable所有的值集合(HashSet<Watcher>集合)
        // 每个集合大小累加
        result += watches.size();
    }
    // 返回结果
    return result;
}

说明:可以看到size方法是同步的,因此在多线程环境下是安全的,其主要作用是获取watchTable的大小,即遍历watchTable的值集合。

2. addWatch方法

public synchronized void addWatch(String path, Watcher watcher) {
    // 根据路径获取对应的所有watcher
    HashSet<Watcher> list = watchTable.get(path);
    if (list == null) { // 列表为空
        // don't waste memory if there are few watches on a node
        // rehash when the 4th entry is added, doubling size thereafter
        // seems like a good compromise
        // 新生成watcher集合
        list = new HashSet<Watcher>(4);
        // 存入watcher表
        watchTable.put(path, list);
    }
    // 将watcher直接添加至watcher集合
    list.add(watcher);
    // 通过watcher获取对应的所有路径
    HashSet<String> paths = watch2Paths.get(watcher);
    if (paths == null) { // 路径为空
        // cnxns typically have many watches, so use default cap here
        // 新生成hash集合
        paths = new HashSet<String>();
        // 将watcher和对应的paths添加至映射中
        watch2Paths.put(watcher, paths);
    }
    // 将路径添加至paths集合
    paths.add(path);
}

说明:addWatch方法同样是同步的,其大致流程如下

  ① 通过传入的path(节点路径)从watchTable获取相应的watcher集合,进入②

  ② 判断①中的watcher是否为空,若为空,则进入③,否则,进入④

  ③ 新生成watcher集合,并将路径path和此集合添加至watchTable中,进入④【类似缓存操作】

  ④ 将传入的watcher添加至watcher集合,即完成了path和watcher添加至watchTable的步骤,进入⑤

  ⑤ 通过传入的watcher从watch2Paths中获取相应的path集合,进入⑥

  ⑥ 判断path集合是否为空,若为空,则进入⑦,否则,进入⑧

  ⑦ 新生成path集合,并将watcher和paths添加至watch2Paths中,进入⑧

  ⑧ 将传入的path(节点路径)添加至path集合,即完成了path和watcher添加至watch2Paths的步骤。

综上:addWatche方法会将:

1.入参所对应的watcher添加到入参path所对应的全部Watcher集合中,如path下已有则添加,没有创建新的并添加进去;

2.入参所对应的path添加到入参watcher所对应给的所有路径集合中,如watcher对应路径为空则创建新的集合进行添加,非空将入参path直接添加进去。

3. removeWatcher方法  

public synchronized void removeWatcher(Watcher watcher) {
    // 从wach2Paths中移除watcher,并返回watcher对应的path集合
    HashSet<String> paths = watch2Paths.remove(watcher);
    if (paths == null) { // 集合为空,直接返回
        return;
    }
    for (String p : paths) { // 遍历路径集合
        // 从watcher表中根据路径取出相应的watcher集合
        HashSet<Watcher> list = watchTable.get(p);
        if (list != null) { // 若集合不为空
            // 从list中移除该watcher
            list.remove(watcher);
            if (list.size() == 0) { // 移除后list为空,则从watch表中移出
                watchTable.remove(p);
            }
        }
    }
}

说明:removeWatcher用作从watch2Paths和watchTable中中移除该watcher,其大致步骤如下

  ① 从watch2Paths中移除传入的watcher,并且返回该watcher对应的路径集合,进入②

  ② 判断返回的路径集合是否为空,若为空,直接返回,否则,进入③

  ③ 遍历②中的路径集合,对每个路径,都从watchTable中取出与该路径对应的watcher集合,进入④

  ④ 若③中的watcher集合不为空,则从该集合中移除watcher,并判断移除元素后的集合大小是否为0,若为0,进入⑤

  ⑤ 从watchTable中移除路径

4. triggerWatch方法

public Set<Watcher> triggerWatch(String path, EventType type, Set<Watcher> supress) {
    // 根据事件类型、连接状态、节点路径创建WatchedEvent
    WatchedEvent e = new WatchedEvent(type, KeeperState.SyncConnected, path);
    // watcher集合
    HashSet<Watcher> watchers;
    synchronized (this) { // 同步块
        // 从watcher表中移除path,并返回其对应的watcher集合
        watchers = watchTable.remove(path);
        if (watchers == null || watchers.isEmpty()) { // watcher集合为空
            if (LOG.isTraceEnabled()) { 
                ZooTrace.logTraceMessage(LOG, ZooTrace.EVENT_DELIVERY_TRACE_MASK,
                                         "No watchers for " + path);
            }
            // 返回
            return null;
        }
        for (Watcher w : watchers) { // 遍历watcher集合
            // 根据watcher从watcher表中取出路径集合
            HashSet<String> paths = watch2Paths.get(w);
            if (paths != null) { // 路径集合不为空
                // 则移除路径
                paths.remove(path);
            }
        }
    }
    for (Watcher w : watchers) { // 遍历watcher集合
        if (supress != null && supress.contains(w)) { // supress不为空并且包含watcher,则跳过
            continue;
        }
        // 进行处理
        w.process(e);
    }
    return watchers;
}

 说明:该方法主要用于触发watch事件,并对事件进行处理。其大致步骤如下

  ① 根据事件类型、连接状态、节点路径创建WatchedEvent,进入②

  ② 从watchTable中移除传入的path对应的键值对,并且返回path对应的watcher集合,进入③

  ③ 判断watcher集合是否为空,若为空,则之后会返回null,否则,进入④

  ④ 遍历②中的watcher集合,对每个watcher,从watch2Paths中取出path集合,进入⑤

  ⑤ 判断④中的path集合是否为空,若不为空,则从集合中移除传入的path。进入⑥

  ⑥ 再次遍历watcher集合,对每个watcher,若supress不为空并且包含了该watcher,则跳过,否则,进入⑦

  ⑦ 调用watcher的process方法进行相应处理,之后返回watcher集合。【这里的process具体怎么执行的呢

5. dumpWatches方法

public synchronized void dumpWatches(PrintWriter pwriter, boolean byPath) {
    if (byPath) { // 控制写入watchTable或watch2Paths
        for (Entry<String, HashSet<Watcher>> e : watchTable.entrySet()) { // 遍历每个键值对
            // 写入键
            pwriter.println(e.getKey());
            for (Watcher w : e.getValue()) { // 遍历值(HashSet<Watcher>)
                pwriter.print("\t0x");
                pwriter.print(Long.toHexString(((ServerCnxn)w).getSessionId()));
                pwriter.print("\n");
            }
        }
    } else {
        for (Entry<Watcher, HashSet<String>> e : watch2Paths.entrySet()) { // 遍历每个键值对
            // 写入"0x"
            pwriter.print("0x");
            pwriter.println(Long.toHexString(((ServerCnxn)e.getKey()).getSessionId()));
            for (String path : e.getValue()) { // 遍历值(HashSet<String>)
                // 
                pwriter.print("\t");
                pwriter.println(path);
            }
        }
    }
}

  说明:dumpWatches用作将watchTable或watch2Paths写入磁盘。

三、总结

  WatchManager类用作管理watcher、其对应的路径以及触发器,其方法都是针对两个映射的操作。

相关文章
|
5月前
|
自然语言处理 数据可视化 Docker
安装ES、Kibana、IK
本文介绍如何通过Docker部署单节点Elasticsearch与Kibana,并安装IK分词器。内容涵盖创建网络、加载镜像、运行容器、配置扩展词典与停用词典,以及常见启动报错处理,帮助快速搭建ES开发环境。
安装ES、Kibana、IK
|
5月前
|
负载均衡 应用服务中间件 Nacos
Nacos配置中心
本文详细介绍如何使用Nacos实现微服务配置中心,涵盖配置管理、热更新、共享配置及优先级规则,并演示Nacos集群搭建与高可用部署,提升系统可维护性与稳定性。
 Nacos配置中心
|
5月前
|
SQL 容灾 Nacos
Seata的部署和集成
本文介绍Seata TC服务器的部署与微服务集成,包括下载、配置、数据库表初始化及高可用集群搭建,实现基于Nacos的分布式事务管理与异地容灾支持。
|
5月前
|
关系型数据库 MySQL Java
开发环境搭建
工欲善其事,必先利其器。本文档指导配置Java开发环境:要求电脑内存16G以上(推荐32G),建议配备便携显示器提升效率。需安装VMware虚拟机(CentOS7系统)、IDEA、Maven、Git等工具,并导入虚拟机镜像与项目资料。通过FinalShell远程连接虚拟机(IP:192.168.101.68),配置Nginx运行前端,最终启动黑马商城项目。详细步骤涵盖环境搭建、网络设置、数据库导入及常见问题处理,助力高效开发。
|
5月前
|
Kubernetes Java 应用服务中间件
1.开发篇(脚手架下载)
本文介绍基于SpringCloud + Kubernetes的微服务开发实践,重点分享EDAS 3.0在项目初始化与本地启动环节的优化体验。通过阿里云start.aliyun.com脚手架快速生成项目,结合Cloud Toolkit插件一键拉起本地注册中心,实现应用快速部署与联调,提升开发者效率。后续将深入讲解云端部署及端云互联能力。
|
5月前
|
NoSQL Linux 网络安全
Redis集群部署指南
本章介绍CentOS7下Redis集群搭建,涵盖单机安装、主从复制、哨兵高可用及分片集群配置,通过实操命令与图示详解各集群模式的部署与测试过程。
|
5月前
|
消息中间件 负载均衡 Linux
RabbitMQ部署指南
本文介绍RabbitMQ在CentOS7中基于Docker的单机与集群部署方案,涵盖镜像安装、DelayExchange插件配置、普通集群与镜像模式搭建,并详细演示仲裁队列使用及集群扩容方法,助力实现高可用消息队列服务。
 RabbitMQ部署指南
|
5月前
|
存储 缓存 负载均衡
Nacos注册中心
本文介绍Nacos的安装部署、服务注册中心整合、分级模型、负载均衡策略、权重控制、环境隔离及实例类型,详解其在微服务架构中的应用,帮助开发者掌握Nacos核心功能与最佳实践。
 Nacos注册中心
|
5月前
|
负载均衡 算法 架构师
Ribbon负载均衡
本文深入讲解Spring Cloud中Ribbon实现客户端负载均衡的原理,包括@LoadBalanced注解的作用、负载均衡策略分类与算法,以及如何自定义配置和优化首次调用延迟的饥饿加载机制,帮助读者全面理解微服务间的流量分发技术。
Ribbon负载均衡
|
5月前
|
NoSQL Java 前端开发
单体版本
JeecgBoot是一款基于Spring Boot与Vue3的低代码开发平台,支持前后端分离,集成MybatisPlus、Shiro、Redis等主流技术,提供代码生成、在线表单、微服务架构等功能,助力快速构建企业级应用。
 单体版本