基于zookeeper原生api实现分布式锁

原理图

image.png

代码

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * @author: sjx
 * @date: 2020/3/8 09:50
 * @description: 基于zk原生api 实现分布式锁
 */
public class DistributedLock implements Lock, Watcher {
    private ZooKeeper zooKeeper;
    //定义一个根节点
    private String ROOT_LOCK = "/locks";
    //等待前一个锁
    private String WAIT_LOCK;
    // 表示当前获得的锁
    private String CURRENT_LOCK;

    private CountDownLatch countDownLatch;

    public DistributedLock() {
        try {
            zooKeeper = new ZooKeeper("ip:端口",
                    4000, this);
            //判断根节点是否存在
            Stat exists = zooKeeper.exists(ROOT_LOCK, false);
            if (null == exists) {
                //如果不存在,则创建
                //创建一个永久节点
                zooKeeper.create(ROOT_LOCK, "0".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void lock() {
        if (this.tryLock()) {
            //获得锁成功
            System.out.println(Thread.currentThread().getName() + "->" + CURRENT_LOCK + "->获得锁成功");
            return;
        }
        waitForLock(WAIT_LOCK);
    }

    /**
     * 阻塞= 等待前一个节点释放锁
     *
     * @param prev
     * @return
     */
    private boolean waitForLock(String prev) {
        //监听上一个节点是否释放锁 watch =true
        try {
            //假如此时节点prev= /lock_seq1 ,那么 /lock_seq2 节点则要监听/lock_seq1 是否释放锁
            Stat exists = zooKeeper.exists(prev, true);
            if (exists != null) {
                //如果上个节点还没释放锁
                System.out.println(Thread.currentThread().getName() + "->等待锁:" + ROOT_LOCK + "/" + prev + "释放");
                countDownLatch = new CountDownLatch(1);
                countDownLatch.await();
                System.out.println(Thread.currentThread().getName() + "->获得锁成功");
            }
        } catch (KeeperException e) {
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return true;
    }


    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        //创建一个临时有序节点
        try {
            CURRENT_LOCK = zooKeeper.create(ROOT_LOCK + "/", "0".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
            System.out.println(Thread.currentThread().getName() + "->" + CURRENT_LOCK + ",尝试竞争锁");
            //获取根节点下的所有的子节点
            List childrens = zooKeeper.getChildren(ROOT_LOCK, false);
            //对子节点进行排序
            //定义一个集合进行拍讯
            SortedSet sortedSet = new TreeSet<>();
            for (String children : childrens) {
                sortedSet.add(ROOT_LOCK + "/" + children);
            }
            //获得当前所有子节点中最小的节点
            String firstNode = sortedSet.first();
            if (CURRENT_LOCK.equals(firstNode)) {
                //通过当前节点和最小的节点进行比较如果相等则表示获得锁成功
                return true;
            }
            SortedSet lastSmallNode = sortedSet.headSet(CURRENT_LOCK);
            if (!lastSmallNode.isEmpty()) {
                //获得比当前节点更小的最后一个节点,设置给WAIT_LOCK
                WAIT_LOCK = lastSmallNode.last();
                System.out.println("WAIT_LOCK" + WAIT_LOCK);
            }
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) {

        return false;
    }

    /**
     * 释放锁
     */
    @Override
    public void unlock() {
        System.out.println(Thread.currentThread().getName() + "->释放锁" + CURRENT_LOCK);
        try {
            zooKeeper.delete(CURRENT_LOCK, -1);
            CURRENT_LOCK = null;
            zooKeeper.close();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Condition newCondition() {
        return null;
    }

    /**
     * 监听节点的变化
     *
     * @param watchedEvent
     */
    @Override
    public void process(WatchedEvent watchedEvent) {
        if (countDownLatch != null) {
            countDownLatch.countDown();

        }
    }

}

测试


import java.io.IOException;
import java.util.concurrent.CountDownLatch;

/**
 * @author: sjx
 * @date: 2020/3/8 11:11
 * @description: zk原生分布式锁测试
 */
public class DistributedLockTest {
    public static void main(String[] args) throws IOException {
        CountDownLatch countDownLatch = new CountDownLatch(5);
        //五个线程并发获取锁
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    countDownLatch.await();
                    DistributedLock distributedLock = new DistributedLock();
                    //获得锁
                    distributedLock.lock();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, "Thread:" + i).start();
            countDownLatch.countDown();
        }
        System.in.read();

    }
}

结果

Thread:0->/locks/0000000016,尝试竞争锁
Thread:3->/locks/0000000015,尝试竞争锁
Thread:1->/locks/0000000019,尝试竞争锁
Thread:4->/locks/0000000017,尝试竞争锁
Thread:2->/locks/0000000018,尝试竞争锁
Thread:3->/locks/0000000015->获得锁成功
WAIT_LOCK/locks/0000000018
WAIT_LOCK/locks/0000000016
WAIT_LOCK/locks/0000000015
WAIT_LOCK/locks/0000000017
Thread:1->等待锁:/locks//locks/0000000018释放
Thread:0->等待锁:/locks//locks/0000000015释放
Thread:4->等待锁:/locks//locks/0000000016释放
Thread:2->等待锁:/locks//locks/0000000017释放

五个线程并发获取锁,线程三获得锁,其他线程等待,此时必/locks/0000000015 小的节点是Thread:4(/locks/0000000016) 在终端把/locks/0000000016 这个节点删掉(相当于释放锁),zk的watch 机制会通知thread4 此时Thread3已经释放锁。

[zk: localhost:2181(CONNECTED) 7] ls /locks               
[0000000016, 0000000015, 0000000018, 0000000017, 0000000019]
[zk: localhost:2181(CONNECTED) 8] delete /locks/0000000016
[zk: localhost:2181(CONNECTED) 9] 

image.png

至此,分布式锁已经完成(没有考虑获取锁的超时时间以及有很多细节没有处理,此demo 只是能更好的理解利用zookeeper 的数据结构和节点的监听机制在分布式锁上运用的原理)。下篇文章介绍使用Curator 自带的分布式锁(原理相同)。

你可能感兴趣的:(基于zookeeper原生api实现分布式锁)