【zookeeper学习笔记】| 十一、Zookeeper实现分布式锁

一、概述

1、Apache Curator是一个Zookeeper的开源客户端,它提供了Zookeeper各种应用场景(Recipe,如共享锁服务、master选举、分布式计数器等)的抽象封装,接下来将利用Curator提供的类来实现分布式锁。

2、Curator提供的跟分布式锁相关的类有5个:

Shared Reentrant Lock 可重入锁

Shared Lock 共享不可重入锁

Shared Reentrant Read Write Lock 可重入读写锁

Shared Semaphore 信号量

Multi Shared Lock 多锁

3、错误处理:推荐使用ConnectionStateListener处理连接状态的改变。当连接LOST时你不再拥有锁。

二、代码实现

1、InterProcessLock


void acquire() throws Exception;
boolean acquire(long var1, TimeUnit var3) throws Exception;
void release() throws Exception;
boolean isAcquiredInThisProcess();

2、Shared Reentrant Lock,全局可重入锁,所有客户端都可以请求,同一个客户端在拥有锁的同时,可以多次获取,不会被阻塞。

1)由类 InterProcessMutex 来实现

// 构造方法
public InterProcessMutex(CuratorFramework client, String path);
public InterProcessMutex(CuratorFramework client, String path, LockInternalDriver driver)
// 通过acquire 获得锁,并提供超时机制
public void acquire() throws Exception
public boolean acquire(long time, TimeUnit unit) throws Exception
// 撤销锁
public void makeRevocable(RevocationListener<InterProcessMutex> listener)
public void makeRevocable(final RevocationListener<InterProcessMutex> listener, Executor executor)

  1. 代码实现

a:定义一个共享资源类,字眼只能被一个线程访问


package com.hao.demo.zookeeper.distributelock;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @author haojunhu
 * @date 2020-06-06
 * 模拟共享资源
 */
@Slf4j
public class FakeLimitedResource {
    private final AtomicBoolean inUse = new AtomicBoolean(false);

    // 模拟只能单线程操作的资源
    public void use() {
        if (!inUse.compareAndSet(false, true)) {
            // 在正确使用的情况下,此异常不可能抛出
            throw new IllegalArgumentException("Needs to be used by one client at a time");
        }
        try {
            Thread.sleep((long) (100 * Math.random()));
        } catch (Exception e) {
            log.info("e=>{}", e.getMessage());
        } finally {
            inUse.set(false);
        }
    }

}


b:创建 线程来模拟分布式系统中的节点:系统将通过 InterProcessMutex 来控制对资源的同步使用;

客户端通过 acquire 请求锁,通过 release 释放锁,获得几把锁就要释放几把锁;这个共享资源一次只能被一个线程使用,如果控制同步失败,抛c出异常。

请求锁
访问资源
再次请求锁
释放锁
释放锁

package com.hao.demo.zookeeper.distributelock;

import com.hao.demo.zookeeper.publishsubscribe.ZKUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.utils.CloseableUtils;

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * @author haojunhu
 * @date 2020-06-07
 * {@link org.apache.curator.framework.recipes.locks.InterProcessLock} : 父接口
 */
@Slf4j
public class SharedReentrantLockTest {
    private static final String lockPath = "/testZK/sharedReentrantLock";

    private static final Integer clientNums = 5;

    final static FakeLimitedResource resource = new FakeLimitedResource(); // 共享的资源

    private static CountDownLatch countDownLatch = new CountDownLatch(clientNums);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < clientNums; i++) {
            String clientName = "client#" + i;
            new Thread(() -> {
                CuratorFramework client = ZKUtils.getClient();
                client.start();
                Random random = new Random();
                try {
                    final InterProcessMutex lock = new InterProcessMutex(client, lockPath);
                    // 每个客户端请求10次共享资源
                    for (int j = 0; j < 10; j++) {
                        if (!lock.acquire(10, TimeUnit.SECONDS)) {
                            throw new IllegalStateException(j + ". " + clientName + "不能得到互斥锁....");
                        }
                        try {
                            log.info("{}. {}==>已经取到互斥锁.....", j, clientName);
                            resource.use(); // 使用资源
                            if (!lock.acquire(10, TimeUnit.SECONDS)) {
                                throw new IllegalStateException(j + ". " + clientName + "不能再次获得到互斥锁");
                            }
                            log.info("{}. {} ==>已经再次获取到互斥锁", j, clientName);
                            lock.release(); // 申请几次锁就要释放几次锁
                        } finally {
                            log.info("{}. {}==>释放互斥锁", j, clientName);
                            lock.release(); // 总是在finally中释放
                        }
                        Thread.sleep(random.nextInt(100));
                    }

                } catch (Throwable e) {

                } finally {
                    CloseableUtils.closeQuietly(client);
                    log.info("client is close.......");
                    countDownLatch.countDown();
                }
            }).start();

        }
        countDownLatch.await();
        log.info("end .....");
    }
}


3、Shared Lock 与 Shared Reentrant Lock 相似,但是不可重入。

a:InterProcessSemaphoreMutex 来实现

// 构造方法
public InterProcessSemaphoreMutex(CuratorFramework client, String path)
// 获取锁
public void acquire()
public boolean acquire(long time, TimeUnit unit)
// 释放锁
public void release()

b InterProcessSemaphoreMutex类实现 SharedLock

package com.hao.demo.zookeeper.distributelock;

import com.hao.demo.zookeeper.publishsubscribe.ZKUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.framework.recipes.locks.InterProcessSemaphoreMutex;
import org.apache.curator.utils.CloseableUtils;

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * @author haojunhu
 * @date 2020-06-09
 */
@Slf4j
public class SharedLock {
    private static final String lockPath = "/testZK/sharedReentrantLock";

    private static final Integer clientNums = 5;

    final static FakeLimitedResource resource = new FakeLimitedResource(); // 共享的资源

    private static CountDownLatch countDownLatch = new CountDownLatch(clientNums);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < clientNums; i++) {
            String clientName = "client#" + i;
            new Thread(() -> {
                CuratorFramework client = ZKUtils.getClient();
                client.start();
                Random random = new Random();
                try {
                    final InterProcessSemaphoreMutex lock = new InterProcessSemaphoreMutex(client, lockPath);
                    // 每个客户端请求10次共享资源
                    for (int j = 0; j < 10; j++) {
                        if (!lock.acquire(10, TimeUnit.SECONDS)) {
                            throw new IllegalStateException(j + ". " + clientName + "不能得到互斥锁....");
                        }
                        try {
                            log.info("{}. {}==>已经取到互斥锁.....", j, clientName);
                            resource.use(); // 使用资源
                            if (!lock.acquire(10, TimeUnit.SECONDS)) {
                                throw new IllegalStateException(j + ". " + clientName + "不能再次获得到互斥锁");
                            }
                            log.info("{}. {} ==>已经再次获取到互斥锁", j, clientName);
                            lock.release(); // 申请几次锁就要释放几次锁
                        } finally {
                            log.info("{}. {}==>释放互斥锁", j, clientName);
                            lock.release(); // 总是在finally中释放
                        }
                        Thread.sleep(random.nextInt(100));
                    }

                } catch (Throwable e) {

                } finally {
                    CloseableUtils.closeQuietly(client);
                    log.info("client is close.......");
                    countDownLatch.countDown();
                }
            }).start();

        }
        countDownLatch.await();
        log.info("end .....");
    }
}

4、Shared Reentrant Read Write Lock,可重入读写锁。
1)描述

a:一个读写锁管理一对相关的锁,一个负责读操作,另外一个负责写操作;

b:读操作在写锁没被使用时可同时由多个进程使用,而写锁在使用时不允许读(阻塞);

c:此锁是可重入的;

d:一个拥有写锁的线程可重入读锁,但是读锁却不能进入写锁,这也意味着写锁可以降级成读锁,

x
请求写锁
读锁
释放写锁
请求读锁
写锁

2)由两个类实现:InterProcessReadWriteLock、InterProcessMutex,创建一个 InterProcessReadWriteLock 实例,然后再根据需求得到读锁或者写锁,读写锁的类型是 InterProcessMutex。

package com.hao.demo.zookeeper.distributelock;

import com.hao.demo.zookeeper.publishsubscribe.ZKUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.framework.recipes.locks.InterProcessReadWriteLock;
import org.apache.curator.utils.CloseableUtils;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * @author haojunhu
 * @date 2020-06-07
 */
@Slf4j
public class SharedReentrantReadWriteLock {
    private static final String lockPath = "/testZK/sharedReentrantLock";

    private static final Integer clientNums = 5;

    final static FakeLimitedResource resource = new FakeLimitedResource(); // 共享的资源

    private static CountDownLatch countDownLatch = new CountDownLatch(clientNums);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < clientNums; i++) {
            final String clientName = "client#" + i;
            new Thread(() -> {
                CuratorFramework client = ZKUtils.getClient();
                client.start();
                final InterProcessReadWriteLock lock = new InterProcessReadWriteLock(client, lockPath);
                final InterProcessMutex readLock = lock.readLock();
                final InterProcessMutex writeLock = lock.writeLock();
                try {
                    // 注意只能先得到写锁再得到读锁,不能反过来。
                    if (!writeLock.acquire(10, TimeUnit.SECONDS)) {
                        throw new IllegalStateException(clientName + "不能得到写锁");
                    }
                    log.info("{} ==> 已经得到写锁", clientName);

                    if (!readLock.acquire(10, TimeUnit.SECONDS)) {
                        throw new IllegalStateException(clientName + "不能得到写锁");
                    }
                    log.info("{}==>已经得到读锁", clientName);

                    try{
                        resource.use(); // 使用资源
                    } finally {
                        log.info("{}:释放读写锁", clientName);
                    }
                } catch (Exception e) {
                    log.info("e=>{}", e.getMessage());
                } finally {
                    CloseableUtils.closeQuietly(client);
                    countDownLatch.countDown();
                }
            }).start();
        }
        countDownLatch.await();
        log.info("end........");
    }
}

5、Shared Semaphore

  1. 描述

1)一个计数的信号量类似JDK的 Semaphore,JDK中 Semaphore 维护的一组许可(permits),而Cubator中称之为租约(Lease)。

  1. 有两种方式可以决定 semaphore 的最大租约数。
a:第一种方式是由用户给定的 path 决定。

b:第二种方式使用 SharedCountReader类。如果不使用SharedCountReader,没有内部代码检查进程是否假定有10个租约而进程B假定有20个租约。
所以所有的实例必须使用相同的 numberOfLeases 值.

2)实现类

InterProcessSemaphoreV2 - 信号量实现类
Lease - 租约(单个信号)
SharedCountReader - 计数器,用于计算最大租约数量

3) 注意

a:调用 acquire 会返回一个租约对象,客户端必须在 finally 中 close 这些租约对象,否则这些租约会丢失掉。

b:如果客户端session由于某种原因比如crash丢掉,那么这些客户端持有的租约会自动close。

c:其它客户端可以继续使用这些租约。

// 租约返回
public void returnLease(Lease lease)
public void returnAll(Collection<Lease> leases) 

d:Semaphore 当前的租约不够,则请求线程会被阻塞。

//超时重载方法
public Lease acquire() throws Exception
public Collection<Lease> acquire(int qty) throws Exception
public Lease acquire(long time, TimeUnit unit) throws Exception
public Collection<Lease> acquire(int qty, long time, TimeUnit unit) throws Exception
  1. 代码
package com.hao.demo.zookeeper.distributelock;

import com.hao.demo.zookeeper.publishsubscribe.ZKUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessSemaphoreV2;
import org.apache.curator.framework.recipes.locks.Lease;

import java.util.Collection;
import java.util.concurrent.TimeUnit;

/**
 * @author haojunhu
 * @date 2020-06-07
 */
@Slf4j
public class SharedSemaphoreTest {
    private static final int MAX_LEASE = 10;

    private static final String PATH = "/testZK/semaphore";

    private static final FakeLimitedResource resource = new FakeLimitedResource();

    public static void main(String[] args) throws Exception {
        CuratorFramework client = ZKUtils.getClient();
        client.start();

        InterProcessSemaphoreV2 semaphore = new InterProcessSemaphoreV2(client, PATH, MAX_LEASE);
        Collection<Lease> leases = semaphore.acquire(5);
        log.info("获取租约的数量:leases.size=>{}", leases.size());

        Lease lease = semaphore.acquire();
        log.info("获取单个租约lease==>{}", lease);
        resource.use(); // 使用资源
        // 再次申请获取5个leases,此时leases数量只剩4个,不够,将超时
        Collection<Lease> leases2 = semaphore.acquire(5, 10, TimeUnit.SECONDS);
        log.info("获取租约,如果超时将为null: leases2=>{}", leases2);
        log.info("==============>>>>释放集合中的所以租约");
        semaphore.returnLease(lease);
        // 再次申请获取5个,这次刚好够
        leases2 = semaphore.acquire(5, 10, TimeUnit.SECONDS);
        log.info("获取租约,如果超时将为null: leases2=>{}", leases2);
        log.info("==============>>>>释放集合中的所以租约");
        semaphore.returnAll(leases);
        semaphore.returnAll(leases2);
        client.close();
        log.info("======>>>>end.....");
    }
}


6、Multi Shared Lock 是一个锁的容器:多锁
1)概念

当调用 acquire,所有的锁都会被 acquire,如果请求失败,所有的锁都会被 release。同样调用 release 时所有的锁都被 release(失败被忽略)。

2)主要实现类

InterProcessMultiLock - 对所对象实现类

// 构造函数需要包含锁的集合 或一组Paths
public InterProcessMultiLock(CuratorFramework client, List<String> paths)
public InterProcessMultiLock(List<InterProcessLock> locks)
  1. 代码

package com.hao.demo.zookeeper.distributelock;

import com.hao.demo.zookeeper.publishsubscribe.ZKUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessLock;
import org.apache.curator.framework.recipes.locks.InterProcessMultiLock;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.framework.recipes.locks.InterProcessSemaphoreMutex;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

/**
 * @author haojunhu
 * @date 2020-06-07
 * 多锁
 */
@Slf4j
public class MultiSharedLockTest {
    private static final String lockPath1 = "/testZK/MSLock1";

    private static final String lockPath2 = "/testZK/MSLock2";

    private static final FakeLimitedResource resource = new FakeLimitedResource();

    public static void main(String[] args) throws Exception {
        CuratorFramework client = ZKUtils.getClient();
        client.start();

        InterProcessLock lock1 = new InterProcessMutex(client, lockPath1); // 可重入锁
        InterProcessLock lock2 = new InterProcessSemaphoreMutex(client, lockPath2); // 不可重入锁

        // 组锁,多锁
        InterProcessMultiLock lock = new InterProcessMultiLock(Arrays.asList(lock1, lock2));
        if (!lock.acquire(10, TimeUnit.SECONDS)) {
            throw new IllegalStateException("不能获取多锁");
        }
        log.info("已经获得多锁 ==> 是否有第一个锁=>{}, 是否有第二个锁=>{}",lock1.isAcquiredInThisProcess(),
                lock2.isAcquiredInThisProcess());
        try {
            resource.use(); // 操作资源
        } finally {
            log.info("释放多个锁 ======>>>>>>");
            lock.release(); // 释放多锁
        }
        log.info("已经获得多锁 ==> 是否有第一个锁=>{}, 是否有第二个锁=>{}",lock1.isAcquiredInThisProcess(),
                lock2.isAcquiredInThisProcess());
        client.close();
        log.info("=========>>>>>>end....");
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值