Zookeeper系列——4Zookeeper的Watcher机制原理分析

本文深入剖析Zookeeper的Watcher工作原理,从客户端注册监听、服务端接收、客户端收到请求到事件触发的完整流程,详细解析了每个步骤的关键操作,包括建立连接、创建节点、注册监听、事件响应和客户端回调等,帮助读者理解Zookeeper分布式协调服务中的监控机制。
摘要由CSDN通过智能技术生成

学习目标

  1. 理解Zookeeper的watcher机制原理

第1章 客户端注册监听

二话不说,先贴上总体流程图

再来看看Watcher的使用

ZooKeeper 的 Watcher 机制,总的来说可以分为三个过程:客户端注册 Watcher、服务器处理Watcher 和客户端回调 Watcher

客户端注册watcher有3种方式,getData、exists、getChildren;以如下代码为例来分析整个触发机制的原理

public class Demo01 {
    public static void main(String[] args) throws KeeperException, InterruptedException, IOException {
        ZooKeeper zookeeper=new ZooKeeper("127.0.0.1:2181",4000,new Watcher(){
            @Override
            public void process(WatchedEvent event) {
                System.out.println("event.type:"+event.getType());
            }
        });
        zookeeper.create("/watch","0".getBytes(), ZooDefs.Ids. OPEN_ACL_UNSAFE,
                CreateMode.PERSISTENT); //创建节点
        zookeeper.exists("/watch",true); //注册监听
        Thread.sleep(1000);
        zookeeper.setData("/watch", "1".getBytes(),-1) ; //修改节点的值触发监听
    }
}

持久化监听

从zookeeper3.6开始,提供了持久化监听以及递归监听机制,演示如下(我本地的zookeeper版本为3.4.13的和3.5.6,所以这块就不做演示了)

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>5.1.0</version>
</dependency>
<dependency><!--分布式锁、leader选举、队列...-->
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>5.1.0</version>
</dependency>
ZooKeeper zooKeeper=new ZooKeeper("localhost:2181", 5000, new Watcher() {
    @Override
    public void process(WatchedEvent watchedEvent) {
        //表示连接成功之后,会产生的回调时间
    }
});
zooKeeper.addWatch("/first1",watchedEvent -> {
    System.out.println(watchedEvent.getPath());
},AddWatchMode.PERSISTENT);

其实很多流程在前面两篇文章已经讲过了,为了大家印象更深刻以及思路更清晰,有些流程在本文不做太详细的介绍。

1.1 建立连接

ZooKeeper zookeeper=new ZooKeeper("127.0.0.1:2181") , Zookeeper在初始化的时候,会构建一个Watcher,我们可以先看看Zookeeper初始化做了什么事情。

public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher,
                 boolean canBeReadOnly) throws IOException{
    //在这里将watcher设置到ZKWatchManager
    watchManager.defaultWatcher = watcher;

    ConnectStringParser connectStringParser = new ConnectStringParser(
        connectString);
    HostProvider hostProvider = new StaticHostProvider(
        connectStringParser.getServerAddresses());
    //初始化了ClientCnxn,在这里会创建一个sendThread和eventThread线程
    cnxn = new ClientCnxn(connectStringParser.getChrootPath(),
                          hostProvider, sessionTimeout, this, watchManager,
                          getClientCnxnSocket(), canBeReadOnly);
    //启动一个sendThread线程进行通信还有eventThread进行事件通知
    cnxn.start();
}

cnxn.start();应该比较熟悉了吧,这个方法里面会启动一个sendThread线程进行通信还有eventThread进行事件通知,然后通过sendThread线程去建立连接,这一步的逻辑在上文中已经着重讲过,这里不再赘述。

1.2 创建节点

这一步是通过create方法区完成,在create方法中我们会发现,实际上他就是调用的sumbitRequest,然后在底层也是调用sendThread发送请求到服务端。

public String create(final String path, byte data[], List<ACL> acl,
                     CreateMode createMode)
    throws KeeperException, InterruptedException
{
	...
    request.setAcl(acl);
    //这一步也眼熟的很,是上文中介绍过的请求处理,实际上进入底层你会发现依然是通过sendThread去发送请求了。
    ReplyHeader r = cnxn.submitRequest(h, request, response, null);
    if (r.getErr() != 0) {
        throw KeeperException.create(KeeperException.Code.get(r.getErr()),
                                     clientPath);
    }
    if (cnxn.chrootPath == null) {
        return response.getPath();
    } else {
        return response.getPath().substring(cnxn.chrootPath.length());
    }
}

1.3 注册监听

exists是用来判断一个节点是否存在,同时,还会针对这个节点注册一个watcher事件。

public Stat exists(final String path, Watcher watcher)
    throws KeeperException, InterruptedException
{
    final String clientPath = path;
    PathUtils.validatePath(clientPath);

    // the watch contains the un-chroot path
    WatchRegistration wcb = null;
    if (watcher != null) {
        wcb = new ExistsWatchRegistration(watcher, clientPath);
    }

    final String serverPath = prependChroot(clientPath);
	//构建请求头
    RequestHeader h = new RequestHeader();
    //表示当前请求的操作类型是exists
    h.setType(ZooDefs.OpCode.exists);
    //构建发送请求和响应的response
    ExistsRequest request = new ExistsRequest();
    request.setPath(serverPath);
    request.setWatch(watcher != null);
    SetDataResponse response = new SetDataResponse();
    //通过submitRequest发送请求
    ReplyHeader r = cnxn.submitRequest(h, request, response, wcb);
    if (r.getErr() != 0) {
        if (r.getErr() == KeeperException.Code.NONODE.intValue()) {
            return null;
        }
        throw KeeperException.create(KeeperException.Code.get(r.getErr()),
                                     clientPath);
    }
	//返回stat元数据
    return response.getStat().getCzxid() == -1 ? null : response.getStat();
}

发现了吧,其实不管是什么操作在zk里面都是这么操作的,调用submitRequest,接下来我们注重分析一下这个方法

1.3.1 submitRequest

这里面的处理逻辑比较简单

  • 调用queuePacket,把请求数据添加到队列

  • 通过packet.wait使得当前线程一直阻塞,直到请求完成

public ReplyHeader submitRequest(RequestHeader h, Record request,
                                 Record response, WatchRegistration watchRegistration)
    throws InterruptedException {
    ReplyHeader r = new ReplyHeader();
    //把请求数据添加到队列
    Packet packet = queuePacket(h, r, request, response, null, null, null,
                                null, watchRegistration);
    // 等到请求执行完成
    synchronized (packet) {
        while (!packet.finished) {
            packet.wait();
        }
    }
    return r;
}

1.3.2 queuePacket

在这个方法里面实际上就干了两件事,1、创建一个Packet对象并加入到队列;2、唤醒一个sendThread线程去执行

Packet queuePacket(RequestHeader h, ReplyHeader r, Record request,
                   Record response, AsyncCallback cb, String clientPath,
                   String serverPath, Object ctx, WatchRegistration watchRegistration)
{
    Packet packet = null;

    // Note that we do not generate 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木木_2024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值