基于Redis的setnx实现的分布式锁仍然存在以下问题:
1.不可重入。即如果线程1中的方法a调用了方法b,ab方法都要获取同一把锁,setnx不能实现。
2.不可重试。获取锁,成功就成功,不然就失败,没有重试的机会。
3.超时释放。如果业务时间太长,锁自己释放了,存在安全隐患。
4.主从一致性。看图解。
Redission是一个在Redis基础上实现的一个分布式工具的集合,在分布式系统下用的各种东西,都能找到。
入门:
1.导入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.23.3</version>
</dependency>
2.配置Redisson客户端
@Configuration
public class RedissionConfig {
@Bean
public RedissonClient redissonClient() {
// 配置
Config config=new Config();
config.useSingleServer().setAddress("redis://localhost:6379").setPassword("123456");
// 创建RedissonClient对象
return Redisson.create(config);
}
}
3.直接使用
@Resource
private RedissonClient redissonClient;
// 1.创建锁对象
RLock lock = redissonClient.getLock("lock:order" + userId);
/**2.获取锁
* 三个参数。第一个代表等待时间,获取失败的话,时间内可重复获取锁
* 第二个是锁过期时间,第三个为单位
* 空置的话 -1 30 (s)秒 意味着,不重入,30s后过期
*/
boolean success = lock.tryLock();
// 2.1获取锁失败
if(!success) {
return Result.fail("一人只能下一单哦");
}
// 2.2成功拿到锁
try {
IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
return proxy.createVoucherOrder(voucherId);
} finally {
// 3.释放锁
lock.unlock();
}
Redisson可重入锁的原理:
基于hash的redis锁,hash类型为:key file value
先根据key判断锁是否存在,不存在就添加锁,file为线程标识,value存放锁计数初始1,并设置有效期。存在,根据key拿到的锁判断file是否一样,否,失败。一样,file+1,刷新锁有效期。接下来流程一样:执行业务,释放锁要判断锁标识是否一致,一致,file-1,然后判断file是否为0,为0就释放。操作比较多,为了确保原子性,我们要用到lua来实现啦。
Redisson对于重试的思路:
1.当锁超时时间过期时,会使用看门口WatchDog不断重新设置超时时间去重试获取锁
2. 看门狗只有在我们没有设置超时时间,在超时时间默认的情况下才会有。
3.在每次重新获取锁之前,不会马上不断的进行去重试,会等待是否有释放锁的信号和不断的精确判断剩余超时时间是否充裕。
Redisson对于超时释放的解决:
看门狗机制:Redission提供的一种自动延期机制,这个机制使得Redission提供的分布式锁是可以自动续期的。
看门狗机制提供的默认超时时间是30*1000毫秒,也就是30秒。
在Redission中想要启动看门狗机制,那么我们就不用在获取锁的时候自己定义leaseTime(锁自动释放时间)
。
如果自己定义了锁自动释放时间的话,无论是通过lock
还是tryLock
方法,都无法启用看门狗机制。
但是,如果传入的leaseTime
为-1,也是会开启看门狗机制的。
Redisson对于主从一致性问题的解决:
所有 Redis 节点都是独立的节点,相互之间无任何关系,都可以做读写操作。此时,我们想获取锁就必须依次向多个 Redis 都去获取锁(之前直接向 Master 节点获取就可以),多个 Redis 节点都保存锁的标识,才算获取成功。
此外,我们还以为给这些独立的节点再加上从节点 Slave,即使一个独立节点宕机了导致其对应的从节点变成新的主节点,且节点上锁标识丢失了也没有关系,因为我们只有在每一个节点都拿到锁才算成功, 尽管可以在这个空虚的节点上获取到锁,但在其他节点上是获取不到的,最终仍然是失败,因此只要有任意一个节点存货,其他线程就不可能拿到锁,就不会出现锁失效问题。这样,既保留了主从同步机制,又确保了 Redis 集群的高可用特性,同时还避免了主从一致所引发的锁失效问题,这个方案就叫做 mutilLock