ZK分布式锁的实现

ZK分布式锁的实现

1. 类图

在这里插入图片描述

2. 实现

2.1 简单实现

通过实现抽象实现AbstractLock,实现共有逻辑

简单实现的想法是每次都监听一个节点,当节点失效的时候通过Zk的监听模式通知客户端来重新争取锁。

AbstractLock
public abstract class AbstractLock {

    public final static String PATH = "/MYLOCK3";

    public static final String ADDRESS = "127.0.0.1:2181";

    public static final int SESSION_TIMEOUT = 1000;

    public final ZkClient zkClient = new ZkClient(ADDRESS, SESSION_TIMEOUT);

//    public final static ZkClient zkClient = MyZkClient.getInstance();


    public void getLock() {
        String threadName = Thread.currentThread().getName();
        if (tryLock()) {
            System.out.println(threadName + "获取锁");
        } else {
            System.out.println(threadName + "获取锁失败,等待锁");
            waitLock();
            getLock();
        }
    }

    public abstract Boolean tryLock();

    public abstract void waitLock();

    public abstract void releaseLock();
}
SimpleLock
public class SimpleZkLock extends AbstractLock {

    private CountDownLatch countDownLatch;

    @Override
    public Boolean tryLock() {
        System.out.println(Thread.currentThread().getName() + "正在获取锁");
        try {
            boolean exists = zkClient.exists(PATH);
            System.out.println(exists);
//        System.out.println(zkClient.getChildren(PATH));
            if (exists) {
                System.out.println("锁占用中。。。");
                return false;
            } else {
                System.out.println("获取锁。。。");
                zkClient.createEphemeral(PATH);
                return true;
            }
        } catch (RuntimeException e) {
            //如果存在会报错NodeExist,这时候返回false即可
//            System.out.println(e);
//            e.printStackTrace();
            return false;
        }
    }

    @Override
    public void waitLock() {
        //监听器
        IZkDataListener iZkDataListener = new IZkDataListener() {
            @Override
            public void handleDataChange(String s, Object o) throws Exception {

            }

            @Override
            public void handleDataDeleted(String s) throws Exception {
                if (countDownLatch != null) {
                    countDownLatch.countDown();
                }
            }
        };

        //监听
        zkClient.subscribeDataChanges(PATH, iZkDataListener);

        if (zkClient.exists(PATH)) {
            countDownLatch = new CountDownLatch(1);
            try {
                countDownLatch.await();
                System.out.println(Thread.currentThread().getName() + "等待锁...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
		//释放监听
        zkClient.unsubscribeDataChanges(PATH, iZkDataListener);

    }

    @Override
    public void releaseLock() {
        System.out.println(Thread.currentThread().getName() + "释放锁成功");
        zkClient.delete(PATH);
        zkClient.close();
    }
}

2.2 更加好的实现

复用AbstractLock,上一份代码存在的问题是每一次当锁被释放的时候就需要通知所有的客户端,但是只有一个是能获取锁的。浪费了很多CPU的性能,其实只要通知一个节点就行。ZK这边有一个顺序临时节点,就能完成这种想法。

代码:

HighQualityLock
public class HighQualityLock extends AbstractLock {
    private String currentPath;

    private String beforePath;

    private CountDownLatch countDownLatch;

    public HighQualityLock() {
        try {
            //初始化根节点
            boolean exists = zkClient.exists(PATH);
            if (!exists) {
                zkClient.createPersistent(PATH);
            }
        } catch (RuntimeException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Boolean tryLock() {
        //创建本次节点
        if (null == currentPath) {
            currentPath = zkClient.createEphemeralSequential(PATH + "/", "lock");
        }

        //在之前创建了一个直接断currentPath,所以永远不为空
        List<String> children = zkClient.getChildren(PATH);
        Collections.sort(children);
//        System.out.println(children);
        if (currentPath.equals(PATH + "/" + children.get(0))) {
            return true;
        } else {
            //获取当前节点的前面一个节点,需要寻找当前节点的前面一个(列表中尾部已经有写入,因为是多线程触发)
            int wz = Collections.binarySearch(children, currentPath.substring(PATH.length() + 1));
            beforePath = PATH + "/" + children.get(wz - 1);
            System.out.println(Thread.currentThread().getName() + " beforePath:" + beforePath);
        }

        return false;
    }

    @Override
    public void waitLock() {
        IZkDataListener iZkDataListener = new IZkDataListener() {
            @Override
            public void handleDataChange(String s, Object o) throws Exception {

            }

            @Override
            public void handleDataDeleted(String s) throws Exception {
                System.out.println("检测到了节点失效");
                if(countDownLatch != null) {
                    countDownLatch.countDown();
                }
            }
        };

        System.out.println(Thread.currentThread().getName() + "监听前一个结点" + beforePath);
        zkClient.subscribeDataChanges(beforePath, iZkDataListener);

        if (zkClient.exists(beforePath)) {
            countDownLatch = new CountDownLatch(1);
            try {
                System.out.println(Thread.currentThread().getName() + "等待释放锁");
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }


        System.out.println(Thread.currentThread().getName() + "运行到这");
        zkClient.unsubscribeDataChanges(beforePath, iZkDataListener);
    }

    @Override
    public void releaseLock() {
        System.out.println(Thread.currentThread().getName() + "释放锁:" + currentPath);
        zkClient.delete(currentPath);
        zkClient.close();
    }
}

2.3 现有框架实现

在ZK上面实现分布式锁的良好框架:Curator

有如下的优点:

  • 对于一个节点的CRUD监控
  • 实现分布式锁
  • 实现barrier、原子计数器
  • 实现队列

有一篇好的ZK关于curator这边的文章:https://blog.csdn.net/haoyuyang/article/details/53469269,实现分布式锁,可重入锁,栅栏等等。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值