ZooKeeper分布式配置——看这篇就够了

Watch有两种监听模式,分别为 事件类型和状态类型

事件类型:Znode 节点关联,主要是针对节点的操作

  • 创建节点:EventType.NodeCreated

  • 节点数据发生变化:EventType.NodeDataChanged

  • 当前节点的子节点发生变化:EventType.NodeChildrenChanged

  • 删除节点:EventType.NodeDeleted

状态类型:客户端关联,主要是针对于ZooKeeper集群和应用服务之间的状态的变更

  • 未连接:KeeperState.Disconnected

  • 已连接:KeeperState.SyncConnected

  • 认证失败:KeeperState.AuthFailed

  • 过期:KeeperState.Expired

  • 客户端连接到只读服务器:KeeperState.ConnectedReadOnly

watch的特性

一次性触发: 对于ZooKeeper的Watcher事件,是一次性触发的,当 Watch 监视的数据发生变化的时候,通知设置当前Watch 的 Client,就是我们对应的 Watcher,因为ZooKeeper 的监控都是一次性的,所以我们需要在每次触发后设置监控。

客户端串行执行: 客户端Watcher回调的过程是一个串行同步的过程,可以为我们保证顺序的执行。

轻量级: WatchedEvent是ZooKeeper整个Watcher通知机制的最小通知单元,总共包含三个部分(通知状态、事件类型和节点路径),Watcher通知,只会告诉客户端发生事件而不会告知具体内容,需要客户端主动去进行获取,比如 当监听到 WatchedEvent.NodeDataChanged 信息变化的时候,只会告诉我们这个节点的数据发生了变更,你快来获取最新的值吧。

客户端设置的每个监视点与会话关联,如果会话过期,等待中的监视点将会被删除。不过监视点可以跨越不同服务端的连接而保持,例如,当一个ZooKeeper客户端与一个ZooKeeper服务端的连接断开后连接到集合中的另一个服务端,客户端会发送未触发的监视点列表,在注册监视点时,服务端将要检查已监视的znode节点在之前注册监视点之后是否已经变化,如果znode节点已经发生变化,一个监视点的事件就会被发送给客户端,否则在新的服务端上注册监视点。这一机制使得我们可以关心逻辑层会话,而非底层连接本身。

客户端注册

在这里插入图片描述

ZooKeeper 注册的时候会向ZooKeeper 服务端请求注册,服务端会返回请求响应,不管成功失败,都会返回响应结果,当响应成功的时候,ZooKeeper服务端会把Watcher对象放到客户端的WatchManager管理并返回响应给客户端

服务端注册

在这里插入图片描述

  1. FinalRequest Processor.ProcessRequest()会判断当前请求是否需要注册Watcher

如果ZooKeeper判断当前客户端需要进行Watcher注册,会将当前的ServerCnxn 对象和数据路径传入 getData 方法中去。ServerCnxn 是ZooKeeper 客户端和服务端之间的连接接口,代表了一个客户端和服务端的连接,可以将 ServerCnxn 当做一个 Watcher 对象,因为它实现了 Watcher 的 process 接口。

  1. WatcherManager

WatcherManager是 ZK服务端 Watcher 的管理器,分为 WatchTable 和 Watch2Paths 两个存储结构,这两个是不同的存储结构

1)WatchTable: 从数据节点路径的颗粒度管理 Watcher

2)Watch2Paths:从Watcher的颗粒度来控制时间出发的数据节点

在服务端,DataTree 中会托管两个 WatchManager, 分别是 dataWatches (数据变更Watch) 和 childWatches(子节点变更Watch)。

  1. Watcher 触发逻辑

1)封装WatchedEven:将(KeeperState(通知状态),EventType(事件类型),Path(节点路径))封装成一个 WatchedEvent 对象

2)查询Watcher:根据路径取出对应的Watcher,如果存在,取出数据同时从 WatcherManager(WatchTable/Watch2Paths) 中删除

3)调用Process方法触发Watcher

4.客户端回调 Watcher

1)反序列化:字节流转换成 WatcherEvent 对象

2)处理 chrootPath:如果客户端设置了 chrootPath 属性,那么需要对服务器传过来的完整节点路径进行 chrootPath 处理,生成客户端的一个相对节点路径。比如(/mxn/app/love,经过chrootPath处理,会变成 /love)

3)还原 WatchedEvent:WatcherEvent 转换成 WatchedEvent

4)回调 Watcher:将 WatcherEvent 对象交给 EventThread 线程,在下一个轮询周期中进行 Watcher 回调

  1. EventThread 处理时间通知

1) SendThread 接收到服务端的通知事件后,会通过调用 EventThread.queueEvent 方法将事件传给 EventThread 线程

2)queueEvent 方法首先会根据该通知事件,从 ZKWatchManager 中取出所有相关的 Watcher 客户端识别出 事件类型 EventType 后,会从相应的 Watcher 存储 (即3个注册方法( dataWatches、existWatcher 或 childWatcher)中去除对应的 Watcher

3) 获取到相关的所有 Watcher 后,会将其放入 waitingEvents 这个队列去

代码实现


下面我们就来演示如何使用代码来实现ZooKeeper的配置

首先我们需要引入ZK的jar

org.apache.zookeeper

zookeeper

3.6.3

配置类

既然我们要做的是分布式配置,首先我们需要模拟一个配置,这个配置用来同步服务的地址

/**

  • @program: mxnzookeeper

  • @ClassName MyConf

  • @description: 配置类

  • @author: muxiaonong

  • @create: 2021-10-19 22:18

  • @Version 1.0

**/

public class MyConfig {

private String conf ;

public String getConf() {

return conf;

}

public void setConf(String conf) {

this.conf = conf;

}

}

Watcher

创建ZooKeeper的时候,我们需要一个Watcher进行监听,后续对Znode节点操作的时候,我们也需要使用到Watcher,但是这两类的功能不一样,所以我们需要定义一个自己的watcher类,如下所示:

import org.apache.zookeeper.WatchedEvent;

import org.apache.zookeeper.Watcher;

import java.util.concurrent.CountDownLatch;

/**

  • @program: mxnzookeeper

  • @ClassName DefaultWatch

  • @description:

  • @author: muxiaonong

  • @create: 2021-10-19 22:02

  • @Version 1.0

**/

public class DefaultWatch implements Watcher {

CountDownLatch cc;

public void setCc(CountDownLatch cc) {

this.cc = cc;

}

@Override

public void process(WatchedEvent event) {

System.out.println(event.toString());

switch (event.getState()) {

case Unknown:

break;

case Disconnected:

break;

case NoSyncConnected:

break;

case SyncConnected:

System.out.println(“连接成功。。。。。”);

//连接成功后,执行countDown,此时便可以拿zk对象使用了

cc.countDown();

break;

case AuthFailed:

break;

case ConnectedReadOnly:

break;

case SaslAuthenticated:

break;

case Expired:

break;

case Closed:

break;

}

}

}

由于是异步进行操作的,我们创建一个ZooKeeper对象之后,如果不进行阻塞操作的话,有可能还没有连接完成就执行后续的操作,所以这里我们用 CountDownLatch进行阻塞操作,当监测连接成功后,进行 countDown放行,执行后续的ZK的动作。

当我们连接成功 ZooKeeper 之后,我们需要通过 exists判断是否存在节点,存在就进行 getData操作。这里我们创建一个 WatchCallBack因为exists和getData都需要一个callback,所以除了实现Watcher以外还需要实现节点状态:AsyncCallback.StatCallback 数据监听:AsyncCallback.DataCallback

import org.apache.zookeeper.AsyncCallback;

import org.apache.zookeeper.WatchedEvent;

import org.apache.zookeeper.Watcher;

import org.apache.zookeeper.ZooKeeper;

import org.apache.zookeeper.data.Stat;

import java.util.concurrent.CountDownLatch;

/**

  • @program: mxnzookeeper

  • @ClassName WatchCallBack

  • @description:

  • @author: muxiaonong

  • @create: 2021-10-19 22:13

  • @Version 1.0

**/

public class WatchCallBack implements Watcher, AsyncCallback.StatCallback, AsyncCallback.DataCallback {

ZooKeeper zk ;

MyConfig conf ;

CountDownLatch cc = new CountDownLatch(1);

public MyConfig getConf() {

return conf;

}

public void setConf(MyConfig conf) {

this.conf = conf;

}

public ZooKeeper getZk() {

return zk;

}

public void setZk(ZooKeeper zk) {

this.zk = zk;

}

public void aWait(){

//exists的异步实现版本

zk.exists(ZKConstants.ZK_NODE,this,this ,“exists watch”);

try {

cc.await();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

/** @Author mxn

  • @Description //TODO 此回调用于检索节点的stat

  • @Date 21:24 2021/10/20

  • @param rc 调用返回的code或结果

  • @param path 传递给异步调用的路径

  • @param ctx 传递给异步调用的上下文对象

  • @param stat 指定路径上节点的Stat对象

  • @return

**/

@Override

public void processResult(int rc, String path, Object ctx, Stat stat) {

if(stat != null){

//getData的异步实现版本

zk.getData(ZKConstants.ZK_NODE,this,this,“status”);

}

}

/** @Author mxn

  • @Description //TODO 此回调用于检索节点的数据和stat

  • @Date 21:23 2021/10/20

  • @param rc 调用返回的code或结果

  • @param path 传递给异步调用的路径

  • @param ctx 传递给异步调用的上下文对象

  • @param data 节点的数据

  • @param stat 指定节点的Stat对象

  • @return

**/

@Override

public void processResult(int rc, String path, Object ctx, byte[] data, Stat stat) {

if(data != null ){

String s = new String(data);

conf.setConf(s);

cc.countDown();

}

}

/** @Author mxn

  • @Description //TODO Watcher接口的实现。

  •                  Watcher接口指定事件处理程序类必须实现的公共接口。
    
  •                  ZooKeeper客户机将从它连接到的ZooKeeper服务器获取各种事件。
    
  •                  使用这种客户机的应用程序通过向客户机注册回调对象来处理这些事件。
    
  •                  回调对象应该是实现监视器接口的类的实例。
    
  • @Date 21:24 2021/10/20

  • @Param watchedEvent WatchedEvent表示监视者能够响应的ZooKeeper上的更改。

  •      WatchedEvent包含发生了什么,
    
  •      ZooKeeper的当前状态,以及事件中涉及的znode的路径。
    
  • @return

**/

@Override

public void process(WatchedEvent event) {

switch (event.getType()) {

case None:

break;

case NodeCreated:

//当一个node被创建后,获取node

//getData中又会触发StatCallback的回调processResult

zk.getData(ZKConstants.ZK_NODE,this,this,“sdfs”);

break;

case NodeDeleted:

//节点删除

conf.setConf(“”);

//重新开启CountDownLatch

cc = new CountDownLatch(1);

break;

case NodeDataChanged:

//节点数据被改变了

//触发DataCallback的回调

zk.getData(ZKConstants.ZK_NODE,this,this,“sdfs”);

break;

//子节点发生变化的时候

case NodeChildrenChanged:

break;

}

}

}

当前面准备好了之后,我们可以编写测试用例了:

ZKUtils 工具类

import org.apache.zookeeper.ZooKeeper;

import java.util.concurrent.CountDownLatch;

/**

  • @program: mxnzookeeper

  • @ClassName ZKUtils

  • @description:

  • @author: muxiaonong

  • @create: 2021-10-19 21:59

  • @Version 1.0

**/

public class ZKUtils {

private static ZooKeeper zk;

//192.168.5.130:2181/mxn 这个后面/mxn,表示客户端如果成功建立了到zk集群的连接,

// 那么默认该客户端工作的根path就是/mxn,如果不带/mxn,默认根path是/

//当然我们要保证/mxn这个节点在ZK上是存在的

private static String address =“192.18.5.129:2181,192.168.5.130:2181,192.168.5.130:2181/mxn”;

private static DefaultWatch watch = new DefaultWatch();

private static CountDownLatch init = new CountDownLatch(1);

public static ZooKeeper getZK(){

try {

//因为是异步的,所以要await,等到连接上zk集群之后再进行后续操作

zk = new ZooKeeper(address,1000,watch);

watch.setCc(init);

init.await();

} catch (Exception e) {

e.printStackTrace();

}

return zk;

}

}

测试类:

import org.apache.zookeeper.ZooKeeper;

import org.junit.Before;

import org.junit.Test;

/**

  • @program: mxnzookeeper

  • @ClassName TestConfig

  • @description:

  • @author: muxiaonong

  • @create: 2021-10-19 22:04

  • @Version 1.0

**/

public class TestConfig {

ZooKeeper zk;

@Before

public void conn(){

zk = ZKUtils.getZK();

}

/** @Author mxn

  • @Description //TODO 关闭ZK

  • @Date 21:16 2021/10/20

  • @Param

  • @return

**/

public void close(){

try {

zk.close();

}catch (Exception e){

e.printStackTrace();

}

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-ZNX3KFYH-1713380648484)]

[外链图片转存中…(img-aoYhOfST-1713380648485)]

[外链图片转存中…(img-TsebJm3t-1713380648485)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

[外链图片转存中…(img-HSdFrRCX-1713380648485)]

[外链图片转存中…(img-Cugwqvlw-1713380648485)]

[外链图片转存中…(img-GXRhJl5z-1713380648485)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 29
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值