摘要:本篇博文是“Java秒杀系统实战系列文章”的第十五篇,本文我们将借助综合中间件Redisson优化“秒杀系统中秒杀的核心业务逻辑”,解决Redis的原子操作在优化秒杀逻辑过程中出现的部分瑕疵。
内容:Redisson,字如其名,是搭建在缓存中间件Redis的基础之上的一款综合中间件,除了拥有Redis本身提供的强大功能之外,还提供了诸如分布式锁、分布式服务、延迟队列、远程调用等强大的功能(从名字就可以看出来了:Redis + son,犹如Redis的儿子,儿子不仅继承了老爸强大的血脉,而且还自己修炼、发展出了属于自己的一套本领)
在本篇文章中,我们将使用Redisson中间件其中一个强大的功能组件“分布式锁”,用以解决秒杀系统中高并发产生的多线程对于共享资源/代码块的访问所导致的“并发安全”问题!
而之所以需要Redisson这一组件,是因为在上一篇文章中,我们在采用Redis解决秒杀系统中出现的“库存超卖”、“重复秒杀”等问题时所对应的代码存在着瑕疵,即在使用Redis的SetNX操作之前、而还没来得及执行Expire操作的时候,Redis的节点如果恰好出现宕机或者服务不能用的情况,那将会导致相应的Key永远存在于缓存中,而处于“被锁死”的状态!
Redisson分布式锁的出现可以很好地解决这种问题,其底层的实现机制在于“Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期”,除此之外,Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间,即超过这个时间后锁便自动解开了。
接下来,我们将基于SpringBoot搭建的秒杀系统整合Redisson,加入其相关的依赖以及配置,并使用其“分布式锁”组件彻底解决秒杀过程中出现的“库存超卖”以及“重复秒杀”等问题。
(1)首先,需要加入Redisson的依赖,版本号为3.8.2,如下所示:
<!--redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>${redisson.version}</version>
</dependency>
然后需要在配置文件application.properties中加入Redis服务所在的Host、Port等信息,如下所示:
#spring.redis.password=
redis.config.host=redis://127.0.0.1:6379
(2)紧接着,是基于Spring Boot自定义注入Redisson相关操作的Bean组件,其中,主要是RedissonClient 操作组件的自定义注入,其完整源代码如下所示:
/**
* redisson通用化配置
* @Author:debug (SteadyJack)
* @Date: 2019/7/2 10:57
**/
@Configuration
public class RedissonConfig {
@Autowired
private Environment env;
@Bean
public RedissonClient redissonClient(){
Config config=new Config();
config.useSingleServer()
.setAddress(env.getProperty("redis.config.host"))
.setPassword(env.getProperty("spring.redis.password"));
RedissonClient client=Redisson.create(config);
return client;
}
}
(3)前期工作已经准备完毕,接下来我们需要将其应用到秒杀系统中 秒杀的核心操作逻辑,在KillService服务类中我们开辟了一个新的处理方法,即killItemV4,其完整的源代码如下所示:
@Autowired
private RedissonClient redissonClient;
//商品秒杀核心业务逻辑的处理-redisson的分布式锁
@Override
public Boolean killItemV4(Integer killId, Integer userId) throws Exception {
Boolean result=false;
final String lockKey=new StringBuffer().append(killId).append(userId).append("-RedissonLock").toString();
RLock lock=redissonClient.getLock(lockKey);
try {
//TODO:第一个参数30s=表示尝试获取分布式锁,并且最大的等待获取锁的时间为30s
//TODO:第二个参数10s=表示上锁之后,10s内操作完毕将自动释放锁
Boolean cacheRes=lock.tryLock(30,10,TimeUnit.SECONDS);
if (cacheRes){
//TODO:核心业务逻辑的处理
if (itemKillSuccessMapper.countByKillUserId(killId,userId) <= 0){
ItemKill itemKill=itemKillMapper.selectByIdV2(killId);
if (itemKill!=null && 1==itemKill.getCanKill() && itemKill.getTotal()>0){
int res=itemKillMapper.updateKillItemV2(killId);
if (res>0){
commonRecordKillSuccessInfo(itemKill,userId);
result=true;
}
}
}else{
throw new Exception("redisson-您已经抢购过该商品了!");
}
}
}finally {
//TODO:释放锁
lock.unlock();
}
return result;
}