5.2 分布式锁-Redission快速入门
引入依赖:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.6</version>
</dependency>
配置Redisson客户端:
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient(){
// 配置
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.174.128:6379");
// 创建RedissonClient对象
return Redisson.create(config);
}
}
在 VoucherOrderServiceImpl使用Redisson带的分布式锁:
@Resource
private RedissonClient redissonClient;
@Override
public Result seckillVoucher(Long voucherId) {
// 1.获取优惠券信息
SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
// 2.判断秒杀是否开始
LocalDateTime beginTime = voucher.getBeginTime();
LocalDateTime endTime = voucher.getEndTime();
if(beginTime.isAfter(LocalDateTime.now()) || endTime.isBefore(LocalDateTime.now())){
return Result.fail("不再秒杀时段内!");
}
// 3.判断库存是否充足
if(voucher.getStock() < 1){
//库存不足
return Result.fail("库存不足!");
}
Long userId = UserHolder.getUser().getId();
// 这个代码我们不用了,下面要用Redisson中的分布式锁
// SimpleRedisLock lock = new SimpleRedisLock("order:" + userId, stringRedisTemplate);
RLock lock = redissonClient.getLock("order:" + userId);
boolean isLock = lock.tryLock();
// 判断是否获取锁成功
if(!isLock){
// 获取锁失败,返回错误和重试
return Result.fail("不允许重复下单~");
}
try {
// 获取代理对象(只有通过代理对象调用方法,事务才会生效)
IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
return proxy.createVoucherOrder(voucherId);
} finally {
lock.unlock();
}
}
进行测试:
在集群环境下,一秒一千次请求~ 一个用户只能下一单。分布式锁测试成功~
5.3 分布式锁-redission可重入锁原理
在Lock锁中,他是借助于底层的一个voaltile的一个state变量来记录重入的状态的,比如当前没有人持有这把锁,那么state=0,假如有人持有这把锁,那么state=1,如果持有这把锁的人再次持有这把锁,那么state就会+1 ,如果是对于synchronized而言,他在c语言代码中会有一个count,原理和state类似,也是重入一次就加一,释放一次就-1 ,直到减少成0 时,表示当前这把锁没有被人持有。
在redission中,我们的也支持支持可重入锁
在分布式锁中,他采用hash结构用来存储锁,其中大key表示表示这把锁是否存在,用小key表示当前这把锁被哪个线程持有。流程图如下:
为什么每次获取锁成功 或 释放锁 后都要重新设置锁的有效期呢?
这样是为了下面的业务有足够的时间去执行~
1、接下来我们一起分析一下当前的可重入锁实现的lua表达式
- 获取锁的Lua脚本:
local key = KEYS[1]; -- 锁的key
local threadId = ARGV[1]; -- 线程唯一标识
local releaseTime = ARGV[2]; -- 锁的自动释放时间
-- 判断是否存在
if(redis.call('exists', key) == 0) then
-- 不存在, 获取锁
redis.call('hset', key, threadId, '1');
-- 设置有效期
redis.call('expire', key, releaseTime);
return 1; -- 返回结果
end;
-- 锁已经存在,判断threadId是否是自己
if(redis.call('hexists', key, threadId) == 1) then
-- 存在, 获取锁,重入次数+1
redis.call('hincrby', key, threadId, '1');
-- 设置有效期
redis.call('expire', key, releaseTime);
return 1; -- 返回结果
end;
return 0; -- 代码走到这里,说明获取锁的不是自己,获取锁失败
- 释放锁的Lua脚本:
local key = KEYS[1]; -- 锁的key
local threadId = ARGV[1]; -- 线程唯一标识
local releaseTime = ARGV[2]; -- 锁的自动释放时间
-- 判断当前锁是否还是被自己持有
if (redis.call('HEXISTS', key, threadId) == 0) then
return nil; -- 如果已经不是自己,则直接返回
end;
-- 是自己的锁,则重入次数-1
local count = redis.call('HINCRBY', key, threadId, -1);
-- 判断是否重入次数是否已经为0
if (count > 0) then
-- 大于0说明不能释放锁,重置有效期然后返回
redis.call('EXPIRE', key, releaseTime);
return nil;
else -- 等于0说明可以释放锁,直接删除
redis.call('DEL', key);
return nil;
end;
2、测试Redission的分布式锁的可重入效果
/\*\*
\* @author lxy
\* @version 1.0
\* @Description 测试Redisson的分布式锁的可重入性质
\* @date 2022/12/21 16:01
\*/
@Slf4j
@SpringBootTest
public class RedissonTest {
@Resource
private RedissonClient redissonClient;
private RLock lock;
@BeforeEach
void setUp(){
lock = redissonClient.getLock("order");
}
@Test
void method1() throws InterruptedException {
// 尝试获取锁
boolean isLock = lock.tryLock(1L, TimeUnit.SECONDS);
if (!isLock){
log.error("获取锁失败....1");
return;
}
try {
log.info("获取锁成功....1");
method2();
log.info("开始执行业务....1");
} finally {
log.warn("开始释放锁....1");
lock.unlock();
}
}
void method2(){
// 尝试获取锁
boolean isLock = lock.tryLock();
if(!isLock){
log.error("获取锁失败....2");
return;
}
try {
log.info("获取锁成功....2");
log.info("开始执行业务....2");
} finally {
log.warn("准备释放锁....2");
lock.unlock();
}
}
}
Debug测试:
3、接下来我们可以查看下Redisson中的分布式锁的实现:
注意源码中的KEYS[1]指外边的大Key,AVG[1]:大Key的过期时间,AVG[2]:当前的线程ID
源码中的KEYS[1]指外边的大Key,AVG[2]:大Key的过期时间,AVG[3]:当前的线程ID。KEYS[2]和ARGV[1]所代表的含义我们后面会讲解~
5.4 分布式锁-redission锁重试和WatchDog机制
关于锁可重试的原理见:https://www.processon.com/view/link/63a86e6534446c6f609d3a3f
关于锁超时续约 和 锁释放的原理见:https://www.processon.com/view/link/63a891cece3d3c6150d7c2ac
Redission分布式锁原理
注意:只有leaseTime=-1,才会走WatchDog的逻辑
总结:Redisson分布式锁原理
- 可重入:利用hash结构记录线程id和重入次数
- 可重试:利用信号量和PubSub功能实现等待、唤醒,获取锁失败的重试机制
- 超时续约:利用watchDog,每隔一段时间(releaseTime / 3),重置超时时间
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
dk-1714165268083)]
[外链图片转存中…(img-zwKE4vk0-1714165268083)]
[外链图片转存中…(img-6WyxzarZ-1714165268084)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新