欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > 名人名企 > 基于Redis实现优惠券秒杀——第3期(分布式锁-Redisson)

基于Redis实现优惠券秒杀——第3期(分布式锁-Redisson)

2025/5/7 1:39:55 来源:https://blog.csdn.net/m0_74016429/article/details/147718369  浏览:    关键词:基于Redis实现优惠券秒杀——第3期(分布式锁-Redisson)

建议先看完第1期和第2期:

基于Redis实现优惠券秒杀——第1期(解决超卖问题、一人一单问题)-CSDN博客

基于Redis实现优惠券秒杀——第2期(分布式锁)-CSDN博客

上一期我们说到,使用分布式锁可能存在一下问题:

  • 基于SETNX实现的分布式锁存在以下问题
    1. 重入问题
      • 重入问题是指获取锁的线程,可以再次进入到相同的锁的代码块中,可重入锁的意义在于防止死锁,例如在HashTable这样的代码中,它的方法都是使用synchronized修饰的,加入它在一个方法内调用另一个方法,如果此时是不可重入的,那就死锁了。所以可重入锁的主要意义是防止死锁,我们的synchronized和Lock锁都是可重入的
    2. 不可重试
      • 我们编写的分布式锁只能尝试一次,失败了就返回false,没有重试机制。但合理的情况应该是:当线程获取锁失败后,他应该能再次尝试获取锁
    3. 超时释放
      • 我们在加锁的时候增加了TTL,这样我们可以防止死锁,但是如果卡顿(阻塞)时间太长,也会导致锁的释放。虽然我们采用Lua脚本来防止删锁的时候,误删别人的锁,但现在的新问题是没锁住,也有安全隐患
    4. 主从一致性
      • 如果Redis提供了主从集群,那么当我们向集群写数据时,主机需要异步的将数据同步给从机,万一在同步之前,主机宕机了(主从同步存在延迟,虽然时间很短,但还是发生了),那么又会出现死锁问题

Redisson入门 

  1. 导入依赖
<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.13.6</version>
</dependency>

2.配置Redisson客户端,在config包下新建RedissonConfig

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;@Configuration
public class RedissonConfig {@Beanpublic RedissonClient redissonClient() {Config config = new Config();config.useSingleServer().setAddress("redis://101.XXX.XXX.160:6379").setPassword("root");return Redisson.create(config);}
}

3.使用Redisson的分布式锁

@Resource
private RedissonClient redissonClient;@Test
void testRedisson() throws InterruptedException {//获取可重入锁RLock lock = redissonClient.getLock("anyLock");//尝试获取锁,三个参数分别是:获取锁的最大等待时间(期间会重试),锁的自动释放时间,时间单位boolean success = lock.tryLock(1,10, TimeUnit.SECONDS);//判断获取锁成功if (success) {try {System.out.println("执行业务");} finally {//释放锁lock.unlock();}}
}

4.替换我们上一期自己写的分布式锁

@Resource
private RedissonClient redissonClient;
@Override
public Result seckillVoucher(Long voucherId) {LambdaQueryWrapper<SeckillVoucher> queryWrapper = new LambdaQueryWrapper<>();//1. 查询优惠券queryWrapper.eq(SeckillVoucher::getVoucherId, voucherId);SeckillVoucher seckillVoucher = seckillVoucherService.getOne(queryWrapper);//2. 判断秒杀时间是否开始if (LocalDateTime.now().isBefore(seckillVoucher.getBeginTime())) {return Result.fail("秒杀还未开始,请耐心等待");}//3. 判断秒杀时间是否结束if (LocalDateTime.now().isAfter(seckillVoucher.getEndTime())) {return Result.fail("秒杀已经结束!");}//4. 判断库存是否充足if (seckillVoucher.getStock() < 1) {return Result.fail("优惠券已被抢光了哦,下次记得手速快点");}Long userId = UserHolder.getUser().getId();RLock redisLock = redissonClient.getLock("order:" + userId);boolean isLock = redisLock.tryLock();if (!isLock) {return Result.fail("不允许抢多张优惠券");}try {IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);} finally {redisLock.unlock();}
}

Redisson可重入锁原理

为了保证原子性,所以流程图中的业务逻辑也是需要我们用Lua来实现的

  • 获取锁的逻辑
local key = KEYS[1]; -- 锁的key
local threadId = ARGV[1]; -- 线程唯一标识
local releaseTime = ARGV[2]; -- 锁的自动释放时间
-- 锁不存在
if (redis.call('exists', key) == 0) then-- 获取锁并添加线程标识,state设为1redis.call('hset', key, threadId, '1');-- 设置锁有效期redis.call('expire', key, releaseTime);return 1; -- 返回结果
end;
-- 锁存在,判断threadId是否为自己
if (redis.call('hexists', key, threadId) == 1) then-- 锁存在,重入次数 +1,这里用的是hash结构的incrby增长redis.call('hincrby', key, thread, 1);-- 设置锁的有效期redis.call('expire', key, releaseTime);return 1; -- 返回结果
end;
return 0; -- 代码走到这里,说明获取锁的不是自己,获取锁失败
  • 释放锁的逻辑
local key = KEYS[1];
local threadId = ARGV[1];
local releaseTime = ARGV[2];
-- 如果锁不是自己的
if (redis.call('HEXISTS', key, threadId) == 0) thenreturn nil; -- 直接返回
end;
-- 锁是自己的,锁计数-1,还是用hincrby,不过自增长的值为-1
local count = redis.call('hincrby', key, threadId, -1);
-- 判断重入次数为多少
if (count > 0) then-- 大于0,重置有效期redis.call('expire', key, releaseTime);return nil;
else-- 否则直接释放锁redis.call('del', key);return nil;
end;

下一期我们继续来讲:

基于Redis实现优惠券秒杀--秒杀优化

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词