一、setnx 实现的分布式锁的问题
我们之前介绍了使用 setnx 实现分布式锁,但是其含有如下问题:
- 不可重入:同一个线程无法多次获取同一把锁
- 不可重试:获取锁只产生一次
- 超时释放:业务执行时间过长导致锁释放,存在安全隐患
- 主从一致性:主从同步延迟导致安全问题 (正常不会出现,主从同步很快)
二、Redisson 是什么
Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。
翻译: Redisson 就是一个 Redis 框架,帮我们实现一些功能
https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95
三、SpringBoot 整合 Redission
- 导入依赖
<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.6</version>
</dependency>
- 配置 Redisson
不推荐使用 SpringBoot 提供 的Starter 的方式,会替代 Spring 官方的 Redis 实现
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author: WanqingLiu
* @Date: 2022/12/10/16:25
*/
@Configuration
public class RedisConfig {
@Bean
public RedissonClient redissonClient(){
// 配置类
Config config = new Config();
// 设置 redis 地址
config.useSingleServer().setAddress("redis:// 192.168.244.130:6379");
// 返回
return Redisson.create(config);
}
}
- 注入使用 RedissonClient
使用模板:
业务代码:
@Service
public class ServiceImpl {
// 注入 RedissonClient
@Resource
private RedissonClient redissonClient;
@Override
public Result testRedissonClient(Long voucherId) {
// ****** 获取锁 *******
RLock rLock = redissonClient.getLock("lock:order:" + userId);
boolean isLock = rLock.tryLock();
// **** 获取锁失败提示 *****
if (!isLock){
return Result.fail("提升获取锁失败");
}
try {
// 执行业务
createVoucherOrder(voucherId);
} finally {
// ******** 释放锁 *********
rLock.unlock();
}
}
@Transactional
// 实现一人一单 —— 悲观锁 —— 以用户 id 加锁 —— 处理同一个用户的并发安全问题,防止一个用户并发买很多单
public Result createVoucherOrder(Long voucherId){
// 业务代码
}
四、Redisson 实现 可重入锁原理
使用 Hash 结构记录线程id 和 线程重录次数
-
获取锁:每次相同线程获取锁重录次数 + 1
获取锁的 Lua 脚本如下:
-
释放锁:把重入次数减 1,当可重入次数为 0 时,删除锁
释放锁的 Lua 脚本如下:
五、Redisson 锁重试和 WatchDog 机制
- 可重试实现思路:利用信号量和订阅者功能实现等待、唤醒,获取锁失败重试机制
Redisson 锁重试关键源码注释:
- 超时延续实现思路:利用 watchdog 看门狗机制,每隔一段时间 (releaseTime / 3), 重置超时时间
WatchDog 机制关键源码注释: