Distributed topic|Are you still using redis to implement distributed locks? Let’s see how Zookeepr elegantly implements distributed locks (exclusive locks|read-write locks)

Distributed topic | How to use zookeeper to implement distributed locks

In distributed, it is inevitable to use distributed locks. In the previous topic, we have already said that using Redis to implement distributed locks. Here I will show you how to use zookeeper to implement distributed locks.

First of all, let me introduce the basic idea of ​​the implementation. By default, everyone has mastered the basic concepts of distributed locks. If you have not understood what distributed locks are used for, you can refer to related articles to understand:

Shared lock (read-write lock):

Both locks are completed based on the characteristics of the sequence number node. Zookeepr helps us to ensure the consistency of the order of creating nodes. Created first, the node sequence number is smaller than the node sequence number created later, so we can achieve this:

Shared lock implementation ideas

Insert picture description here

  • If it is a write lock:

    1. Create a serial number node lock-w-00003
    2. Get all sequence number sub-nodes under the lock node
    3. By comparing the size of the node sequence number, lock-w-00003 is the smallest sequence number node, indicating that the acquisition is successful
    4. Otherwise, if lock-w-00003 is not the smallest serial number node, monitor the node before lock-w-00003 and block the current thread; if the previous node changes, perform the same judgment until lock-w- 00003 is the smallest child node, that is, the lock is successfully acquired;
  • If it is a read lock:

    1. Create a sequence number node lock-r-0004
    2. Get all sequence number sub-nodes under the lock node
    3. By comparing the size of the sequence number node. If there are read nodes in front of lock-r-00004, it means that the read lock is successfully acquired. Otherwise, it monitors the node before lock-r-00004 and blocks the current thread; if the node changes, the same judgment is performed until lock -r-00004 are all read nodes in front of it, get lock-r-00004 is the smallest node, which means the lock is successfully acquired;

Exclusive lock implementation ideas

Insert picture description here

  1. Create a sequence number node, lock-0005
  2. Get all sequence number sub-nodes under the lock node
  3. The comparison found that lock-0005 is the smallest child node, which means that the exclusive lock is successfully acquired;
  4. Otherwise, monitor the node changes in front of lock-0005 and block the current thread
  5. If a node change is found, continue to perform operations 2-4 until the lock is acquired;

Exclusive lock implementation core code and comments

   @Override
    public void lock() {
        if (tryLock()) { //如果获取锁成功,则直接返回
            System.out.println("###成功获取锁###");
        } else {
        // 获取锁失败,监听前面的子节点,阻塞等待
            waitLock();
            // 前面子节点被删除,重新获取锁
            lock();
        }
    }
    @Override
    public void unLock() {
        if(zkClient!=null){
            // 由子类调用,关闭会话,防止回话过多,导致拒绝链接
            zkClient.close();
            System.out.println("###释放所资源###");
        }
    }
public boolean tryLock() {
// 创建临时序号节点
        if (null == currentPath || currentPath.length() <= 0) {
            currentPath = zkClient.createEphemeralSequential(lockPath + "/", "lock");
        }
// 获取所有子节点按照从小到大进行排序
        List<String> children = zkClient.getChildren(lockPath);
        Collections.sort(children);
        // 判断第一个节点是否是自己,如果是自己,则获取锁成功,直接返回
        if (currentPath.equals(lockPath + "/" + children.get(0))) {
            System.out.println("成功获取锁:" + currentPath);
            return true;
        }
        String key = currentPath.substring(lockPath.length() + 1);      
        int location = Collections.binarySearch(children, key);
        // 定位前面一个节点的位置,进行监听
        this.beforePath = lockPath + "/" + children.get(location - 1);
        return false;
    }
// 锁获取失败,会调用此方法
    public void waitLock() {
        IZkDataListener listener = new IZkDataListener() {
            public void handleDataChange(String s, Object o) {
            }

            public void handleDataDeleted(String s) {
                LATCH.countDown();
            }
        };
        // 监听前面一个子节点变化
        zkClient.subscribeDataChanges(this.beforePath, listener);
        // 如果没有子节点,则直接返回,重新获取锁
        if (!zkClient.exists(this.beforePath)) {
            return;
        }
        try {
        // 这里使用JUC中的CountDownLatch 进行阻塞等待
            LATCH.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 被监听的节点发生变化,取消监听,节省资源;
        zkClient.unsubscribeDataChanges(this.beforePath, listener);
    }
// 子类实现的释放锁的方法
   @Override
    public void unLock() {
        System.out.println("成功释放锁"+currentPath);
        zkClient.delete(currentPath);
        // 调用父类方法释放资源
        super.unLock();
    }

The code has been uploaded to Code Cloud https://gitee.com/yangleliu/code_learning.git

Read-write lock implementation core code and comments

The implementation of read-write lock and exclusive lock code is mainly different in the way of tryLock:

Write lock

 public boolean tryLock() {
        if (null == currentPath || currentPath.length() <= 0) {
            currentPath = zkClient.createEphemeralSequential(lockPath + "/write", "lock");
        }
        List<String> children = zkClient.getChildren(lockPath);
        // 按照序号节点进行排序
        Collections.sort(children, Comparator.comparingInt(o -> Integer.parseInt(o.replaceAll("[^0-9]*", ""))));
//        System.out.println("当前孩子:"+children.toString());
        // 如果当前节点是第一个,则代表获取写锁成功
        if (currentPath.equals(lockPath + "/" + children.get(0))) {
            System.out.println("成功获取锁:" + currentPath);
            return true;
        }
        String key = currentPath.substring(lockPath.length() + 1);
        
        int location = children.indexOf(key);
        // 获取前面一个序号节点,并监听
        this.beforePath = lockPath + "/" + children.get(location - 1);
        System.out.println("获取写锁失败"+key);
        System.out.println("当前孩子:"+children.toString());
        return false;
    }

Read lock

  public boolean tryLock() {
        if (null == currentPath || currentPath.length() <= 0) {
            currentPath = zkClient.createEphemeralSequential(lockPath + "/read", "lock");
            System.out.println(currentPath);
        }
        List<String> children = zkClient.getChildren(lockPath);
        // 按照节点进行排序
        Collections.sort(children, Comparator.comparingInt(o -> Integer.parseInt(o.replaceAll("[^0-9]*", ""))));
        // 如果当前节点处于第一个则获取锁成功
        if (currentPath.equals(lockPath + "/" + children.get(0))) {
//            System.out.println("成功获取锁:" + currentPath);
            return true;
        }
        String key = currentPath.substring(lockPath.length() + 1);
        int location = children.indexOf(key);
        // 遍历当前节点位置前面的所有子节点
        for (int i=0;i<location;i++){
            System.out.println(children.get(i));
            // 如果包含写锁,则直接返回获取锁失败,
            if (children.get(i).contains("write")){
                this.beforePath = lockPath + "/" + children.get(location - 1);
                System.out.println("获取读锁失败:当前孩子"+children.toString());
                return false;
            }
        }
        // 如果前面没有写锁,则代表获取锁成功
        return true;
    }

Search on WeChat [AI Coder] Follow the handsome me and reply [Receive dry goods], there will be a lot of interview materials and architect must-read books waiting for you to choose, including java basics, java concurrency, microservices, middleware, etc. More information is waiting for you.

Guess you like

Origin blog.csdn.net/weixin_34311210/article/details/109589123