Java秒杀系统实战系列~基于Redisson的分布式锁优化秒杀逻辑

Java秒杀系统实战系列~基于Redisson的分布式锁优化秒杀逻辑

摘要:

本篇博文是“Java秒杀系统实战系列文章”的第十五篇,本文我们将借助综合中间件Redisson优化“秒杀系统中秒杀的核心业务逻辑”,解决Redis的原子操作在优化秒杀逻辑过程中出现的部分瑕疵。

内容:

Redisson,字如其名,是搭建在缓存中间件Redis的基础之上的一款综合中间件,除了拥有Redis本身提供的强大功能之外,还提供了诸如分布式锁、分布式服务、延迟队列、远程调用等强大的功能(从名字就可以看出来了:Redis + son,犹如Redis的儿子,儿子不仅继承了老爸强大的血脉,而且还自己修炼、发展出了属于自己的一套本领)。

www.zeeklog.com - Java秒杀系统实战系列~基于Redisson的分布式锁优化秒杀逻辑

在本篇文章中,我们将使用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; } 

从该源代码中,我们主要是使用了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的数据库表,查看其最终的记录结果,如下图所示:

www.zeeklog.com - Java秒杀系统实战系列~基于Redisson的分布式锁优化秒杀逻辑
www.zeeklog.com - Java秒杀系统实战系列~基于Redisson的分布式锁优化秒杀逻辑

对于这一结果,其实可以说是预料之中了!

Redisson的分布式锁确实可以在高并发业务场景/多线程高并发 场景下起到举足轻重的作用。而在现实生活中,其实Debug也是建议各位小伙伴可以去研究这一综合中间件,它完全可以替代Redis在项目中的使用,而且其提供的数据结构以及使用方式跟JavaSE中的数据结构很类似,比如List、Set、Map、Queue等等都可以在Java中找到相应的踪影(而事实上Redisson的许多分布式组件跟数据结构正是基于Java中相应的数据结构来实现的)!

补充:

1、目前,这一秒杀系统的整体构建与代码实战已经全部完成了,完整的源代码数据库地址可以来这里下载: 记得Fork跟Star啊!!!

2、由于相应的博客的更新可能并不会很快,故而如果有想要快速入门以及实战整套系统的,可以考虑联系Debug获取这一“Java秒杀系统”的完整视频教程(课程是收费的!),当然,大家也可以点击下面这个链接  联系Debug或者加入相应的技术交流群进行交流!

3、实战期间有任何问题都可以留言或者与Debug联系、交流;技术交流群:605610429(Java实战基地交流1群)

4、最后,不要忘记了关注一下Debug的技术微信公众号:

www.zeeklog.com - Java秒杀系统实战系列~基于Redisson的分布式锁优化秒杀逻辑

Read more

Outlook个人邮箱OAuth认证配置全流程

Outlook个人邮箱OAuth认证配置全流程

Outlook个人邮箱OAuth认证配置 以下是使用OAuth 2.0认证Outlook个人邮箱、获取access_token并发送邮件的完整流程: 🔑 步骤1:注册Azure AD应用 1. 访问 Azure门户 2. 创建新应用注册: * 名称:自定义应用名(如"MyMailApp") * 重定向URI:http://localhost(开发环境)或实际域名 3. 身份验证设置 * 在身份验证 选项卡: * 添加重定向URI:http://localhost * 启用 “访问令牌” 和 “ID 令牌” * 允许公共客户端流:设置为 “是” 4. 证书和密码 * 创建客户端密钥(客户端密码) * 记录密钥值(仅显示一次) * 设置合理的有效期(通常1-2年) 记录关键信息: Application

【大模型智能体】大型语言模型的智能体能力:架构、习得、安全性与未来路径

【大模型智能体】大型语言模型的智能体能力:架构、习得、安全性与未来路径

Agent Skills for Large Language Models: Architecture, Acquisition, Security, and the Path Forward 大型语言模型的智能体能力:架构、习得、安全性与未来路径 摘要 从单体语言模型向模块化、技能赋能智能体的转变,标志着大型语言模型(LLMs)实际部署方式的一次根本性转折。智能体技能——即按需加载的、由指令、代码和资源组成的可组合包——使得动态能力扩展无需重新训练成为可能,而无需将所有程序性知识编码于模型权重之中。这一范式通过渐进式上下文披露、可移植技能定义以及与模型上下文协议(MCP)的集成得以形式化。本综述对智能体技能领域进行了全面阐述,该领域在过去几个月中发展迅速。我们围绕四个轴向组织该领域内容:(一)架构基础,审视SKILL.md规范、渐进式上下文加载以及技能与MCP的互补作用;(二)技能获取,涵盖基于技能库的强化学习(SAGE)、自主技能发现(SEAgent)以及组合式技能合成;(三)大规模部署,

【JAVA进阶】Spring Boot 核心知识点之自动配置:原理与实战

【JAVA进阶】Spring Boot 核心知识点之自动配置:原理与实战

文章目录 * 一、Spring Boot 自动配置:开启高效开发之门 * 1.1 什么是 Spring Boot * 1.2 Spring Boot 自动配置的重要性 * 二、Spring Boot 自动配置初相识 * 2.1 自动配置的概念 * 2.2 核心注解 @EnableAutoConfiguration * 2.2.1 注解作用 * 2.2.2 与 @SpringBootApplication 的关系 * 2.3 自动配置的基本原理 * 三、深入剖析自动配置原理 * 3.1 关键组件 SpringFactoriesLoader * 3.1.1 工作机制

时序数据库选型革命:深入解析Apache IoTDB的架构智慧与实战指南

时序数据库选型革命:深入解析Apache IoTDB的架构智慧与实战指南

目录 引言:时序数据时代的到来 第一章 时序数据的独特魅力与挑战 1.1 时序数据的"个性特征" 1.2 时序数据管理的"技术大山" 第二章 时序数据库的"心脏"——存储引擎 2.1 架构演进:从通用到专用 2.2 IoTDB的创新存储设计 第三章 选型的"金标准"——关键指标详解 3.1 性能指标:数据库的"体能测试" 3.2 功能完备性:数据库的"技能树"