redis分布式锁RedissonLock的实现细节解析

redis分布式锁RedissonLock 简单使用

String key = "key-lock"; RLock lock = redisson.getLock(key); lock.lock(); try {// TODO} catch (Exception e){log.error(e.getMessage(), e); } finally {lock.unlock(); }

String key = "key-tryLock"; long maxWaitTime = 3_000; RLock lock = redisson.getLock(key); if (lock.tryLock(maxWaitTime, TimeUnit.MILLISECONDS)){try {// TODO} catch (Exception e){log.error(e.getMessage(), e); } finally {lock.unlock(); }} else {log.debug("redis锁竞争失败"); }

流程图
多个线程节点锁竞争的正常流程如下图:
redis分布式锁RedissonLock的实现细节解析
文章图片

多个线程节点锁竞争,并出现节点下线的异常流程如下图:
redis分布式锁RedissonLock的实现细节解析
文章图片

源码解析
RedissonLock是可重入锁,使用redis的hash结构作为锁的标识存储,锁的名称作为hash的key,UUID + 线程ID作为hash的field,锁被重入的次数作为hash的value。如图所示:
redis分布式锁RedissonLock的实现细节解析
文章图片

private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {long threadId = Thread.currentThread().getId(); // 尝试获取锁,锁获取成功则ttl为null; 获取失败则返回锁的剩余过期时间Long ttl = tryAcquire(leaseTime, unit, threadId); if (ttl == null) {return; }// 锁被其他线程占用而索取失败,使用线程通知而非自旋的方式等待锁// 使用redis的发布订阅pub/sub功能来等待锁的释放通知RFuture future = subscribe(threadId); commandExecutor.syncSubscription(future); try {while (true) {ttl = tryAcquire(leaseTime, unit, threadId); // 尝试获取锁,锁获取成功则ttl为null; 获取失败则返回锁的剩余过期时间if (ttl == null) {break; }if (ttl >= 0) {// 使用LockSupport.parkNanos方法线程休眠try {getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS); } catch (InterruptedException e) {if (interruptibly) {throw e; }getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS); }} else {if (interruptibly) {getEntry(threadId).getLatch().acquire(); } else {getEntry(threadId).getLatch().acquireUninterruptibly(); }}}} finally {// 退出锁竞争(锁获取成功或者放弃获取锁),则取消锁的释放订阅unsubscribe(future, threadId); }}

public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {long time = unit.toMillis(waitTime); long current = System.currentTimeMillis(); long threadId = Thread.currentThread().getId(); Long ttl = tryAcquire(leaseTime, unit, threadId); if (ttl == null) {return true; }time -= System.currentTimeMillis() - current; if (time <= 0) {acquireFailed(threadId); return false; }current = System.currentTimeMillis(); RFuture subscribeFuture = subscribe(threadId); if (!await(subscribeFuture, time, TimeUnit.MILLISECONDS)) {if (!subscribeFuture.cancel(false)) {subscribeFuture.onComplete((res, e) -> {if (e == null) {unsubscribe(subscribeFuture, threadId); }}); }acquireFailed(threadId); return false; }try {time -= System.currentTimeMillis() - current; if (time <= 0) {acquireFailed(threadId); return false; }while (true) {long currentTime = System.currentTimeMillis(); ttl = tryAcquire(leaseTime, unit, threadId); // lock acquiredif (ttl == null) {return true; }time -= System.currentTimeMillis() - currentTime; if (time <= 0) {acquireFailed(threadId); return false; }currentTime = System.currentTimeMillis(); if (ttl >= 0 && ttl < time) {getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS); } else {getEntry(threadId).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS); }time -= System.currentTimeMillis() - currentTime; if (time <= 0) {acquireFailed(threadId); return false; }}} finally {unsubscribe(subscribeFuture, threadId); }}

RedissonLock实现的是可重入锁,通过redis的hash结构实现,而非加单的set nx ex。为了实现原子性的复杂的加锁逻辑,而通过lua脚本实现。获取锁会有如下三种状态:
1、锁未被任何线程占用,则锁获取成功,返回null
2、锁被当前线程占用,则锁获取成功并进行锁的重入,对锁的重入计数+1,返回null
3、锁被其他线程占用,则锁获取失败,返回该锁的自动过期时间ttl
RFuture tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand command) {internalLockLeaseTime = unit.toMillis(leaseTime); return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,"if (redis.call('exists', KEYS[1]) == 0) then " +"redis.call('hset', KEYS[1], ARGV[2], 1); " +"redis.call('pexpire', KEYS[1], ARGV[1]); " +"return nil; " +"end; " +"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +"redis.call('pexpire', KEYS[1], ARGV[1]); " +"return nil; " +"end; " +"return redis.call('pttl', KEYS[1]); ",Collections.singletonList(getName()), internalLockLeaseTime, getLockName(threadId)); }
当锁因为被其他线程占用而 使用redis的发布订阅pub/sub功能,通过监听锁的释放通知(在其他线程通过RedissonLock释放锁时,会通过发布订阅pub/sub功能发起通知),等待锁被其他线程释放。通过如此的线程唤醒而非自旋的操作,提高了锁的效率。
public RFuture subscribe(String entryName, String channelName) {AtomicReference listenerHolder = new AtomicReference(); AsyncSemaphore semaphore = service.getSemaphore(new ChannelName(channelName)); RPromise newPromise = new RedissonPromise() {@Overridepublic boolean cancel(boolean mayInterruptIfRunning) {return semaphore.remove(listenerHolder.get()); }}; Runnable listener = new Runnable() {@Overridepublic void run() {E entry = entries.get(entryName); if (entry != null) {entry.aquire(); semaphore.release(); entry.getPromise().onComplete(new TransferListener(newPromise)); return; }E value = https://www.it610.com/article/createEntry(newPromise); value.aquire(); E oldValue = entries.putIfAbsent(entryName, value); if (oldValue != null) {oldValue.aquire(); semaphore.release(); oldValue.getPromise().onComplete(new TransferListener(newPromise)); return; }RedisPubSubListener listener = createListener(channelName, value); service.subscribe(LongCodec.INSTANCE, channelName, semaphore, listener); }}; semaphore.acquire(listener); listenerHolder.set(listener); return newPromise; }
由于是可重入锁则需要在释放锁的时候做订阅通知,因此释放锁的操作同样是lua脚本实现。锁的释放会有如下三个状态:
1、等待释放的锁不存在或者不是当前线程持有,返回null
2、等待释放的锁被当前线程持有,且该锁当前被重入多次,则锁的重入计数-1,返回0
3、等待释放的锁被当前线程持有,且该锁当前未被重入,则锁的删除并发布该锁释放的订阅通知,返回1
protected RFuture unlockInnerAsync(long threadId) {return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +"return nil; " +"end; " +"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +"if (counter > 0) then " +"redis.call('pexpire', KEYS[1], ARGV[2]); " +"return 0; " +"else " +"redis.call('del', KEYS[1]); " +"redis.call('publish', KEYS[2], ARGV[1]); " +"return 1; "+"end; " +"return nil; ",Arrays.asList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId)); }
Watchdog
RedissonLock为了避免应用获取锁后宕机,因为没人来释放锁而导致死锁情况的出现,默认每次锁的占用只有30秒的时间(org.redisson.config.Config#lockWatchdogTimeout = 30 * 1000)。
于是便有了Watchdog设计,由独立的线程定时给未释放的锁续期,默认锁有效期的三分之一的时长即每10秒给锁自动续期。
private void renewExpiration() {ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName()); if (ee == null) {return; }// 默认10秒钟后执行锁续期任务Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {@Overridepublic void run(Timeout timeout) throws Exception {ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName()); if (ent == null) {return; }Long threadId = ent.getFirstThreadId(); if (threadId == null) {return; }RFuture future = renewExpirationAsync(threadId); future.onComplete((res, e) -> {if (e != null) {log.error("Can't update lock " + getName() + " expiration", e); return; }// 如果锁续期成功,则10秒钟后再次续期if (res) {renewExpiration(); }}); }}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); ee.setTimeout(task); }protected RFuture renewExpirationAsync(long threadId) {return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +"redis.call('pexpire', KEYS[1], ARGV[1]); " +"return 1; " +"end; " +"return 0; ",Collections.singletonList(getName()), internalLockLeaseTime, getLockName(threadId)); }
Redisson 几种锁 1. 可重入锁(Reentrant Lock)
Redisson的分布式可重入锁RLock Java对象实现了java.util.concurrent.locks.Lock接口,同时还支持自动过期解锁。
public void testReentrantLock(RedissonClient redisson){ RLock lock = redisson.getLock("anyLock"); try{// 1. 最常见的使用方法//lock.lock(); // 2. 支持过期解锁功能,10秒钟以后自动解锁, 无需调用unlock方法手动解锁//lock.lock(10, TimeUnit.SECONDS); // 3. 尝试加锁,最多等待3秒,上锁以后10秒自动解锁boolean res = lock.tryLock(3, 10, TimeUnit.SECONDS); if(res){//成功// do your business }} catch (InterruptedException e) {e.printStackTrace(); } finally {lock.unlock(); } }

Redisson同时还为分布式锁提供了异步执行的相关方法:
public void testAsyncReentrantLock(RedissonClient redisson){RLock lock = redisson.getLock("anyLock"); try{lock.lockAsync(); lock.lockAsync(10, TimeUnit.SECONDS); Future res = lock.tryLockAsync(3, 10, TimeUnit.SECONDS); if(res.get()){// do your business }} catch (InterruptedException e) {e.printStackTrace(); } catch (ExecutionException e) {e.printStackTrace(); } finally {lock.unlock(); } }

2. 公平锁(Fair Lock)
Redisson分布式可重入公平锁也是实现了java.util.concurrent.locks.Lock接口的一种RLock对象。在提供了自动过期解锁功能的同时,保证了当多个Redisson客户端线程同时请求加锁时,优先分配给先发出请求的线程。
public void testFairLock(RedissonClient redisson){ RLock fairLock = redisson.getFairLock("anyLock"); try{// 最常见的使用方法fairLock.lock(); // 支持过期解锁功能, 10秒钟以后自动解锁,无需调用unlock方法手动解锁fairLock.lock(10, TimeUnit.SECONDS); // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁boolean res = fairLock.tryLock(100, 10, TimeUnit.SECONDS); } catch (InterruptedException e) {e.printStackTrace(); } finally {fairLock.unlock(); } }

Redisson同时还为分布式可重入公平锁提供了异步执行的相关方法:
RLock fairLock = redisson.getFairLock("anyLock"); fairLock.lockAsync(); fairLock.lockAsync(10, TimeUnit.SECONDS); Future res = fairLock.tryLockAsync(100, 10, TimeUnit.SECONDS);

3. 联锁(MultiLock)
Redisson的RedissonMultiLock对象可以将多个RLock对象关联为一个联锁,每个RLock对象实例可以来自于不同的Redisson实例。
public void testMultiLock(RedissonClient redisson1,RedissonClient redisson2, RedissonClient redisson3){ RLock lock1 = redisson1.getLock("lock1"); RLock lock2 = redisson2.getLock("lock2"); RLock lock3 = redisson3.getLock("lock3"); RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3); try {// 同时加锁:lock1 lock2 lock3, 所有的锁都上锁成功才算成功。lock.lock(); // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS); } catch (InterruptedException e) {e.printStackTrace(); } finally {lock.unlock(); } }

4. 红锁(RedLock)
Redisson的RedissonRedLock对象实现了Redlock介绍的加锁算法。该对象也可以用来将多个RLock
对象关联为一个红锁,每个RLock对象实例可以来自于不同的Redisson实例。
public void testRedLock(RedissonClient redisson1,RedissonClient redisson2, RedissonClient redisson3){ RLock lock1 = redisson1.getLock("lock1"); RLock lock2 = redisson2.getLock("lock2"); RLock lock3 = redisson3.getLock("lock3"); RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3); try {// 同时加锁:lock1 lock2 lock3, 红锁在大部分节点上加锁成功就算成功。lock.lock(); // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS); } catch (InterruptedException e) {e.printStackTrace(); } finally {lock.unlock(); } }

5. 读写锁(ReadWriteLock)
Redisson的分布式可重入读写锁RReadWriteLock Java对象实现了java.util.concurrent.locks.ReadWriteLock接口。同时还支持自动过期解锁。该对象允许同时有多个读取锁,但是最多只能有一个写入锁。
RReadWriteLock rwlock = redisson.getLock("anyRWLock"); // 最常见的使用方法rwlock.readLock().lock(); // 或rwlock.writeLock().lock(); // 支持过期解锁功能// 10秒钟以后自动解锁// 无需调用unlock方法手动解锁rwlock.readLock().lock(10, TimeUnit.SECONDS); // 或rwlock.writeLock().lock(10, TimeUnit.SECONDS); // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁boolean res = rwlock.readLock().tryLock(100, 10, TimeUnit.SECONDS); // 或boolean res = rwlock.writeLock().tryLock(100, 10, TimeUnit.SECONDS); ...lock.unlock();

6. 信号量(Semaphore)
Redisson的分布式信号量(Semaphore)Java对象RSemaphore采用了与java.util.concurrent.Semaphore相似的接口和用法。
RSemaphore semaphore = redisson.getSemaphore("semaphore"); semaphore.acquire(); //或semaphore.acquireAsync(); semaphore.acquire(23); semaphore.tryAcquire(); //或semaphore.tryAcquireAsync(); semaphore.tryAcquire(23, TimeUnit.SECONDS); //或semaphore.tryAcquireAsync(23, TimeUnit.SECONDS); semaphore.release(10); semaphore.release(); //或semaphore.releaseAsync();

7. 可过期性信号量(PermitExpirableSemaphore)
Redisson的可过期性信号量(PermitExpirableSemaphore)实在RSemaphore对象的基础上,为每个信号增加了一个过期时间。每个信号可以通过独立的ID来辨识,释放时只能通过提交这个ID才能释放。
RPermitExpirableSemaphore semaphore = redisson.getPermitExpirableSemaphore("mySemaphore"); String permitId = semaphore.acquire(); // 获取一个信号,有效期只有2秒钟。String permitId = semaphore.acquire(2, TimeUnit.SECONDS); // ...semaphore.release(permitId);

8. 闭锁(CountDownLatch)
Redisson的分布式闭锁(CountDownLatch)Java对象RCountDownLatch采用了与java.util.concurrent.CountDownLatch相似的接口和用法。
【redis分布式锁RedissonLock的实现细节解析】以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

    推荐阅读