SpringBoot整合Zookeeper集群下实现分布式锁

SpringBoot整合Zookeeper集群下实现分布式锁:

首先简单了解一下Zookeeper的分布式锁的执行流程:Zookeeper原理请看后续文章…
在这里插入图片描述

第一步:搭建环境
在linux系统上通过docker的docker-compose.yml文件快速部署zookeeper集群:

docker-compose.yml:通过docker-compose up -d 命令启动

version: '3.1'
services:
    zoo1:
        image: daocloud.io/atsctoo/zookeeper:3.4.8-all-broker
        container_name: zoo1
        ports:
            - "2181:2181"
        environment:
            ZOO_MY_ID: 1
            ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888

    zoo2:
        image: daocloud.io/atsctoo/zookeeper:3.4.8-all-broker
        container_name: zoo2
        ports:
            - "2182:2181"
        environment:
            ZOO_MY_ID: 2
            ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888

    zoo3:
        image: daocloud.io/atsctoo/zookeeper:3.4.8-all-broker
        container_name: zoo3
        ports:
            - "2183:2181"
        environment:
            ZOO_MY_ID: 3
            ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888

启动成功效果:
在这里插入图片描述
利用zookeeper的可视化工具:在这里插入图片描述 可以查看zookeeper中节点,但是权限只时查看,不能进行增删改操作。
工具下载: https://blog.csdn.net/u014636209/article/details/109194920

链接: https://pan.baidu.com/s/1av1cXc8lc6I347NaD6cNfg  
密码: n5p5

点击右上角的添加操作,可视化工具连接效果:
在这里插入图片描述

第二部:SpringBoot整合Zookeeper
gitee源码地址:https://gitee.com/xzq25_com/zookeeper-exercise

利用zookeeper实现分布式锁代理示例:


public class DistributedLock  {

    // 设置zookeeper连接(docker部署zookeeper集群)
    private final String connectString = "120.76.159.196:2181,120.76.159.196:2181,120.76.159.196:2181";

    // 设置超时时间
    private final int sessionTimeout = 5000;

    // 声明zookeeper
    private final ZooKeeper zk;

    // CountDownLatch使用场景
    // 线程计数器 用于线程执行任务,计数 等待线程结束
    private CountDownLatch countDownLatch = new CountDownLatch(1);
    private CountDownLatch waitLatch = new CountDownLatch(1);

    // 定义该临时节点上一个节点的路径
    private String waitPath;

    // 定义临时节点
    private String node;

    public DistributedLock() throws IOException, InterruptedException, KeeperException {
        // 获取连接
        zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                // 如果连接上zk的话便可以对countDownLatch进行释放
                if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
                    countDownLatch.countDown();
                }
                // 如果上一个节点进行了删除节点的操作后则可以对监听进行释放
                if(watchedEvent.getType()==Event.EventType.NodeDeleted && watchedEvent.getPath().equals(waitPath)){
                    waitLatch.countDown();
                    System.out.println("当前线程已释放锁,监听当前节点的后一个节点的线程准备获取锁");
                }
            }
        });

        // 等待zk连接后才会继续往下执行
        countDownLatch.await();

        // 判断根节点/locks是否存在
        Stat stat = zk.exists("/locks", false);

        // 如果节点不存在
        if (stat == null) {
            zk.create("/locks", "locks".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }else{
            int version = stat.getVersion();
            long id = Thread.currentThread().getId();
            System.out.println(version+": 线程【"+id+"】绑定zookeeper初始化成功");
        }
    }

    /**
     * 一:CreateMode类型分为4种
     *1.PERSISTENT--持久型
     *2.PERSISTENT_SEQUENTIAL--持久顺序型
     *3.EPHEMERAL--临时型
     * 4.EPHEMERAL_SEQUENTIAL--临时顺序
     *
     * 二:节点访问权限:
     * 1.OPEN_ACL_UNSAFE:表示当前节点可以被所有客户端访问。
     */
    // 对zk加锁
    public void zkLock() {
        // 创建节点(临时带序号的)
        try {
            // 当前线程id
            long curThreadId = Thread.currentThread().getId();

            node = zk.create("/locks/" + "seq-", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);

            System.out.println("id为:【"+curThreadId+"】创建的节点路径:"+node);
            // 判断节点是否是最小的序号节点,如果是的话就获取到锁;如果不是,监听到他序号前一个节点
            List<String> children = zk.getChildren("/locks", false);

            // 用于判断children中的值
            // 如果只有一个值,你就直接获取锁;如果有多个节点,则需要判断谁最小
            if (children.size() == 1) {
                return;
            } else {
                // 对获取到的节点进行排序方便持续获取节点
                Collections.sort(children);

                // 获取节点的名称 截取掉节点的前缀
                String thisNode = node.substring("/locks/".length());

                // 通过该节点的名称获取该节点在集合中的位置
                int index = children.indexOf(thisNode);
                System.out.println(index+":"+thisNode);
                // 对节点所在的索引进行判断
                if (index == -1) {
                    System.out.println("数据出现错误");
                } else if (index == 0) {
                    // 只有一个节点可以直接获取锁
                    return;
                } else {
                    // 需要监听他前一个节点的变化
                    waitPath = "/locks/" + children.get(index - 1);
                    // 通过获取前一个节点的路径对这个节点进行监听
                    zk.getData(waitPath, true, null);

                    // 等待监听
                    waitLatch.await();
                    System.out.println( "监听到前一个线程已结束,节点已删除,当前线程获取锁");
                    return;
                }
            }
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }


    // 对zk解锁
    public void unZkLock() {
        try {
            // 删除节点
            zk.delete(node,0);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

}

注意,测试多线程的情况不能再@Test方法中测试,会报错,具体原因自定查找,这里放在mian方法中

public class ZkperLockRun {
    public static void main(String[] args){

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {

                    final DistributedLock lock1 = new DistributedLock();

                    long id = Thread.currentThread().getId();

                    lock1.zkLock();
                    System.out.println("线程【"+id+"】启动,获取到锁");
                    System.out.println("执行商品秒杀业务代码.......");
                    Thread.sleep(5 * 1000);
                    System.out.println("线程【"+id+"】结束,释放锁");
                    lock1.unZkLock();

                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {

                    final DistributedLock lock2 = new DistributedLock();

                    long id = Thread.currentThread().getId();

                    lock2.zkLock();
                    System.out.println("线程【"+id+"】启动,获取到锁");
                    System.out.println("执行商品秒杀业务代码.......");
                    Thread.sleep(5 * 1000);
                    System.out.println("线程【"+id+"】结束,释放锁");
                    lock2.unZkLock();

                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {

                    final DistributedLock lock3 = new DistributedLock();

                    long id = Thread.currentThread().getId();

                    lock3.zkLock();
                    System.out.println("线程【"+id+"】启动,获取到锁");
                    System.out.println("执行商品秒杀业务代码.......");
                    Thread.sleep(5 * 1000);
                    System.out.println("线程【"+id+"】结束,释放锁");
                    lock3.unZkLock();

                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {

                    final DistributedLock lock4 = new DistributedLock();

                    long id = Thread.currentThread().getId();

                    lock4.zkLock();
                    System.out.println("线程【"+id+"】启动,获取到锁");
                    System.out.println("执行商品秒杀业务代码.......");
                    Thread.sleep(5 * 1000);
                    System.out.println("线程【"+id+"】结束,释放锁");
                    lock4.unZkLock();

                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

控制台顺序打印结果如下:

节点当前数据版本Version:0: 线程【23】绑定zookeeper初始化成功
节点当前数据版本Version:0: 线程【21】绑定zookeeper初始化成功
节点当前数据版本Version:0: 线程【20】绑定zookeeper初始化成功
节点当前数据版本Version:0: 线程【22】绑定zookeeper初始化成功
线程id为:【21】创建的节点路径:/locks/seq-0000000120
线程id为:【22】创建的节点路径:/locks/seq-0000000121
线程id为:【20】创建的节点路径:/locks/seq-0000000123
线程id为:【23】创建的节点路径:/locks/seq-0000000122
线程【21】节点存储下标:【0: seq-0000000120
线程【21】启动,获取到锁
执行商品秒杀业务代码.......
线程【22】节点存储下标:【1: seq-0000000121
线程【20】节点存储下标:【3: seq-0000000123
线程【23】节点存储下标:【2: seq-0000000122
线程【21】结束,释放锁
当前线程已释放锁,监听当前节点的后一个节点的线程准备获取锁
监听到前一个线程已结束,节点已删除,当前线程获取锁
线程【22】启动,获取到锁
执行商品秒杀业务代码.......
线程【22】结束,释放锁
当前线程已释放锁,监听当前节点的后一个节点的线程准备获取锁
监听到前一个线程已结束,节点已删除,当前线程获取锁
线程【23】启动,获取到锁
执行商品秒杀业务代码.......
线程【23】结束,释放锁
当前线程已释放锁,监听当前节点的后一个节点的线程准备获取锁
监听到前一个线程已结束,节点已删除,当前线程获取锁
线程【20】启动,获取到锁
执行商品秒杀业务代码.......
线程【20】结束,释放锁

简单解释一下打印的顺序思路:

这里测试用例给的是4个线程并发执行,4个线程首先都连接上了zk,初始化成功,接下来4个线程都再zk上创建了zk节点,注意,再zk上创建过程是原子性的,一个一个创建的,这里是不涉及多线程安全问题,创建的节点的顺序已经监听如下图所示:
在这里插入图片描述
之后 21线程获取到锁,执行完业务代码之后再释放锁(在运行结果中可以看出21线程获取锁之后,紧接着其他3个线程在zookeeper上创建了节点,是因为在线程获取锁之后就让当前线程休眠了5秒,5秒足以让后面其他线程在zookeeper上创建节点了,注意这里只是创建了节点,并没有拿到锁,所以没有并发安全问题),此时21释放锁,21的监听撤销,22监听到21释放锁,22就开始获取锁,以此类推,每个线程的节点总是监听前一个节点的变化,如果前一个节点删除了,撤销监听后,当前节点的线程就可以获取锁执行业务代码了。注意:总是最小节点获取锁,Zookeeper做分布式锁执行速度相对于Redis实现分布式锁的速度底,性能相对差,一般不用Zookeeper做分布式锁,Zookeeper可以用在分布式微服务下服务统一治理以及一致性调度问题上等

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值