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。

相关推荐
爬山算法4 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
kfyty7254 小时前
集成 spring-ai 2.x 实践中遇到的一些问题及解决方案
java·人工智能·spring-ai
猫头虎4 小时前
如何排查并解决项目启动时报错Error encountered while processing: java.io.IOException: closed 的问题
java·开发语言·jvm·spring boot·python·开源·maven
李少兄4 小时前
在 IntelliJ IDEA 中修改 Git 远程仓库地址
java·git·intellij-idea
忆~遂愿4 小时前
ops-cv 算子库深度解析:面向视觉任务的硬件优化与数据布局(NCHW/NHWC)策略
java·大数据·linux·人工智能
小韩学长yyds4 小时前
Java序列化避坑指南:明确这4种场景,再也不盲目实现Serializable
java·序列化
仟濹4 小时前
【Java基础】多态 | 打卡day2
java·开发语言
Re.不晚4 小时前
JAVA进阶之路——无奖问答挑战2
java·开发语言
Ro Jace5 小时前
计算机专业基础教材
java·开发语言
mango_mangojuice5 小时前
Linux学习笔记(make/Makefile)1.23
java·linux·前端·笔记·学习