分布式锁(3) ----- 基于zookeeper的分布式锁

分布式锁系列文章

分布式锁(1) ----- 介绍和基于数据库的分布式锁
分布式锁(2) ----- 基于redis的分布式锁
分布式锁(3) ----- 基于zookeeper的分布式锁

思路

利用zookeeper的临时有序节点和watch实现,思路如下:

  1. 获取锁的请求在锁的根目录下调用create()创建临时有序节点
  2. 在锁的根目录下调用getChildren()获取所有子节点
  3. 判断步骤1创建的临时有序节点是否是所有子节点中最小的一个,是则获取锁,结束获取
  4. 否则对排在该节点的前一个节点调用exists()判断节点是否存在,并设置watch监听节点的删除事件
  5. 如果步骤4返回节点不存在转到步骤2,否则等待节点删除再转到步骤2

注意:

  1. 创建临时节点,是在请求锁的客户端挂了,节点会自动删除
  2. 创建有序节点,是为了防止群体效应,只需监听前一个节点的删除事件,不用监听所有节点的删除事件
  3. 该思路实现的是公平锁

实现

java实现

public boolean tryLock(long waitTime){
    try {
        //1.创建临时有序节点
        myZNode = zk.create(root + "/" + lockName + "-", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        return waitForLock(waitTime);
    } catch (KeeperException e) {
        e.printStackTrace();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return false;
}

private boolean waitForLock(long watiTime) {
    long start = System.currentTimeMillis();
    try {
        while (System.currentTimeMillis() - start < watiTime) {
            //2.获取子节点
            List<String> children = zk.getChildren(root, false);
            List<String> lockNodes = new ArrayList<>();
            for (String s : children) {
                if (s.startsWith(lockName)) {
                    lockNodes.add(s);
                }
            }
            Collections.sort(lockNodes);
            //3.判断是否最小节点
            if (myZNode.equals(root + "/" + lockNodes.get(0))) {
                return true;
            }
            //4.监听前一个节点删除事件
            String seq = myZNode.substring(myZNode.lastIndexOf('/') + 1);
            String waitNode = lockNodes.get(Collections.binarySearch(lockNodes, seq) - 1);
            CountDownLatch latch = new CountDownLatch(1);
            Stat stat = zk.exists(root + "/" + waitNode, watchedEvent -> {
                if (watchedEvent.getType() == Watcher.Event.EventType.NodeDeleted) {
                    latch.countDown();
                }
            });
            if (stat != null) {
                latch.await(watiTime - System.currentTimeMillis() + start, TimeUnit.MILLISECONDS);
            }
        }
    }catch (Exception e){
        deleteNode();
        e.printStackTrace();
    }
    deleteNode();
    return false;
}

public boolean unlock() {
    return deleteNode();
}

private boolean deleteNode(){
    try {
        zk.delete(myZNode, -1);
        return true;
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (KeeperException e) {
        e.printStackTrace();
    }
    return false;
}

测试代码

private static class ZoookeeperLockTest implements Runnable {

    private CyclicBarrier barrier;

    ZoookeeperLockTest(CyclicBarrier barrier) {
        this.barrier = barrier;
    }

    @Override
    public void run() {
        try {
            CountDownLatch latch = new CountDownLatch(1);
            ZooKeeper zk = new ZooKeeper("127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183", 5000, WatchedEvent -> {
                latch.countDown();
            });
            latch.await();
            try {
                DistributeLock lock = new ZookeeperLock(zk, "lock");
                barrier.await();
                lock.tryLock(Integer.MAX_VALUE);
                try {
                    System.out.println(Thread.currentThread().getName() + "get lock");
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName() + "get unlock");
                } finally {
                    lock.unlock();
                }
            } finally {
                zk.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

缺点与解决方案

没有实现重入。可以使用threadlocal保存重入次数,每次解锁-1,到0时才删除节点

curator

curator是apache的顶级开源项目,实现了分布式锁及队列、选举等多种高级功能。同时优化了zookeeper原生api的很多问题,如支持自动连接,递归创建删除节点等等。

private static class CuratorLockTest implements Runnable {

    private CyclicBarrier barrier;

    CuratorLockTest(CyclicBarrier barrier) {
        this.barrier = barrier;
    }

    @Override
    public void run() {
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        CuratorFramework client = CuratorFrameworkFactory.builder()
                .connectString("127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183")
                .sessionTimeoutMs(5000)
                .retryPolicy(retryPolicy)
                .build();
        client.start();
        InterProcessLock lock = new InterProcessMutex(client, "/locks/curator-lock");
        try{
            lock.acquire();
            try {
                System.out.println(Thread.currentThread().getName() + "get lock");
                Thread.sleep(100);
                System.out.println(Thread.currentThread().getName() + "get unlock");
            }finally {
                lock.release();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            client.close();
        }
    }
}

参考资料

http://zookeeper.apache.org/doc/r3.4.13/recipes.html#sc_recipes_Locks
https://www.cnblogs.com/seesun2012/p/9214653.html

猜你喜欢

转载自www.cnblogs.com/wuweishuo/p/10627480.html