zookeeper分布式锁

前言

前面并发章节中提到各种锁来防止并发带来的异常结果,但是这都是在单机情况下才奏效,如果在分布式情况下,就要采用分布式锁了,分布式锁的实现有很多种,主要有zookeeper和redis分布式锁,此章先说说zookeeper怎么实现分布式锁。

分布式锁实现

前面提到zookeeper的搭建和客户端操作,我们在这基础上来实现一下分布式锁。

zookeeper主要是各种节点,要实现分布式锁可以用创建节点和删除节点来实现,但是创建什么样的节点呢?前面提到zookeeper有四种节点,其中的顺序节点就可以模拟各个线程,每个客户端在同一个根节点下创建顺序节点时,都会创建一个唯一编号的顺序节点,不会重复。而且zookeeper中的临时顺序节点,在客户端断开连接时,就会自动此客户端创建的顺序节点,这就有效防止了死锁的产生。

而且zookeeper中的监听机制可以用来实现释放锁通知其他线程的作用。

下面我们来理一下实现逻辑:定义一个分布式锁的根节点,要想获取锁就要在根节点下创建临时顺序节点,然后看看自己创建的是否是根节点下所有子节点中的第一个(编号最小),如果是第一个就说明获取锁成功,如果不是则获取失败,这是就要加一个监听器,来监听比自己小的前一个节点(只监听前一个,防止羊群效应,如果监听所有的,那一个线程释放锁,就会通知所有其他线程,这样对服务器压力太大;如果只监听前一个,只有前一个节点删除才会得到通知。),如果前一个节点删除了,就会通知自己,这时自己就是最小的节点,说明获取到锁,以此类推,这样分布式锁就实现了,通过上面描述,可以知道zookeeper实现的是公平锁。

代码实现

public class ZKLock {

    private static final String LOCK_PREFIX = "/demo/lock/lock-";
    private static final String PARENT_PATH = "/demo/lock";
    private String currentPath;
    private String prePath;
    private CuratorFramework client;

    public ZKLock() {
        ExponentialBackoffRetry retry = new ExponentialBackoffRetry(1000, 3);
        client = CuratorFrameworkFactory.builder()
                .connectString("123.206.212.170")
                .connectionTimeoutMs(10000)
                .sessionTimeoutMs(10000)
                .retryPolicy(retry).build();
        client.start();
    }
    public static void main(String[] args){
        ZKLock zkLock = new ZKLock();
        for (int i = 0; i < 2; i++) {
            new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + "尝试获取锁");
                    zkLock.lock();
                    System.out.println(Thread.currentThread().getName() + "获取锁成功");
                    zkLock.unLock();
                    System.out.println(Thread.currentThread().getName() + "释放锁");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }

    public synchronized boolean lock() throws Exception{
        // 尝试获取锁
        boolean lock = tryLock();
        if (lock) {
            return true;
        }
        //获取不到,等待其他线程释放锁
        print("获取锁失败,进入等待");
        waitPre();
        print("等待结束,获取到锁");
        return true;
    }

    private boolean tryLock() throws Exception{
        // 创建临时顺序节点
        currentPath = client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(LOCK_PREFIX);
        print("当前节点:" + currentPath);
        // 创建的节点是否是所有子节点中的第一个
        List<String> list = sortChilds();
        print("所有子节点:" + JSONArray.toJSONString(list));
        if (list == null) {
            throw new Exception("创建失败");
        }
        // 是第一个就说明获取到锁,不是则未获取到锁
        return currentPath.equals(list.get(0));
    }

    /**
     * 释放锁
     * @throws Exception
     */
    public void unLock() throws Exception {
        client.delete().forPath(currentPath);
    }

    /**
     * 等待上一个节点删除
     * @throws Exception
     */
    private void waitPre() throws Exception{
        // 上一个节点
        prePath = prePath();
        // 监听上一个节点变化
        CountDownLatch countDownLatch = new CountDownLatch(1);
        client.getData().usingWatcher(new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                print("上一个节点删除了");
                countDownLatch.countDown();
            }
        }).forPath(prePath);
        countDownLatch.await();
    }

    /**
     * 获得所有子节点,并排序
     * @return
     * @throws Exception
     */
    private List<String> sortChilds() throws Exception {
        List<String> list = client.getChildren().forPath(PARENT_PATH);
        if (list != null) {
            list = list.stream().map(e -> e = PARENT_PATH + "/" + e).collect(Collectors.toList());
            Collections.sort(list);
        }
        return list;
    }

    private String prePath() throws Exception{
        List<String> list = sortChilds();
        int i = list.indexOf(currentPath);
        return list.get(i - 1);
    }

    private void print(String s) {
        System.out.println(Thread.currentThread().getName() + " " + s);
    }
}

上面是我们自己手动实现的分布式锁,只是简单示例,在真正使用的时候,建议使用zookeeper客户端Curator自带的分布式锁,下面是客户端实现分布式锁的简单示例:

public class CuratorLock {

    private CuratorFramework client;

    public CuratorLock() {
        ExponentialBackoffRetry retry = new ExponentialBackoffRetry(1000, 3);
        client = CuratorFrameworkFactory.builder()
                .connectString("123.206.212.170")
                .connectionTimeoutMs(10000)
                .sessionTimeoutMs(10000)
                .retryPolicy(retry).build();
        client.start();
    }

    public void test() throws Exception{
        List<String> list = new ArrayList<>();
        list.add("/demo/curatorLock");
        final InterProcessMultiLock lock = new InterProcessMultiLock(client, list);
        int num = 10;
        CountDownLatch downLatch = new CountDownLatch(num);
        for (int i = 0; i < num; i++) {
            new Thread(() -> {
                try {
                    lock.acquire();
                    System.out.println(Thread.currentThread().getName()+"获得锁");
                    Thread.sleep(1000);
                    lock.release();
                    downLatch.countDown();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
        downLatch.await();
    }
    public static void main(String[] args) throws Exception{
        CuratorLock demo = new CuratorLock();
        demo.test();
    }
}

总结

一般公司里都会有现成的zookeeper分布式集群,在平时要使用到分布式锁的时候,可以使用现有的zookeeper集群来实现,但是在高并发的情况下,还是建议使用redis分布式锁,因为zookeeper分布式锁是用节点的创建和删除来实现的,我们都知道,zookeeper中创建和删除节点都是在leader节点上执行的,而leader还要将数据同步到各个follower节点,这样频繁交互是很消耗性能的。

因此在高并发要求不高的情况下推荐使用zookeeper分布式锁,因为他的高可用性。而对并发和性能要求很高的时候,推荐使用redis分布式锁。


扫一扫,关注我

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值