分布式锁zookeeper实现详解

  • 问题:为什么要使用分布式锁?分布式锁如何实现?
  • 分析
1、主流解决分布式锁的方式使用zookeeper分布式协调工具;
2.....
  • 详细介绍zookeeper实现分布式锁:
  • 一、为什么要使用分布式锁?
java中对于一个jvm而言,jdk提供了lock和同步。
分布式情况下,多个进程对资源产生竞争关系,
多个进程往往在不同的主机上,jdk无法满足。
分布式锁是分布式情况的并发锁。
  • 二、zookeeper实现分布式锁
  • 1、zk节点类型:
持久节点、持久顺序节点、临时节点、临时顺序节点

  • 2、实现思路:
1)创建一个持久节点,代表该分布式锁父节点;
(2)当进程访问共享资源时,调用lock或trylock获取锁;
第一步通过临时顺序节点,建立一个临时顺序节点name+顺序号。
(3)对节点下的所有节点进行排序,判断刚建立的子节点是否是最小节点,
是的话,获取锁。
(4)不是的话,获取上一个顺序节点,给该节点注册监听事件,并阻塞,
监听事件,并获取到锁的控制权。
(5)每个资源当调用完共享锁之后,调用unlock方法,释放该资源。
(6)监听节点监听到前一个节点断开事件后,再次判断该节点
是否是最小一个节点,防止上个节点意外(宕机、时间过长)释放锁。

3、java代码实现:

package com.wqq.registry.zk;


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

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * @ClassName: ZookeeperLock
 * @Description: 实现zk分布式锁
 * 思路:
 * (1)创建一个持久节点,代表该分布式锁父节点;
 * (2)当进程访问共享资源时,调用lock或trylock获取锁;第一步通过临时顺序节点,建立一个临时顺序节点name+顺序号。
 * (3)对节点下的所有节点进行排序,判断刚建立的子节点是否是最小节点,是的话,获取锁。
 * (4)不是的话,获取上一个顺序节点,给该节点注册监听事件,并阻塞,监听事件,并获取到锁的控制权。
 * (5)每个资源当调用完共享锁之后,调用unlock方法,释放该资源。
 * (6)监听节点监听到前一个节点断开事件后,再次判断该节点是否是最小一个节点,防止上个节点意外(宕机、时间过长)释放锁。
 * @Author: wangqq
 * @create: 2019/10/30
 **/

public class ZookeeperLock implements Watcher {

    //zk客户端
    private ZooKeeper zooKeeper;
    //通一类型根节点,也可以传入
    private String root = "/lock";
    //分割符号
    private String splitLock = "_lock_";
    //当前节点
    private String currentNode;
    //等待锁
    private String waitNode;
    //竞争资源标志
    private String lockName;
    //超时时间
    private int sessionTimeout = 30000;
    //未获取锁继续等待,标记
    private CountDownLatch latch;
    //作用 zkclient连接
    private CountDownLatch connected = new CountDownLatch(1);

    /**
     * 构造带有根节点的zk client
     *
     * @param url      // zk 地址
     * @param lockName //锁标志
     */
    public ZookeeperLock(String url, String lockName) {
        this.lockName = lockName;
        try {
            //创建客户端的时候需要监听配置,本代码直接监听this自己
            zooKeeper = new ZooKeeper(url, sessionTimeout, this);
            //建立链接后,执行后面
            connected.await();
            //判断节点是否存在,不用监听
            Stat stat = zooKeeper.exists(root, false);
            if (null == stat) {
                //创建根节点 参数1: 要创建的节点的路径 参数2: 节点数据  参数3: 节点权限  参数4:节点的类型(持久节点)
                zooKeeper.create(root, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    /**
     * 监听事件方法
     *
     * @param watchedEvent
     */
    @Override
    public void process(WatchedEvent watchedEvent) {
        //建立连接用
        if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
            connected.countDown();
        }
        //其他线程放弃锁的标志,判断该进程是否有等待的锁
        if (this.latch != null) {
            //判断当前节点是否是第一个节点;
            // 防止上一个节点宕机,或者意外释放锁的watch
            //。。。。。。。
            //取出所有子节点
            this.latch.countDown();
        }
    }

    /**
     * 获取锁
     */
    public void lock() {
        //尝试获取锁成功,方法结束
        if (this.tryLock()) {
            System.out.println("Thread " + Thread.currentThread().getId() + " " + currentNode + " get lock true");
            return;
        } else {//未获取到锁,等待
            try {
                waitLock(waitNode, sessionTimeout, TimeUnit.MILLISECONDS);
            } catch (KeeperException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 等待锁
     *
     * @param waitNode
     * @param sessionTimeout
     */
    public boolean waitLock(String waitNode, long sessionTimeout, TimeUnit unit) throws KeeperException, InterruptedException {
        synchronized (this) {
            Stat stat = zooKeeper.exists(root + "/" + waitNode, true);//同时注册监听。
            //判断上一个节点是否存在,
            if (null != stat) {
                this.latch = new CountDownLatch(1);
                //等待,这里应该一直等待其他线程释放锁
                this.latch.await(sessionTimeout, unit);
                this.latch = null;
            }
        }
        return true;

    }

    /**
     * 尝试获取锁,指定时间
     *
     * @param time
     * @param unit
     */
    public boolean tryLock(long time, TimeUnit unit) {
        if (tryLock()) {
            return true;
        } else {
            try {
                return waitLock(waitNode, time, unit);
            } catch (KeeperException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return false;
    }


    /**
     * 尝试获取锁,默认时间
     *
     * @return
     */
    public boolean tryLock() {
        if (lockName.contains(splitLock)) {
            throw new RuntimeException("lockName can not contains _lock_");
        }
        try {
            //创建临时子节点
            currentNode = zooKeeper.create(root + "/" + lockName + splitLock, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
            System.out.println(currentNode + "is created");
            //取出所有子节点
            List<String> nodeList = zooKeeper.getChildren(root, false);
            //所有符合标准的锁
            List<String> nodeListFilter = nodeList.stream().filter((String str) -> lockName.equals(str.split(splitLock)[0])).collect(Collectors.toList());
            //排序
            Collections.sort(nodeListFilter);
            //当前锁节点是否是第一个节点
            if (currentNode.equals(root + "/" + nodeListFilter.get(0))) {
                //如果是获取锁
                return true;
            }
            //如果不是的话,获取前一个节点 lockName_lock_/
            String lastNumStr = currentNode.substring(currentNode.lastIndexOf("/") + 1);
            //用二分法从 nodeListFilter 集合中找到lastNumStr的的位置,-1得到在集合中的下标,并获取上个子节点
            waitNode = nodeListFilter.get(Collections.binarySearch(nodeListFilter, lastNumStr) - 1);
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }

    public void unlock() {
        try {
            zooKeeper.delete(currentNode, -1);
            zooKeeper.close();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }

    }
    //测试用例
    static Integer count = 1000;
    static ExecutorService executorService = Executors.newFixedThreadPool(1000);
    public static void main(String[] args) throws InterruptedException {
        IntStream.range(0, 1000).forEach(i -> executorService.execute(new Runnable() {
            @Override
            public void run() {
                ZookeeperLock zookeeperLock = new ZookeeperLock("127.0.0.1:2181", "name");
                zookeeperLock.lock();
                count--;
                System.out.println("count值为" + count);
                zookeeperLock.unlock();
            }
        }));
        System.out.println("等待执行完成");
        //
        TimeUnit.MINUTES.sleep(1);
        System.out.println("count:" + count);

    }

 }


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章