zookeeper (五) zookeeper 实现分布式配置注册&分布式锁

zookeeper 实现分布式配置注册&分布式锁

1.分布式配置注册

1.1 简介

分布式环境下,多个服务节点需要相互同步配置,一个服务配置修改完成,需要其他服务对修改的配置可见,并及时的同步修改的数据;

zookeeper (五) zookeeper 实现分布式配置注册&分布式锁_第1张图片

1.2 实现方式

zookeeper 就是提供一系列原语和功能,基于这些原语和功能 我们可以实现分布式配置注册 ;

原语:get ,set ,create,delete

功能:watch ,客户端的回调。。

1.3 简单实现

1. ZKTest主类

测试启动类

package com.msb.zookeeper.configuration;


import org.apache.zookeeper.ZooKeeper;

import java.util.concurrent.CountDownLatch;

/**
 * 测试类
 */
public class ZKTest {
    /**
     *  path
     */
    public static String CHILD_WORK_NODE = "/name";

    /**
     * 分布式配置的配置数据  : path 的 data 数据 ;
     */
    public static String config ;

    /**
     * 获取 ZooKeeper 连接
     */
    public static ZooKeeper zkClient = ZKUtils.zkClient;

    /**
     * 用于阻塞
     */
    public static CountDownLatch cdl = new CountDownLatch(1);

    /**
     * 同步配置
     */
    public static void syncConf() {
        //存在吗
        zkClient.exists(CHILD_WORK_NODE,  ZNodeWatcher.watchInstance, ZNodeStatCallBack.statCB, "syncConf aWait");
        try {
            cdl.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    public static void main(String[] args) {

        //............程序获取分布式配置 并设置回调同步......
        syncConf();


        //模拟其他线程 获取配置 干事情
        new Thread(()->{
            while (true) {
                System.out.println(Thread.currentThread().getName()+" ::"+config);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

2.ZKUtils

获取连接类

package com.msb.zookeeper.configuration;

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

import java.util.concurrent.CountDownLatch;

/**
 * @Author sff
 * @Description 连接工具  做zk连接 释放连接等
 * @Date 19:40 2022/4/17
 **/
public class ZKUtils {

    /**
     * 设置连接和
     */
    public static String CONNECTIONURL = "192.168.141.131:2181,192.168.141.132:2181,192.168.141.133:2181,192.168.141.134:2181/iconfig" ;
    /**
     * 连接的过期时间
     */
    public static Integer SESSION_TIMEOUT = 4000;

    /**
     * 外部使用
     **/
    public static ZooKeeper zkClient = getConnection();


    /**
     * 初始化  获取连接
     * @return
     */
    public static ZooKeeper getConnection() {
        ZooKeeper zkClient  = null;
        try {
            CountDownLatch downLatch = new CountDownLatch(1);
            zkClient = new ZooKeeper( CONNECTIONURL,  SESSION_TIMEOUT, new Watcher() {
                @Override
                public void process(WatchedEvent event) {
                    Event.KeeperState state = event.getState();
                    switch (state) {
                        case Disconnected:
                            break;
                        case SyncConnected:
                            downLatch.countDown();
                            break;
                        case AuthFailed:
                            break;
                        case Expired:
                            break;
                    }
                }
            });
            downLatch.await();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return zkClient;
    }
}

3.ZNodeWatcher

节点的watch

package com.msb.zookeeper.configuration;

import lombok.SneakyThrows;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;

import java.util.concurrent.CountDownLatch;

import static com.msb.zookeeper.configuration.ZKTest.CHILD_WORK_NODE;

public class ZNodeWatcher implements Watcher {

    public static final ZNodeWatcher watchInstance = new ZNodeWatcher();

    public static final ZooKeeper zkClient = ZKUtils.zkClient;

    @SneakyThrows
    @Override
    public void process(WatchedEvent event) {
        switch (event.getType()) {
            case None:
                break;
            case NodeCreated:
                System.out.println("节点被创建  注册 watch 和数据改变回调");
                //节点被创建  注册 watch 和数据改变回调
                zkClient.getData(CHILD_WORK_NODE, watchInstance,
                        ZNodeDataCallBack.dataCB, "NodeCreated");
                break;
            case NodeDeleted:
                //节点被删除  继续阻塞
                ZKTest.config = null;
                ZKTest.cdl = new CountDownLatch(1);
                System.out.println("NodeDeleted  :" + Thread.currentThread().getName());
                //为什么不能放这里  如果放在这里 就是 这个是异步的太快了节点,或者的是板顶在后面
                new Thread(() -> {
                    System.out.println("NEW NodeDeleted  :" + Thread.currentThread().getPriority());
                    ZKTest.syncConf();
                }).start();

                break;
            case NodeDataChanged:
                //节点被创建  注册 watch 和 数据改变回调
                zkClient.getData(CHILD_WORK_NODE, watchInstance,
                        ZNodeDataCallBack.dataCB, "NodeDataChanged");
                break;
            case NodeChildrenChanged:
                break;
        }
       
    }

}

4.ZNodeStatCallBack

节点状态改变回调

/**
 * 节点数据改变的 节点存在 添加watch 并 数据改变回调
 */
public class ZNodeStatCallBack implements AsyncCallback.StatCallback {

    public static ZNodeStatCallBack statCB = new ZNodeStatCallBack();


    @Override
    public void processResult(int rc, String path, Object ctx, Stat stat) {
        //节点存在 添加watch 和 节点 数据改变回调
        //节点不存在 什么都不做
        if (stat != null) {
            ZKUtils.zkClient.getData(ZKTest.CHILD_WORK_NODE, ZNodeWatcher.watchInstance,
                    ZNodeDataCallBack.dataCB, "ZNodeStatCallBack");
        }
    }
}

5.ZNodeDataCallBack

节点数据改变回调

/**
 * 节点数据改变的 回调方法  ==》 数据存在就 设置本地配置 并解除阻塞
 */
public class ZNodeDataCallBack implements AsyncCallback.DataCallback {

    public  static ZNodeDataCallBack dataCB = new ZNodeDataCallBack();

    @Override
    public void processResult(int rc, String path, Object ctx, byte[] data, Stat stat) {
        if(data != null ){
            ZKTest.config = new String(data);
            ZKTest.cdl.countDown();
        }
    }
}

1.4 步骤图

  • 开始同步配置
  • exist 方法 判断 path, 添加 watch 和 回调 ;
  • 节点存在,获取到节点,在添加一个数据存在回调,有数据 修,改数据 更新本地配置文件 解除缓存;(还会有个watch 在 path 上
  • 节点不存在,一直阻塞 (还会有个watch 在 path 上
  • 一直阻塞 ,等待其他服务节点数据 添加或者修改数据 触发watch
  • watch 中触发 getData 获取数据再次添加此watch 并且添加一个 数据存在回调 有数据 修,改数据 更新本地配置文件 解除缓
  • 这个watch 一直存在这个path 上;
  • 其他场景:数据删除,在watch 的NodeDeleted 中删除本地配置并且 syncConf ,

zookeeper (五) zookeeper 实现分布式配置注册&分布式锁_第2张图片

1.5 小问题

到此,只是学习ZK 的API 的一部分,还有很多小问题,不如在 1.4 中的其他场景,在删除path 触发的watch NodeDeleted,重新 syncConf必须在新线程里面(上文代码);

2.分布式锁

2.1简介

也会在分布式的场景中,多个服务节点同时修改同一个资源;为了避免多个节点并发修改的问题,可以采用分布式锁的方案;节点在获取资源的同时,先去获取一把锁,这个锁只能在同一时间被一个节点获取,其他节点一直处于阻塞的状态,直到前一个节点修改资源完成,并释放了锁之后,再去获得锁;使得并发的工作在分布式锁的干预下变成一个线性的过程;

zookeeper (五) zookeeper 实现分布式配置注册&分布式锁_第3张图片

2.2实现方式

锁的基本要求

  • 多个节点获取,只有一个可以获取,
  • 获取到锁的节点出现异常,要释放锁 ,zk 的临时节点可以解决
  • 业务执行后,需要释放锁
  • 重入 ,一个节点可以重复获取锁
  • 锁释放,其它节点要知道 ,怎么知道 ? watch

watch 虽好,但是多个节点watch 一个path ,可能会有压力;解决方案:临时节点+ sequence (并发创建);序列小的在前,添加watch,第二个

2.3简单实现

1.ZKLock

public class ZKLock {

    @SneakyThrows
    public static void main(String[] args) {
        //模拟  多服务节点 获取锁
        //重入特性重新设置
        ZKUtils.zkClient.setData("/", "".getBytes(), -1);
        for (int i = 0; i < 10; i++) {
            new Thread() {
                @Override
                public void run() {
                    WatchCallBack watchCallBack = new WatchCallBack();
                    String threadName = Thread.currentThread().getName();
                    watchCallBack.setThreadName(threadName);
                    watchCallBack.tryLock();
                    //干活
                    // do something 
                    //释放锁
                    watchCallBack.unLock();
                }
            }.start();
        }
    }

}

2.WatchAndCallBack

@Data
public class WatchCallBack   implements Watcher, AsyncCallback.StringCallback ,AsyncCallback.Children2Callback {


    //创建该对象的线程
    String threadName;
    //创建的并发节点的路径
    String pathName;
    //加锁阻塞 直到最后加上锁才释放
    CountDownLatch cc = new CountDownLatch(1);


    @SneakyThrows
    public void tryLock(){
        try {
            byte[] data = ZKUtils.zkClient.getData("/", false, new Stat());
            if(new String(data).equals(threadName)){
                //可以重入
            }else{
                ZKUtils.zkClient.create("/lock",threadName.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL,
                        /*** StringCallback **/
                        this,"aaa");
                cc.await();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * StringCallback  对象创建后回调
     */
    @Override
    public void processResult(int rc, String path, Object ctx, String name) {
        //获取到创建后的节点名称 给 pathName
        if(name != null ){
            pathName =  name ;
            System.out.println("init info:"+ threadName+ "  to: "+ pathName);
            //给该节点的父节点添加 一个 getChildren 回调
            ZKUtils.zkClient.getChildren("/",false,
                    /***Children2Callback ***/
                    this ,"bbb");
        }

    }

    /**
     *  Children2Callback
     */
    @Override
    public void processResult(int rc, String path, Object ctx, List<String> children, Stat stat) {
        //一定能看到自己前边的。。
        System.out.println(threadName+" try get lock ");
        for (String child : children) {
            System.out.println(child);
        }
        Collections.sort(children);
        int i = children.indexOf(pathName.substring(1));

        //是不是第一个
        if(i == 0){
            //yes
            System.out.println(threadName +" is first lock,get lock ,起飞 ");
            System.out.println(pathName);
            try {
                ZKUtils.zkClient.setData("/",threadName.getBytes(),-1);
                cc.countDown();

            } catch (KeeperException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            System.out.println(threadName +" not first lock ,add watch to "+children.get(i-1));
            ZKUtils.zkClient.exists("/" + children.get(i - 1),
                    /*** watch */
                    this, new StatCallback() {
                @Override
                public void processResult(int rc, String path, Object ctx, Stat stat) {
                    //stat 为空  提前释放了  这个时候也可以
                    if(stat == null){
                        cc.countDown();
                    }
                }
            }, "ccc");
        }
    }

    @Override
    public void process(WatchedEvent event) {
        switch (event.getType()) {
            case None:
                break;
            case NodeCreated:
                break;
            case NodeDeleted:
                // 1. 释放锁 通知后面的第一个拿到锁
                // 2. 不是第一个 和前一个关联
                /****Children2Callback ***/
                ZKUtils.zkClient.getChildren("/",false,this ,"sdf");
                break;
            case NodeDataChanged:
                break;
            case NodeChildrenChanged:
                break;
        }

    }

    public void unLock(){
        try {
            System.out.println(threadName + " unload ,deleted path:" + pathName);
            ZKUtils.zkClient.delete(pathName,-1);
          } catch ( Exception e) {
            e.printStackTrace();
        }
    }
}

2.4步骤图

  • 多个时间同一时间并发写入节点,开始阻塞;并添加写入成功回调;
  • 获取写入节点路径,获取父路径下所有节点,并添加回调
  • 回调为,判断节点路径,是不是所有节点的第一个,是的话,就获取到锁,解除阻塞
  • 不是第一个 添加watch 到自己序列的上一个节点,watch NodeDelete 事件 ,继续阻塞
  • watch 事件被触发, 再次判断是不是第一个节点,重复上一步,直到是第一个节点,解除阻塞
  • 所有节点都执行前面几个步骤 ,直到所有结束;

zookeeper (五) zookeeper 实现分布式配置注册&分布式锁_第4张图片

2.5 小问题

其中一个服务节点在获取到所有节点,并且是在判断自己节点是不是第一个节点的时候,上一个节点就已经把自己释放;会就导致后面无法继续执行!!!

前一个节点获取到释放过快(业务逻辑时间间隙),后面节点在判断的时候较慢 (判断时间)(加watch 的时候);

处理:1. 业务逻辑时间> 判断时间; 2. 给前一个exits 做判断;

//stat 为空  提前释放了  这个时候也可以
if(stat == null){
   cc.countDown();
}

判断节点路径,是不是所有节点的第一个,是的话,就获取到锁,解除阻塞

  • 不是第一个 添加watch 到自己序列的上一个节点,watch NodeDelete 事件 ,继续阻塞
  • watch 事件被触发, 再次判断是不是第一个节点,重复上一步,直到是第一个节点,解除阻塞
  • 所有节点都执行前面几个步骤 ,直到所有结束;

zookeeper (五) zookeeper 实现分布式配置注册&分布式锁_第5张图片

2.5 小问题

其中一个服务节点在获取到所有节点,并且是在判断自己节点是不是第一个节点的时候,上一个节点就已经把自己释放;会就导致后面无法继续执行!!!

前一个节点获取到释放过快(业务逻辑时间间隙),后面节点在判断的时候较慢 (判断时间)(加watch 的时候);

处理:1. 业务逻辑时间> 判断时间; 2. 给前一个exits 做判断;

//stat 为空  提前释放了  这个时候也可以
if(stat == null){
   cc.countDown();
}

你可能感兴趣的:(zookeeper,zookeeper,分布式,分布式锁,分布式注册中心)