带大家简单了解下Redisson的看门狗机制,这个面试中也比较常见。
目录
- WatchDog(看门狗)机制
- 开启WatchDog(看门狗)
- 浅看下源码
WatchDog(看门狗)机制
Redisson看门狗机制是用于解决在业务运行时间大于锁失效时间的情况,即自动续期,当某用户执行抢占锁执行需要40秒,而锁有效期为30秒,到期后锁就有可能被其他用户抢占,这个时候看门狗机制就可以帮其自动续期至执行结束。
开启WatchDog(看门狗)
引入maven
<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.17.6</version>
</dependency>
先来看下redisson的简单使用(开门狗机制未开启时)
@RequestMapping("/watch_dog")
public String watchDog(){// 简单配置RedissonClientConfig config = new Config();config.useSingleServer().setAddress("redis://127.0.0.1:6379");RedissonClient redisson = Redisson.create(config);// 获取锁RLock lock = redisson.getLock("anyLock");try {// 尝试获取锁,最多等待3秒,锁定之后3秒自动解锁(锁释放程序照跑)boolean isLocked = lock.tryLock(3, 3, TimeUnit.SECONDS);if (isLocked) {System.out.println(Thread.currentThread().getName()+":还没睡觉");Thread.sleep(3000);System.out.println(Thread.currentThread().getName()+":睡眠了3秒钟");// 业务逻辑Thread.sleep(5000);System.out.println(Thread.currentThread().getName()+":睡眠了5秒钟");}} catch (InterruptedException e) {e.printStackTrace();} finally {// 释放锁(查询当前线程是否保持锁定)if (lock.isHeldByCurrentThread()) {lock.unlock();}}// 关闭RedissonClientredisson.shutdown();return Thread.currentThread().getName();
}
通过以下结果可以发现,当http-nio-19428-exec-1线程还在执行时,3秒后锁过期导致http-nio-19428-exec-4就可以抢占了锁
开启看门狗机制,以下是各类情况:
//
// //拿锁失败会不停重试
// //具有 Watch Dog 自动延期机制,默认续30s 每隔30/3=10 秒续到30s
// lock.lock();
//
// //businessLock.tryTime() 秒之后停止重试加锁,返回false
// //具有 Watch Dog 自动延期机制,默认续30s 每隔30/3=10 秒续到30s
// boolean locked1 = lock.tryLock(businessLock.tryTime(), businessLock.timeUnit());
//
// //businessLock.tryTime() 秒之后停止重试加锁,返回false
// //不具有 Watch Dog 自动延期机制
// boolean locked2 = lock.tryLock(businessLock.tryTime(), businessLock.expire(), businessLock.timeUnit());
//
// //businessLock.tryTime() 秒之后停止重试加锁,返回false
// //只有 leaseTime(默认-1) 等于 -1 时(示具体版本情况而定),才具有 Watch Dog 自动延期机制,默认续30s 每隔30/3=10 秒续到30s
// boolean locked3 = lock.tryLock(businessLock.tryTime(), -1, businessLock.timeUnit());
lock()方法是阻塞获取锁的方式,如果当前锁被其他线程持有,则当前线程会一直阻塞等待获取锁,直到获取到锁或者发生超时或中断等情况才会结束等待。该方法获取到锁之后可以保证线程对共享资源的访问是互斥的,适用于需要确保共享资源只能被一个线程访问的场景。Redisson 的 lock() 方法支持可重入锁和公平锁等特性,可以更好地满足多线程并发访问的需求。
tryLock()方法是一种非阻塞获取锁的方式,在尝试获取锁时不会阻塞当前线程,而是立即返回获取锁的结果,如果获取成功则返回 true,否则返回false。Redisson 的 tryLock() 方法支持加锁时间限制、等待时间限制以及可重入等特性,可以更好地控制获取锁的过程和等待时间,避免程序出现长时间无法响应等问题。
按个人理解:lock()会一直自旋等待锁,而tryLock()尝试获取锁后快速返回结果
以最后一种情况为例,修改代码如下:
//尝试获取锁,最多等待6秒
boolean isLocked = lock.tryLock(6, -1, TimeUnit.SECONDS);
通过以下结果可以看出,在http-nio-19428-exec-2无法并没有自动释放锁
这时候大家就会问了,不是没设置过期时间么,当然不会自动失效啦,我也是带着这个疑问,饭约了资料以及一些博主的讲解,其实redisson的看门狗机制主要是用于以下情况的:
分布式锁在执行过程中若锁失效的情况则会导致锁被其他线程占用,但是若锁不设置失效时长,虽然有逻辑控制释放锁,若出现宕机时则会导致锁未释放而死锁,而redission的看门狗机制就可以解决这个死锁问题,宕机后在默认的配置下最长30s 的时间后,这个锁就自动释放了。
浅看下源码
// 源码这里解释前面三四点为何出现leaseTime问题,该版本为leaseTime>0即不触发
// 源码跟进 tryLock->tryAcquire->tryAcquireAsync
<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {return this.evalWriteAsync(this.getRawName(), LongCodec.INSTANCE, command, "if (redis.call('exists', KEYS[1]) == 0) then redis.call('hincrby', 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(this.getRawName()), new Object[]{unit.toMillis(leaseTime), this.getLockName(threadId)});
}
执行 Redis 的 Lua 脚本来加锁
if (redis.call(‘exists’, KEYS[1]) == 0)
then redis.call(‘hincrby’,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]);
具体的源码解析我觉得可以看看该博客 https://www.cnblogs.com/Leo_wl/p/16600565.html