zookeeper如何设置超时时间 zookeeper锁超时_zookeeper分布式锁


zookeeper如何设置超时时间 zookeeper锁超时_互斥锁_02


Zookeeper实现分布式锁需要满足以下条件

1、在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;
2、高可用的获取锁与释放锁;
3、高性能的获取锁与释放锁;
4、具备可重入特性;
5、具备锁失效机制,防止死锁;
6、具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。

用原生的Zookeeper API接口实现分布式锁不仅仅需要考虑锁机制问题,还需要解决持续监听、session超时、级联创建删除等问题,是不是有点头大,那有没有现成的框架呢。

Apache Curator是一个比较完善的zk客户端框架,封装了一套高级API 简化了zk的操作,并且实现了完善的分布式锁。

1、安装Zookeeper
2、使用分布式锁
3、浅析锁的实现

一、安装Zookeeper

安装过程可以参考

微服务篇-Zookeeper快速入门篇

二、引入Curator依赖

<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.1</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-client</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version>
</dependency>

三、创建锁工厂


@Slf4j
public class ZkLock {
    static CuratorFramework framework= null;
    static {
        framework = CuratorFrameworkFactory.newClient(
                "106.55.154.105:2181",
                20000,
                20000,
                new RetryNTimes(3, 5000));
        framework.start();
    }

    /**
     * 获取互斥锁
     * @param name
     * @return
     * @throws Exception
     */
    public InterProcessMutex getLock(String name) throws Exception {
        return  new InterProcessMutex(framework,buildPath(name));
    }

    /**
     * 获取互斥锁
     * @param name
     * @return
     * @throws Exception
     */
    public InterProcessMutex getMutexLock(String name) throws Exception {
        return  new InterProcessMutex(framework,buildPath(name));
    }

    /**
     * 获取读写锁
     * @param name
     * @return
     * @throws Exception
     */
    public InterProcessReadWriteLock getReadWriteLock(String name) throws Exception {
        return new InterProcessReadWriteLock(framework,buildPath(name));
    }

    /**
     * 获取不可重入互斥锁
     * @param name
     * @return
     * @throws Exception
     */
    public InterProcessSemaphoreMutex getSemaphoreLock(String name) throws Exception {
        return new InterProcessSemaphoreMutex(framework,buildPath(name));
    }

    /**
     * 获取多锁  集合锁
     * @param names
     * @return
     * @throws Exception
     */
    public InterProcessMultiLock getMutilLock(List<String> names) throws Exception {
        return  new InterProcessMultiLock(framework,mutilPath(names));
    }

    public List<String> mutilPath(List<String> names){
        List<String> paths = new ArrayList<>();
        for(String name :names)
        {
            paths.add(buildPath(name));
        }
        return paths;
    }


    public String buildPath(String name)
    {
        String path = "";
        String[] roots = new String[]{"mg","mylock"};
        for(String str : roots)
        {
            if(str.startsWith("/")){
                path +="/";
            }
            path +="/" +str;
        }
        path +="/" +name;
        return path;
    }
}


四、使用分布式锁

Curator提供了四种锁

可重入互斥锁 InterProcessMutex
不可重入互斥锁 InterProcessSemaphoreMutex
读写锁 InterProcessReadWriteLock
集合锁 InterProcessMultiLock

让我们分别使用一下吧

1、可重入互斥锁的demo

可重入互斥锁具备可重入性,试着执行如下代码


@Slf4j
public class Demo1 {

    public void lock1(InterProcessMutex lock) throws Exception {
        lock.acquire();
        log.info("lock1成功获取锁");
        lock2(lock);
        lock.release();
        log.info("lock1成功释放锁");
    }

    public void lock2(InterProcessMutex lock) throws Exception {
        lock.acquire();
        log.info("lock2成功获取锁");
        Thread.sleep(1000*10);

        lock.release();
        log.info("lock2成功释放锁");
    }

    public static void main(String[] args) throws Exception {
        ZkLock zkLock = new ZkLock();
        InterProcessMutex lock = zkLock.getLock("demo");
        Demo1 demo1 = new Demo1();
        demo1.lock1(lock);

    }
}


控制台输出

22:57:49.922 [main] INFOcom.mg.lock.demo.zk.Demo1 - lock1成功获取锁
22:57:49.922 [main] INFOcom.mg.lock.demo.zk.Demo1 - lock2成功获取锁
22:57:59.923 [main] INFOcom.mg.lock.demo.zk.Demo1 - lock2成功释放锁
22:58:00.008 [main] INFOcom.mg.lock.demo.zk.Demo1 - lock1成功释放锁

使用ZooInspector查看zk中的锁节点,两次加锁只有一个节点,如下图


zookeeper如何设置超时时间 zookeeper锁超时_zookeeper分布式锁_03


2、不可重入互斥锁的demo

不可重入互斥锁不具备锁的可重入性,执行如下代码


@Slf4j
public class Demo2 {

    public void lock1(InterProcessSemaphoreMutex lock) throws Exception {
        lock.acquire();
        log.info("lock1成功获取锁");
        lock2(lock);
        lock.release();
        log.info("lock1成功释放锁");
    }

    public void lock2(InterProcessSemaphoreMutex lock) throws Exception {
        log.info("lock2尝试获取锁");
        boolean result = lock.acquire(1000*2, TimeUnit.MILLISECONDS);

        if(result)
        {
            log.info("lock2成功获取锁");
            Thread.sleep(1000*10);
            lock.release();
            log.info("lock2成功释放锁");
        }
        else {
            log.info("lock2获取锁失败");
        }
    }

    public static void main(String[] args) throws Exception {
        ZkLock zkLock = new ZkLock();
        InterProcessSemaphoreMutex lock = zkLock.getSemaphoreLock("demo");
        Demo2 demo2 = new Demo2();
        demo2.lock1(lock);
    }
}


控制台输出结果

22:07:00.984 [main] INFOcom.mg.lock.demo.zk.Demo2 - lock1成功获取锁
22:07:00.984 [main] INFOcom.mg.lock.demo.zk.Demo2 - lock2尝试获取锁
22:07:11.163 [main] INFOcom.mg.lock.demo.zk.Demo2 - lock2获取锁失败
22:07:11.220 [main] INFOcom.mg.lock.demo.zk.Demo2 - lock1成功释放锁

同一个线程,lock2函数获取锁还是会失败。

3、读写锁的demo


@Slf4j
public class Demo3 {


    public void buildReadTask(InterProcessMutex lock,String pre)
    {
        for(int i=0;i<5;i++)
        {
            Thread task = new Thread(()->{

                try {
                    log.info("[{}]开始获取读锁",Thread.currentThread().getName());
                    lock.acquire();
                    log.info("[{}]获取读锁成功",Thread.currentThread().getName());
                    Thread.sleep(1000*5);
                    lock.release();
                    log.info("[{}]释放读锁",Thread.currentThread().getName());
                } catch (Exception e) {
                    e.printStackTrace();
                }

            });
            task.setName(pre+"-mg-read-"+i);
            task.start();
        }
    }

    public void buildWriteTask(InterProcessMutex lock,String pre)
    {
        Thread task = new Thread(()->{

            try {
                log.info("[{}]开始获取写锁",Thread.currentThread().getName());
                lock.acquire();
                log.info("[{}]获取写锁成功",Thread.currentThread().getName());
                Thread.sleep(1000*5);
                lock.release();
                log.info("[{}]释放写锁",Thread.currentThread().getName());
            } catch (Exception e) {
                e.printStackTrace();
            }

        });
        task.setName(pre+"-mg-wirte");
        task.start();
    }

    public static void main(String[] args) throws Exception {
        ZkLock zkLock = new ZkLock();
        InterProcessReadWriteLock lock = zkLock.getReadWriteLock("demo");

        Demo3 demo3 = new Demo3();
        demo3.buildWriteTask(lock.writeLock(),"before");
        Thread.sleep(1000*2);
        demo3.buildReadTask(lock.readLock(),"before");
        Thread.sleep(1000*5);
        demo3.buildWriteTask(lock.writeLock(),"after");
//        Thread.sleep(1000*15);
//        demo3.buildReadTask(lock.readLock(),"after");
    }
}


控制台输出内容太长,就不写了,有兴趣的可以执行下试试

五、浅析锁实现

通过InterProcessMutex加锁和解锁的代码来简单了解下分布式锁的实现

1、创建锁 构造函数


InterProcessMutex(CuratorFramework client, String path, String lockName, int maxLeases, LockInternalsDriver driver) {
    this.threadData = Maps.newConcurrentMap();
    this.basePath = PathUtils.validatePath(path);
    this.internals = new LockInternals(client, driver, path, lockName, maxLeases);
}


构造函数中创建了一个InterProcessMutex对象,并保存了锁的path、客户端等信息。

2、加锁

acquire函数如下


public boolean acquire(long time, TimeUnit unit) throws Exception {
    return this.internalLock(time, unit);
}


进到internalLock函数中


private boolean internalLock(long time, TimeUnit unit) throws Exception {
    Thread currentThread = Thread.currentThread();
    InterProcessMutex.LockData lockData = (InterProcessMutex.LockData)this.threadData.get(currentThread);
    if (lockData != null) {
        lockData.lockCount.incrementAndGet();
        return true;
    } else {
        String lockPath = this.internals.attemptLock(time, unit, this.getLockNodeBytes());
        if (lockPath != null) {
            InterProcessMutex.LockData newLockData = new InterProcessMutex.LockData(currentThread, lockPath);
            this.threadData.put(currentThread, newLockData);
            return true;
        } else {
            return false;
        }
    }
}


internalLock中,判断获取锁的线程是否是当前线程,是lockCount(AtomicInteger类型的原子类)加1继续执行,否则尝试获取锁。

再看下attemptLock这个函数


String attemptLock(long time, TimeUnit unit, byte[] lockNodeBytes) throws Exception {
    long startMillis = System.currentTimeMillis();
    Long millisToWait = unit != null ? unit.toMillis(time) : null;
    byte[] localLockNodeBytes = this.revocable.get() != null ? new byte[0] : lockNodeBytes;
    int retryCount = 0;
    String ourPath = null;
    boolean hasTheLock = false;
    boolean isDone = false;

    while(!isDone) {
        isDone = true;

        try {
            ourPath = this.driver.createsTheLock(this.client, this.path, localLockNodeBytes);
            hasTheLock = this.internalLockLoop(startMillis, millisToWait, ourPath);
        } catch (NoNodeException var14) {
            if (!this.client.getZookeeperClient().getRetryPolicy().allowRetry(retryCount++, System.currentTimeMillis() - startMillis, RetryLoop.getDefaultRetrySleeper())) {
                throw var14;
            }

            isDone = false;
        }
    }

    return hasTheLock ? ourPath : null;
}


其中最主要的就是这两行代码


ourPath = this.driver.createsTheLock(this.client, this.path, localLockNodeBytes);//创建临时节点
hasTheLock = this.internalLockLoop(startMillis, millisToWait, ourPath);//判断是否为最小节点


已经基本和原生ZK API实现了一致,创建临时节点,监听等待当前节点为最小节点,获取到锁执行同步代码块。

3、解锁

Release方法如下


public void release() throws Exception {
    Thread currentThread = Thread.currentThread();
    InterProcessMutex.LockData lockData = (InterProcessMutex.LockData)this.threadData.get(currentThread);
    if (lockData == null) {
        throw new IllegalMonitorStateException("You do not own the lock: " + this.basePath);
    } else {
        int newLockCount = lockData.lockCount.decrementAndGet();
        if (newLockCount <= 0) {
            if (newLockCount < 0) {
                throw new IllegalMonitorStateException("Lock count has gone negative for lock: " + this.basePath);
            } else {
                try {
                    this.internals.releaseLock(lockData.lockPath);
                } finally {
                    this.threadData.remove(currentThread);
                }

            }
        }
    }
}


先判断线程是否占有锁,占有的情况下lockCount减1,如果lockCount小于等于0,执行releaseLock函数


final void releaseLock(String lockPath) throws Exception {
    this.client.removeWatchers();
    this.revocable.set((Object)null);
    this.deleteOurPath(lockPath);
}


releaseLock函数中,释放watch监听和临时节点。

zookeeper+Curator实现分布式锁入门篇,就结束了,如果有想深入了解的知识点。