zookeeper学习三

本文介绍了ZooKeeper在分布式环境中的配置管理及回调数据代码实现,包括ZK连接、节点监听和数据变更回调。此外,还详细讲解了ZooKeeper分布式锁的概念,强调了锁的创建、释放和心跳机制,以及使用序列节点和Watch策略来确保锁的正确性。
摘要由CSDN通过智能技术生成


1、ZK是分布式协调用的(只要有东西被修改了,则马上会有回调)
2、分布式锁

ZK配置及回调数据代码实现

如果有很多服务器,启动的时候需要获取配置,并且在运行的时候会发布订阅修改配置,
那么我们可以用zk去存配置的数据data,那么客户端可以watch这些数据是否修改,
并且能够取回这些数据。
在这里插入图片描述
代码实现:
首先定义一个ZKUtils,为的是快速创建zk的连接,抽取出

package com.lsd.zookeeper.config;

import org.apache.zookeeper.ZooKeeper;

import java.util.concurrent.CountDownLatch;

/**
 * @ClassName ZKUtils
 * @Description
 * @Author SD.LIU
 * @Date 2022/8/19 10:40
 * @Version 1.0
 **/
public class ZKUtils {


    public static ZooKeeper zooKeeper;

    /**
     * 指定工作目录
     */
    private static final String ADDRESS = "192.168.75.128:2181,192.168.75.130:2181,192.168.75.131:2181,192.168.75.132:2181/testConf";

    private static DefaultWatch watch = new DefaultWatch();

   private static CountDownLatch countDownLatch = new CountDownLatch(1);
    public static ZooKeeper getZooKeeper() throws Exception {

        zooKeeper = new ZooKeeper(ADDRESS, 1000, watch);
        watch.setCountDownLatch(countDownLatch);
        countDownLatch.await();
        return zooKeeper;
    }

}

由于是异步的去连接ZK的:
并且watchevent的stat能获取到这个连接是否是已连接了的,所以我们要等到ZK给的回调为连接成功了,我们才能把CountDownLatch给减一。

package com.lsd.zookeeper.config;

import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;

import java.util.concurrent.CountDownLatch;

/**
 * @ClassName DefaultWatch
 * @Description
 * @Author SD.LIU
 * @Date 2022/8/19 10:45
 * @Version 1.0
 **/
public class DefaultWatch implements Watcher {
   private CountDownLatch countDownLatch;

    public void setCountDownLatch(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void process(WatchedEvent event) {
        System.out.println("new zk default watch:" + event.toString());
        switch (event.getState()) {
            case Unknown:
                break;
            case Disconnected:
                break;
            case NoSyncConnected:
                break;
            case SyncConnected:
                countDownLatch.countDown();
                break;
            case AuthFailed:
                break;
            case ConnectedReadOnly:
                break;
            case SaslAuthenticated:
                break;
            case Expired:
                break;
        }
    }
}

再写如下测试类,注意这里要建立一个WatchCallBack,分别是一个监听节点的watcher,以及状态回调
的StatCallback,以及数据回调的DataCallback:

package com.lsd.zookeeper.config;

import org.apache.zookeeper.ZooKeeper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * @ClassName TestConfig
 * @Description
 * @Author SD.LIU
 * @Date 2022/8/19 10:40
 * @Version 1.0
 **/
public class TestConfig {
    ZooKeeper zk;

    @Before
    public void conn() throws Exception {
        zk = ZKUtils.getZooKeeper();
    }

    @After
    public void close() throws InterruptedException {
        zk.close();

    }

    @Test
    public void getConf() throws InterruptedException {
        WatchCallBack watchCallBack = new WatchCallBack();
        watchCallBack.setZooKeeper(zk);
        MyConf myConf = new MyConf();
        //两个线程共享同一个引用
        watchCallBack.setMyConf(myConf);
        //1、节点不存在,会一直等创建节点的回调
        //2、节点已存在,则直接回调取数据
        watchCallBack.aWait();

        while (true) {
            if (myConf.getConf().equals("")) {
                System.out.println("conf loss....");
                watchCallBack.aWait();
            } else {
                System.out.println(myConf.getConf());
            }
            Thread.sleep(10000);
        }

    }


}

package com.lsd.zookeeper.config;

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;

/**
 * @ClassName WatchCallBack
 * @Description
 * @Author SD.LIU
 * @Date 2022/8/19 10:59
 * @Version 1.0
 **/
public class WatchCallBack implements Watcher, AsyncCallback.StatCallback, AsyncCallback.DataCallback {
    private ZooKeeper zooKeeper;
    private MyConf myConf;
    CountDownLatch countDownLatch = new CountDownLatch(1);

    public MyConf getMyConf() {
        return myConf;
    }

    public void setMyConf(MyConf myConf) {
        this.myConf = myConf;
    }

    public ZooKeeper getZooKeeper() {
        return zooKeeper;
    }

    public void setZooKeeper(ZooKeeper zooKeeper) {
        this.zooKeeper = zooKeeper;
    }

    /**
     * dataCallBack
     *
     * @param rc
     * @param path
     * @param ctx
     * @param data
     * @param stat
     */
    @Override
    public void processResult(int rc, String path, Object ctx, byte[] data, Stat stat) {
        if(data !=null) {
            String test = new String(data);
            myConf.setConf(test);
            countDownLatch.countDown();
        }


    }

    /**
     * statCallBack
     *
     * @param rc
     * @param path
     * @param ctx
     * @param stat
     */
    @Override
    public void processResult(int rc, String path, Object ctx, Stat stat) {
        //代表节点已存在
        if (stat != null) {
            // 回调方法processResult(int rc, String path, Object ctx, byte[] data, Stat stat)
            zooKeeper.getData("/AppConf",this,this,"csd");
        }
    }

    public void aWait() {
        //testConf/AppConf
        zooKeeper.exists("/AppConf",this,this,"ctx");
        // 回调方法 processResult(int rc, String path, Object ctx, Stat stat)
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    /**
     * watchCallBack
     * 当节点发生变化的时候
     * @param event
     */
    @Override
    public void process(WatchedEvent event) {

        switch (event.getType()) {
            case None:
                break;
            case NodeCreated:
                //节点刚被创建,还是走取数据的流程
                zooKeeper.getData("/AppConf",this,this,"csd");
                break;
            case NodeDeleted:
                //删除节点就是容忍性的问题
                myConf.setConf("");
                countDownLatch = new CountDownLatch(1);
                break;
            case NodeDataChanged:
                //节点数据被变更了,则重新调用更新配置
                zooKeeper.getData("/AppConf",this,this,"csd");
                break;
            case NodeChildrenChanged:
                break;
        }
    }
}

还有一个配置类是存返回数据的,因为是异步回调,主线程和回调的线程不是同一个线程
,所以这里需要让主线程把这个myconf对象传入进去,并且让回调的线程再把这个myconf的值给设置进去:

package com.lsd.zookeeper.config;

/**
 * @ClassName MyConf
 * @Description 这个class是未来最关心的地方
 * @Author SD.LIU
 * @Date 2022/8/19 11:11
 * @Version 1.0
 **/
public class MyConf {

    private  String conf;

    public String getConf() {
        return conf;
    }

    public void setConf(String conf) {
        this.conf = conf;
    }
}

在这里插入图片描述
刚启动是阻塞的状态
在这里插入图片描述
然后创建一个我客户端设置的节点
在这里插入图片描述
此时是有循环打印的:
在这里插入图片描述
再重新更改一下这个节点的内容:

在这里插入图片描述
那么程序中就会监听到并且打印新的内容:
在这里插入图片描述
删除配置时打印:
在这里插入图片描述
再重新创建更新节点会有如下打印:
在这里插入图片描述

zooKeeper分布式锁的概念

这里拿两个节点来举例,里面有业务只能同步执行,不能两个业务都执行那个业务,则需要
zookeeper来实现一把分布式锁。
分布式锁:
1、争抢锁,只有一个人获得锁
2、获得锁的人出问题了,采用zookeeper的临时节点(session)来规避
3、获得锁成功的人,需要释放锁
4、锁被释放了,删除了,别人是怎么知道的
4-1:主动轮询,心跳,弊端:会有延迟,如果服务器多了以后,会造成zookeeper
压力变大
4-2:使用watch:解决延迟的问题,弊端:通信上面有压力【当其中一个节点释放锁后
,zookeeper会回调其他的所有节点,然后其他的所有节点再抢锁】
4-3:序列节点(sequence)+watch:watch谁?每一个sequnce的人都watch前一个
,最小的获得锁,一旦最小的释放了锁,成本:zk只给第二个发事件回调
在这里插入图片描述

分布式锁代码实现

说明见代码注释:

package com.lsd.zookeeper.lock;

import com.lsd.zookeeper.config.ZKUtils;
import org.apache.zookeeper.ZooKeeper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * @ClassName TestLock
 * @Description
 * @Author SD.LIU
 * @Date 2022/8/19 13:37
 * @Version 1.0
 **/
public class TestLock {
    ZooKeeper zk;

    @Before
    public void conn() throws Exception {
        zk = ZKUtils.getZooKeeper();
    }

    @After
    public void close() throws InterruptedException {
        zk.close();
    }

    @Test
    public void lock() {

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                //每一个线程做的事情
                WatchCallBack watchCallBack = new WatchCallBack();
                watchCallBack.setZooKeeper(zk);
                String threadName = Thread.currentThread().getName();
                watchCallBack.setThreadName(threadName);
                //1、抢锁
                watchCallBack.tryLock();

                //2、干活,如果把如下代码注释掉的话可能会产生,我第二个线程在监控之前第一个线程已经把要监控的节点删除了
                System.out.println("gan huo ....");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //3、释放锁
                watchCallBack.unLock();

            }).start();
        }

        while (true) {

        }

    }
}

package com.lsd.zookeeper.lock;

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;

/**
 * @ClassName WatchCallBack
 * @Description
 * @Author SD.LIU
 * @Date 2022/8/19 13:42
 * @Version 1.0
 **/
public class WatchCallBack implements Watcher, AsyncCallback.StringCallback, AsyncCallback.Children2Callback, AsyncCallback.StatCallback {
    private ZooKeeper zooKeeper;

    private String threadName;

    private CountDownLatch countDownLatch = new CountDownLatch(1);

    private String pathName;

    public String getPathName() {
        return pathName;
    }

    public void setPathName(String pathName) {
        this.pathName = pathName;
    }

    public String getThreadName() {
        return threadName;
    }

    public void setThreadName(String threadName) {
        this.threadName = threadName;
    }

    public ZooKeeper getZooKeeper() {
        return zooKeeper;
    }

    public void setZooKeeper(ZooKeeper zooKeeper) {
        this.zooKeeper = zooKeeper;
    }

    public void tryLock() {
        try {
            //每个线程创建节点,会得到有序的带序列号的
            // /testLock/lock
            System.out.println(threadName + " create....");
            // if(zooKeeper.getData("/")),同时创建十个lock目录
            zooKeeper.create("/lock", threadName.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE,
                    CreateMode.EPHEMERAL_SEQUENTIAL, this, "abc");
            //为了解决第一个节点删除第二个节点监控不到的问题,加上一步将自己的信息写入,并且也是锁的重入
            zooKeeper.setData("/", threadName.getBytes(), -1);

            countDownLatch.await();
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


    }

    public void unLock() {
        try {
            // -1忽略版本的判定
            zooKeeper.delete(pathName, -1);
            System.out.println(threadName + " over work");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    /**
     * zooKeeper.exists watch
     *
     * @param event
     */
    @Override
    public void process(WatchedEvent event) {
        //如果第一个人的锁释放了,其实只有第二个收到了回调的事件
        //如果,不是第一个人,某一个挂了,那么也能造成它后边的节点收到这个通知,从而让它后面的人
        //去监控挂掉的这个机器的前一个
        switch (event.getType()) {
            case None:
                break;
            case NodeCreated:
                break;
            case NodeDeleted:
                //重新去获取锁的目录,重新完成监控
                zooKeeper.getChildren("/", false, this, "cde");
                break;
            case NodeDataChanged:
                break;
            case NodeChildrenChanged:
                break;
        }
    }

    /**
     * StringCallBack
     * create的回调
     *
     * @param rc
     * @param path
     * @param ctx
     * @param name
     */
    @Override
    public void processResult(int rc, String path, Object ctx, String name) {
        if (name != null) {
            //返回对应的名称
            System.out.println(threadName + "create node : " + name);
            this.pathName = name;
            //testLock,父节点不需要监控 回调到processResult
            //此处watch为false的原因为锁不需要关注其目录的变化,要关注它前面的变化,然后进入getChildren回调方法
            zooKeeper.getChildren("/", false, this, "cde");
        }

    }


    /**
     * getChildren call back
     *
     * @param rc
     * @param path
     * @param ctx
     * @param children
     * @param stat
     */
    @Override
    public void processResult(int rc, String path, Object ctx, List<String> children, Stat stat) {
        //创建成功后看子节点的目录回调,能看到前面的包含自己的所有节点
        /*  System.out.println(threadName+"look locks");
         for (String child : children) {
            System.out.println(child);
        }*/
        //此时发现上面是乱序的,将自己的children排序
        Collections.sort(children);
        //判断自己是不是在1位置上面
        int i = children.indexOf(pathName.substring(1));
        if (i == 0) {
            //如果是第一个,那么拿到锁,自己countdown,那么自己await的那个地方就能顺着往下走
            System.out.println(threadName + " :i am first");
            countDownLatch.countDown();
        } else {
            // no 监控的是前面的一个节点,而不是自己的节点,如果前边的节点发生删除的时候才会回调自己
            //找前面一个并放一个watch进去,监控他是否被删除了,如果它前面一个被删除了
            // 那么就得重新触发排序看自己是否是第一个
            //然后此处的回调暂时没写
            zooKeeper.exists("/" + children.get(i - 1), this, this, "asd");

        }


    }

    @Override
    public void processResult(int rc, String path, Object ctx, Stat stat) {
        //暂不加回调逻辑
    }
}

结果如下:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值