Redisson可重入锁&看门狗

Redission可重入锁原理

当一个线程想要获取锁时,先判断一下锁是否存在,如果不存在,则直接获取锁,并把获取锁的次数(value)设置为1,如果存在,则判断一下加锁线程是不是自己,如果是的话,就把获取锁的次数+1,同时重置锁的过期时间,如果不是的话,就直接返回

1. 获取锁的lua脚本:

当释放锁的时候,先把获取锁的次数-1,然后判断获取锁的次数的值是否大于0,如果大于0的话,重置锁的过期时间,如果小于0的话,就表示没有其他线程在使用此锁,所以可以进行删除,即释放锁

2. 释放锁的lua脚本:

Redission看门狗原理

java
复制代码
@Resource private RedissonClient redissonClient; @Test public void test() throws InterruptedException { RLock lock = redissonClient.getLock("test"); /** * tryLock是一个重载方法 * 1. 获取锁失败后,等待的时间,如果其他线程在这个时间内释放锁,则可以进行重试获取锁的操作 * 2. 锁的过期时间 * 3. 时间单位 */ lock.tryLock(10L,100L,TimeUnit.MICROSECONDS); //不传锁的过期时间的话,会使用看门狗的时间,进行自动续期 lock.tryLock(10L, TimeUnit.MICROSECONDS); lock.unlock(); }

如果想要使用Redission的看门狗机制的话,那么在尝试获取锁的时候就不能传入锁的释放时间,因为一旦传入锁的释放时间这个参数的话,那么就不会走看门狗机制

  1. 当要获取锁的时候,会先拿到当前线程id、当前时间、以及获取锁失败后的等待时间
  2. 然后去尝试获取锁,此时会执行lua脚本,判断锁是否存在或当前加锁线程是不是自己,是的话,就记录获取锁的次数,并重置锁的过期时间,如果不是的话,就表示获取锁失败(有其他线程在使用锁),然后返回锁的剩余有效期
  3. 然后会判断获取锁的动作花费了多少时间,是否在等待时间内(获取锁失败后,等待的时间),如果不在的话,就直接返回false,获取锁失败
  4. 如果还有剩余等待时间,那么就会去订阅释放锁的消息,当其他线程释放锁之后,进行重试,先判断锁的剩余有效期是否为null,是的话表示获取到锁,直接返回true,不是的话就判断是否还有剩余等待时间,有的话就进行重试(获取锁)
  5. 当线程获取到锁后,就会启用看门狗续期机制(这个锁没有设置有效时间,需要完成任务后自动释放锁),递归调用续期方法,每次续期的时间为 看门狗时间 / 3 。(默认看门狗时间是30秒)

获取锁的源码:

java
复制代码
@Override 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(waitTime, leaseTime, unit, threadId); // lock acquired if (ttl == null) { return true; } time -= System.currentTimeMillis() - current; if (time <= 0) { acquireFailed(waitTime, unit, threadId); return false; } current = System.currentTimeMillis(); RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId); if (!subscribeFuture.await(time, TimeUnit.MILLISECONDS)) { if (!subscribeFuture.cancel(false)) { subscribeFuture.onComplete((res, e) -> { if (e == null) { unsubscribe(subscribeFuture, threadId); } }); } acquireFailed(waitTime, unit, threadId); return false; } try { time -= System.currentTimeMillis() - current; if (time <= 0) { acquireFailed(waitTime, unit, threadId); return false; } while (true) { long currentTime = System.currentTimeMillis(); ttl = tryAcquire(waitTime, leaseTime, unit, threadId); // lock acquired if (ttl == null) { return true; } time -= System.currentTimeMillis() - currentTime; if (time <= 0) { acquireFailed(waitTime, unit, threadId); return false; } // waiting for message currentTime = System.currentTimeMillis(); if (ttl >= 0 && ttl < time) { subscribeFuture.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS); } else { subscribeFuture.getNow().getLatch().tryAcquire(time, TimeUnit.MILLISECONDS); } time -= System.currentTimeMillis() - currentTime; if (time <= 0) { acquireFailed(waitTime, unit, threadId); return false; } } } finally { unsubscribe(subscribeFuture, threadId); } // return get(tryLockAsync(waitTime, leaseTime, unit)); }

获取锁的lua脚本:

看门狗续期:

续期代码:
java
复制代码
private void renewExpiration() { ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName()); if (ee == null) { return; } Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() { @Override public 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<Boolean> future = renewExpirationAsync(threadId); future.onComplete((res, e) -> { if (e != null) { log.error("Can't update lock " + getName() + " expiration", e); return; } if (res) { // reschedule itself renewExpiration(); } }); } }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); ee.setTimeout(task); }

watchdog一直续期,那客户端挂了怎么办?

如果应用集群中的一台服务器拿到了分布式锁,但是在执行的过程中,他挂了,还没来得及把锁释放,那么会有问题吗?

锁的续期是Redission实现的,而Redission的后台任务是基于JVM运行的,也就是说,如果这台机器挂了,那么Redission的后台任务也就没办法继续执行了

那么它也就不会再继续续期了,到了期限之后,锁就会自动解除了,这样就可以避免因为一个实例宕机导致分布式锁的不可用

0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
下载 APP