ZooKeeper架构篇 - Curator分布式锁

本文详细探讨了ZooKeeper的Curator框架中分布式可重入锁、分布式联锁和分布式信号量的实现原理。通过源码分析,解释了锁的获取、释放、互斥性以及避免死锁的策略。着重介绍了InterProcessMutex、InterProcessMultiLock和InterProcessSemaphoreV2的使用和工作流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

对于分布式锁的实现,我们一般需要考虑如下:

  1. 保证分布式锁的互斥性。同一时刻,只有一个客户端成功获取到锁。
  2. 多个客户端同时请求按照什么样的顺序来获取锁。
  3. 没有获取到锁的客户端如何重新尝试获取锁。
  4. 如何保证只有成功获取到锁的客户端才能释放锁。
  5. 如何保证获取锁的客户端成功释放锁,避免死锁问题。

本文基于 Curator 5.4.0 版本,展开对分布式锁的分析。

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>5.4.0</version>
</dependency>

分布式可重入锁

即 InterProcessMutex。

可重入:一个持有锁的线程在没有释放锁的情况下,可以再次获取这个锁。

具体地,每个客户端在指定路径下创建一个临时顺序节点。选取序号最小的节点进行抢占锁。然后对这个节点路径添加一个监听器,抢占失败的节点进入等待状态(或者超时等待状态)一旦持有锁的节点释放锁,这些等待的节点会被唤醒,然后尝试抢占锁。

Curator 使用 InterProcessMutex 的 acquire、release 方法表达抢占锁、释放锁。使用 ConcurrentHashMap 存储线程与 LockData 之间的对应关系,如果 LockData 为空,表示当前没有线程持有锁;反之,表示当前线程持有锁。使用 LockData#lockCount 属性记录持有锁的次数。

使用

public class CuratorDemo {
   
   
    private static final Logger LOGGER = LoggerFactory.getLogger(CuratorDemo.class);

    private static final String CONNECT_STRING = "127.0.0.1:2181";

    private static RetryPolicy initRetryPolicy() {
   
   
        return new ExponentialBackoffRetry(500, 5, 5000);
    }

    private static CuratorFramework initCuratorFramework() {
   
   
        CuratorFramework curatorFramework = CuratorFrameworkFactory
                .builder()
                .connectString(CONNECT_STRING)
                .retryPolicy(initRetryPolicy())
                .build();
        curatorFramework.start();
        return curatorFramework;
    }

    public static void main(String[] args) {
   
   
        for (int i = 0; i < 10; i++) {
   
   
            new Thread(() -> {
   
   
                CuratorFramework curatorFramework = initCuratorFramework();
                InterProcessMutex interProcessMutex = new InterProcessMutex(curatorFramework, "/mutex");
                try {
   
   
                    interProcessMutex.acquire();
                    LOGGER.info(Thread.currentThread().getName() + " acquired the lock");
                    Thread.sleep(2000);
                } catch (Exception e) {
   
   
                    LOGGER.error(Thread.currentThread().getName() + " encountered error");
                } finally {
   
   
                    try {
   
   
                        interProcessMutex.release();
                    } catch (Exception e) {
   
   
                        LOGGER.error(Thread.currentThread().getName() + " failed to release the lock");
                    }
                }
            }).start();
        }
    }
}

源码分析

从上述的例子分析,实际的调用的结构如下:

InterProcessMutex mutex = new InterProcessMutex(curatorFramework, path);
try {
   
   
  mutex.acquire();
  // 业务处理
} finally {
   
   
  mutex.release();
}

接下来先看下 InterProcessMutex 的构造器方法。

public InterProcessMutex(CuratorFramework client, String path)
{
   
   
  	// 指定 LockInternalsDriver 为 StandardLockInternalsDriver 实例
    this(client, path, new StandardLockInternalsDriver());
}

public InterProcessMutex(CuratorFramework client, String path, LockInternalsDriver driver)
{
   
   
  	// 指定 lockName 为 "lock-",maxLeases 为 1
    this(client, path, LOCK_NAME, 1, driver);
}

InterProcessMutex(CuratorFramework client, String path, String lockName, int maxLeases, LockInternalsDriver driver)
{
   
   
  	// 校验节点路径
    basePath = PathUtils.validatePath(path);
  	// 构建 LockInternals 实例
    internals = new LockInternals(client, driver, path, lockName, maxLeases);
}

然后重点看下它的 acquire 方法。它有两种方法实现 - acquire() 阻塞直到成功获取到锁、acquire(long time, TimeUnit unit) 阻塞直到成功获取到锁或者超时。

@Override
public void acquire() throws Exception
{
   
   
    if ( !internalLock(-1, null) )
    {
   
   
        throw new IOException("Lost connection while trying to acquire lock: " + basePath);
    }
}

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

可以看出 acquire 的两种重载方法内部都会调用 internalLock 方法。

private boolean internalLock(long time, TimeUnit unit) throws Exception
{
   
   
    Thread currentThread = Thread.currentThread();
	// 从 threadData 缓存中获取指定线程对应的 LockData 实例
    LockData lockData = threadData.get(currentThread);
  	// 如果 LockData 实例不为空,即表示当前线程已持有锁
    if ( lockData != null )
    {
   
   
      	// 对持有锁的次数加一
        lockData.lockCount.incrementAndGet();
      	// 返回 true,表示获取锁成功
        return true;
    }
	// 抢占锁
    String lockPath = internals.attemptLock(time, unit, getLockNodeBytes());
  	// 如果抢占成功
    if ( lockPath != null )
    {
   
   
      	// 构造 LockData 实例
        LockData newLockData = new LockData(currentThread, lockPath);
      	// 将当前线程、LockData 实例添加到 threadData 缓存中
        threadData.put(currentThread, newLockData);
      	// 返回 true,表示获取锁成功
        return true;
    }
	// 返回 false,表示获取锁失败
    return false;
}

接着分析如何抢占锁的。

String attemptLock(long time, TimeUnit unit, byte[] lockNodeBytes) throws Exception
{
   
   
    final long      startMillis = System.currentTimeMillis();
    final Long      millisToWait = (unit != null) ? unit.toMillis(time) : null;
    final byte[]    localLockNodeBytes = (revocable.get() != null) ? new byte[0] : lockNodeBytes;
    int             retryCount = 0;<
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值