zookeeper应用之分布式锁

在分布式系统中多个服务需要竞争同一个资源时就需要分布式锁,这里使用zookeeper的临时顺序节点来实现分布式锁。

在节点X下创建临时顺序节点,getChildren()获取节点X的所有子节点,判断当前节点是否是第一个子节点,如果是就获取锁成功了,如果不是,那么就监听当前节点的前一个节点删除watcher事件,在前一节点删除之前当前线程需要阻塞等待,前一节点删除在watcher事件处理通知当前线程获取锁成功。

实现一:

还是使用curator按照上面的逻辑先来自己实现一个简易版的

首先需要抽象出一个DistributeLock类,有两个操作获取锁和释放锁。剩下的就是存储一些加锁节点路径信息等。

锁类定义如下:

java 复制代码
public class DistributeLock {
    private CuratorFramework client;
    private String ROOT_PATH = "/test_lock";
    //顺序节点的父节点
    private String lockpath;
    //当前创建顺序节点的路径(全路径)
    private String currPath;
    //线程等待latch
    private CountDownLatch latch = new CountDownLatch(1);

    public DistributeLock(CuratorFramework client,String lockpath){
        this.client = client;
        this.lockpath = lockpath;
    }

    public boolean lock(){
        try {
            //创建临时顺序节点
            currPath = client.create().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(ROOT_PATH+"/"+lockpath);
            String lockId = currPath.substring(currPath.lastIndexOf("/")+1);
            List<String> children = client.getChildren().forPath(ROOT_PATH);
            Collections.sort(children);
            //当前节点是顺序节点中第一个,获锁成功
            if(currPath.endsWith(children.get(0))){
                System.out.println(Thread.currentThread().getName() +"get lock0");
                return true;
            }
            /**
             * 不是第一个,监听排在自己前面节点的删除事件
             */
            //获取当前顺序节点前一节点的索引
            int preIndex = Collections.binarySearch(children,lockId)-1;
            //前一节点是否存在
            Stat stat = client.checkExists().forPath(ROOT_PATH+"/"+children.get(preIndex));
            System.out.println(ROOT_PATH+"/"+children.get(preIndex));
            System.out.println(stat==null);
            if(stat != null){//设置节点监听
                CuratorCache cache = CuratorCache.build(client,ROOT_PATH+"/"+ children.get(preIndex));
                cache.listenable().addListener(new CuratorCacheListener() {
                    @Override
                    public void event(Type type, ChildData childData, ChildData childData1) {
                        if(Type.NODE_DELETED.equals(type))
                            latch.countDown();
                    }
                });
                cache.start();
            }else{
                return true;
            }
            //等到前节点删除事件发生
            latch.await();
            System.out.println(Thread.currentThread().getName() +"get lock3");
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return false;
    }

    public void unlock(){
        try {//删除当前节点
            client.delete().forPath(currPath);
            System.out.println(Thread.currentThread().getName() +"release lock");
            client.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

这里使用了CountDownLatch来进行线程阻塞,只是用来实现逻辑,很多细节没有考虑。比如可重入异常控制等。

然后使用这个分布式锁来控制线程执行:

java 复制代码
//定义一个竞争资源
private final static AtomicInteger stock = new AtomicInteger(10);
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
    executor.submit(new Runnable() {
        @Override
        public void run() {
            CuratorFramework client = CuratorFrameworkFactory
                    .newClient("localhost:2181", new ExponentialBackoffRetry(1000, 3));
            client.start();
            DistributeLock lock = new DistributeLock(client,"stock");
            lock.lock();
            int value = stock.decrementAndGet();
            System.out.println("stock change to:"+value);
            try {
                Thread.sleep(new Random().nextInt(2000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.unlock();
        }
    });
}
executor.shutdown();

定义一个竞争资源stock,多个线程对该资源进行操作,一次只允许一个线程进行操作。

实现二:

在curator的recipes包里同样提供了工具类InterProcessMutex用来获取互斥锁。

java 复制代码
//初始化锁,需要client连接和锁路径参数
InterProcessMutex mutexLock = new InterProcessMutex(client,"/mutex_lock");
//获取锁 阻塞等待,有重构方法可以设置等待时间
mutexLock.acquire();
//do sth
//释放锁
mutexLock.release();

他这里的锁就是可重入锁。一个线程acquire要对应同等量的release。

相关推荐
隔窗听雨眠14 分钟前
CI/CD测试数据清理完全指南:从污染根因到三层清理覆盖的完整方案
java·数据库·ci/cd
MetaLite15 分钟前
Spring-Scheduled多实例重复执行怎么办-Redis分布式锁如何只让一个节点运行
redis·分布式·spring
Code知行合壹16 分钟前
数据中台设计
大数据·分布式·spark
Query*19 分钟前
Agent 开发之项目 AI 能力自我进化:通过浏览器自动化与数据采集实现持续学习
java·人工智能·ai·自动化
晓晓_za89866830 分钟前
GEO 搜索源码白帽合规改造:适配各大 AI 信源收录规则
java·开发语言·人工智能·性能优化·开源
Haooog31 分钟前
从 Tool Calling 到 State 并发控制:一个 Agent 如何安全地连续执行任务
java·agent·后端开发
君顾11 小时前
从0到1课后辅导管理系统开发实战:需求文档、架构设计全复盘
java·开发语言
NeoGressAI外贸数字化1 小时前
外贸建站:新手怎么自建独立站完整指南
java·人工智能
前端 贾公子1 小时前
第09章:上下文与记忆 (4)
java·服务器·前端
wjjzhbb1 小时前
中小团队敏捷转型:Scrum还是看板?
java·maven·scrum