在本篇文章中,咱们将使用Redisson中间件其中一个强大的功能组件“分布式锁”,用以解决秒杀系统中高并发产生的多线程对于共享资源/代码块的访问所致使的“并发安全”问题!git
而之因此须要Redisson这一组件,是由于在上一篇文章中,咱们在采用Redis解决秒杀系统中出现的“库存超卖”、“重复秒杀”等问题时所对应的代码存在着瑕疵,即在使用Redis的SetNX操做以前、而还没来得及执行Expire操做的时候,Redis的节点若是刚好出现宕机或者服务不能用的状况,那将会致使相应的Key永远存在于缓存中,而处于“被锁死”的状态!redis
Redisson分布式锁的出现能够很好地解决这种问题,其底层的实现机制在于“Redisson内部提供了一个监控锁的看门狗,它的做用是在Redisson实例被关闭前,不断的延长锁的有效期”,除此以外,Redisson还经过加锁的方法提供了leaseTime的参数来指定加锁的时间,即超过这个时间后锁便自动解开了。spring
接下来,咱们将基于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 操做组件的自定义注入,其完整源代码以下所示:
bash
/**
* 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;
}复制代码
从该源代码中,咱们主要是使用了Redisson分布式锁中的“可重入锁”组件,其使用须要通过以下几个步骤:数据结构
A.须要尝试去获取锁,其对应的代码以及注释以下所示:多线程
//TODO:第一个参数30s=表示尝试获取分布式锁,而且最大的等待获取锁的时间为30s
//TODO:第二个参数10s=表示上锁以后,10s内操做完毕将自动释放锁
Boolean cacheRes=lock.tryLock(30,10,TimeUnit.SECONDS);复制代码
B.在获取到锁以后,即cacheRes=true,便可进入秒杀核心业务逻辑的处理;同时在处理完成以后,须要释放锁,以下所示:
//TODO:释放锁
lock.unlock();复制代码
(4)至此,基于Redisson的分布式锁解决高并发业务场景下,并发多线程对于共享资源/共享代码块的并发访问所出现的并发安全的问题的代码实战已经完毕了!
咱们接下来进入压测环节,仍然以以前的测试用例为例,即killId=3的待秒杀商品的可秒杀数量total=6,能够随机选取的用户Id列表的总数为10个,其取值为10040~10049,则理论上最好的结果是:total最终变为=0,同时item_kill_success有6条用户秒杀成功后生成的订单记录。
这个时候,咱们尝试将线程组中并发的线程数调整为10w,点击启动按钮,稍等片刻,观察控制台的输出信息以及item_kill和item_kill_success的数据库表,查看其最终的记录结果,以下图所示:
对于这一结果,其实能够说是预料之中了!
Redisson的分布式锁确实能够在高并发业务场景/多线程高并发 场景下起到举足轻重的做用。而在现实生活中,其实Debug也是建议各位小伙伴能够去研究这一综合中间件,它彻底能够替代Redis在项目中的使用,并且其提供的数据结构以及使用方式跟JavaSE中的数据结构很相似,好比List、Set、Map、Queue等等均可以在Java中找到相应的踪迹(而事实上Redisson的许多分布式组件跟数据结构正是基于Java中相应的数据结构来实现的)!
一、目前,这一秒杀系统的总体构建与代码实战已经所有完成了,完整的源代码数据库地址能够来这里下载:gitee.com/steadyjack/… 记得Fork跟Star啊!!
二、最后,不要忘记了关注一下Debug的技术微信公众号: