Zookeeper客户端Curator使用(分布式锁实现)

说明

注意: 如未安装Zookeeper服务端,请先移步《docker安装Zookeeper(单点)》搭建简易zk环境。

使用Curator框架实现简单CRUD操作请看《Zookeeper客户端Curator使用(增删改查)》

本篇文章关于Zookeeper实现分布式锁。

概述

1. 线程锁和分布式锁的区别

线程锁:主要用来给方法、代码块加锁。当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段。

线程锁只在同一JVM中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比如synchronized是共享对象头,显示锁Lock是共享某个变量(state)。

分布式锁:分布式锁,即分布式系统中的锁,解决了分布式系统中控制共享资源访问的问题。分布式锁是在分布式或者集群环境下,多进程可见,并且互斥的锁。

2. ZK实现分布式锁分析

客户端向zookeeper集群建立连接并在一个永久节点下创建有序的临时子节点后,根据编号顺序,最小顺序的子节点获取到锁,其他子节点由小到大监听前一个节点。

当拿到锁的节点处理完事务后,释放锁,后一个节点监听到前一个节点释放锁后,立刻申请获得锁,以此类推。

3. ZK实现分布式锁流程图

使用

1.原生Zookeeper代码实现分布式锁

按照上面zk分布式锁分析和流程图,使用原生zookeeper相关的api实现分布式锁,具体代码如下

Java 复制代码
public static class DistributedLock {
    private ZooKeeper client;

    /**
     * 等待zk连接成功
     */
    private CountDownLatch countDownLatch;

    /**
     * 等待节点变化
     */
    private CountDownLatch waitLatch;

    /**
     * 当前节点
     */
    private String currentNode;

    /**
     * 前一个节点路径
     */
    private String waitPath;

    /**
     * 根节点
     */
    private String lockPath;

    public DistributedLock(String connectString, int sessionTimeOut, String path) throws Exception {
        countDownLatch = new CountDownLatch(1);
        waitLatch = new CountDownLatch(1);
        lockPath = path;
        client = new ZooKeeper(connectString, sessionTimeOut, watchedEvent -> {
            // 连上ZK后,释放
            if (watchedEvent.getState() == Watcher.Event.KeeperState.SyncConnected) {
                countDownLatch.countDown();
            }

            // waitLatch 需要释放 (节点被删除并且删除的是前一个节点)
            if (watchedEvent.getType() == Watcher.Event.EventType.NodeDeleted &&
                    watchedEvent.getPath().equals(waitPath)) {
                waitLatch.countDown();
            }
        });

        // 等待Zookeeper连接成功,连接完成继续往下走
        countDownLatch.await();

        Stat stat = client.exists(lockPath, false);
        if (stat == null) {
            // 根节点不存在,则创建
            client.create(lockPath, lockPath.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }
    }

    /**
     * 加锁
     */
    public void lock() {
        try {
            // 创建有序临时子节点
            currentNode = client.create(lockPath + "/seq-", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);

            // 如果是最小序号节点,则获取锁;如果不是就监听前一个节点
            List<String> children = client.getChildren(lockPath, false);

            // 子节点排序
            Collections.sort(children);

            // 截取子节点名称
            String nodeName = currentNode.substring((lockPath + "/").length());

            // 通过名称获取子节点在集合的位置
            int index = children.indexOf(nodeName);

            if (index == -1) {
                System.out.println("数据异常");
            } else if (index == 0) {
                // 最小序号子节点,则获取锁
                return;
            } else {
                // 监听前一个节点变化
                waitPath = (lockPath + "/") + children.get(index-1);
                client.getData(waitPath,true,null);

                waitLatch.await();
                return;
            }

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

    /**
     * 释放锁
     *
     * @throws KeeperException
     * @throws InterruptedException
     */
    public void unlock() throws KeeperException, InterruptedException {
        client.delete(currentNode, -1);
    }
}

模拟两个进程抢占分布式锁流程,测试代码如下:

Java 复制代码
public static void main(String[] args) throws Exception {
    String connString = "192.169.7.171:2181";
    int sessionTimeOut = 3000;
    String lockPath = "/jms";
    // 创建两个分布式锁,模拟两个进程抢占分布式锁流程
    DistributedLock lock1 = new DistributedLock(connString, sessionTimeOut, lockPath);
    DistributedLock lock2 = new DistributedLock(connString, sessionTimeOut, lockPath);

    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                lock1.lock();
                System.out.println("线程 [" + Thread.currentThread().getName() + "]" + "->抢到分布式锁--开始工作");

                Thread.sleep(5000);
                lock1.unlock();
                System.out.println("线程 [" + Thread.currentThread().getName() + "]" + "->释放分布式锁--结束工作");
            } catch (InterruptedException | KeeperException e) {
                e.printStackTrace();
            }
        }
    }).start();

    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                lock2.lock();
                System.out.println("线程 [" + Thread.currentThread().getName() + "]" + "->抢到分布式锁--开始工作");

                Thread.sleep(5000);
                lock2.unlock();
                System.out.println("线程 [" + Thread.currentThread().getName() + "]" + "->释放分布式锁--结束工作");
            } catch (InterruptedException | KeeperException e) {
                e.printStackTrace();
            }
        }
    }).start();
}

2.使用Curator框架实现分布式锁

首先要引入Curator框架

Java 复制代码
<dependency>
      <groupId>org.apache.curator</groupId>
      <artifactId>curator-framework</artifactId>
      <version>5.4.0</version>
</dependency>
<dependency>
      <groupId>org.apache.curator</groupId>
      <artifactId>curator-recipes</artifactId>
      <version>5.4.0</version>
</dependency>

使用InterProcessMutex类来实现分布式锁

Java 复制代码
public static class DistributedLock {
    private CuratorFramework client;
    private InterProcessMutex mutex;

    public DistributedLock(String connString, String lockPath) {
        this(connString, lockPath, new ExponentialBackoffRetry(3000,5));
    }

    public DistributedLock(String connString, String lockPath, ExponentialBackoffRetry retryPolicy) {
        try {
            client = CuratorFrameworkFactory.builder()
                    .connectString(connString)
                    .retryPolicy(retryPolicy)
                    .build();
            client.start();

            mutex = new InterProcessMutex(client, lockPath);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取分布式锁
     *
     * @throws Exception
     */
    public void acquire() throws Exception {
        mutex.acquire();
    }

    /**
     * 获取分布式锁(指定时间)
     *
     * @param time  时间
     * @param unit  时间单位
     * @return boolean
     * @throws Exception
     */
    public boolean acquire(long time, TimeUnit unit) throws Exception {
        return mutex.acquire(time, unit);
    }

    /**
     * 释放分布式锁
     *
     * @throws Exception
     */
    public void release() throws Exception {
        mutex.release();
    }
}

模拟50个进程抢占分布式锁,测试代码如下:

Java 复制代码
public static void main(String[] args) {
    // 模拟50个进程抢占分布式锁
    String connString = "192.169.7.171:2181";
    int threadCount = 50;
    CountDownLatch countDownLatch = new CountDownLatch(threadCount);
    for (int i = 0; i < threadCount; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    countDownLatch.await();

                    // 1.创建分布式锁
                    DistributedLock lock = new DistributedLock(connString, "/jms");

                    // 2.抢分布式锁
                    lock.acquire();

                    System.out.println("线程 [" + Thread.currentThread().getName() + "]" + "->抢到分布式锁--开始工作");

                    // 3.模拟执行业务逻辑
                    Thread.sleep(500L);

                    // 4.释放分布式锁
                    lock.release();
                    System.out.println("线程 [" + Thread.currentThread().getName() + "]" + "->释放分布式锁--结束工作");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();

        countDownLatch.countDown();
    }
}

欢迎关注公众号: 趣丸技术,回复关键词curator-example获取示例代码

相关推荐
想用offer打牌3 小时前
MCP (Model Context Protocol) 技术理解 - 第二篇
后端·aigc·mcp
KYGALYX5 小时前
服务异步通信
开发语言·后端·微服务·ruby
Hello.Reader5 小时前
Flink ZooKeeper HA 实战原理、必配项、Kerberos、安全与稳定性调优
安全·zookeeper·flink
掘了5 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
爬山算法5 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
Moment6 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
Cobyte6 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc
程序员侠客行7 小时前
Mybatis连接池实现及池化模式
java·后端·架构·mybatis
Honmaple7 小时前
QMD (Quarto Markdown) 搭建与使用指南
后端
PP东8 小时前
Flowable学习(二)——Flowable概念学习
java·后端·学习·flowable