Redis实战11-实现优惠券秒杀下单
本篇,咱们来实现优惠券秒杀下单功能。通过本篇学习,我们将会有如下收获:
1:优惠券领券业务逻辑;
2:分析在高并发情况下,出现超卖问题产生的原因;
3:解决超卖问题两种方案:版本号法及CAS法
4:乐观锁弊端改进方案;
本文涉及内容比较多,篇幅会比较长,同时有大量截图。希望大家能耐心看完。好了,话不对说,咱们开始go go go~
一:基本的秒杀实现
下单时候需要判断:
1:秒杀是否开始或结束,如果尚未开始或者已经结束则无法下单;
2:库存是否充足,不充足无法下单
业务:
根据上图逻辑,我们可以得到代码相关逻辑:
1:查下优惠券、2:判断是否秒杀开始;3:判断秒杀是否结束;4:判断库存是否充足;5:扣减库存;6:创建订单;
相关代码如下:
二:分析上面代码是否存在问题
我们使用JMeter模拟200个用户去秒杀抢优惠券。运行结果:
异常是45.5%。这个不对啊,按照我们预期的应该是50%的用户失败才对。这45.5%,说明优惠券超卖出了9个。是吗?我们来查查优惠券表:
库存为-9.再来查询订单表:发现订单是109条。在高并发的情况下,还真的是超卖出了9个呢。
来分析为什么会出现这种情况呢?
来看看代码,扣减库存的相关代码:
我们来分享下扣除库存流程:
两个线程来抢,假设当前就库存就剩下一个了。线程1和线程2来抢这个库存。流程如下:
在高并发的情况下,线程谁先执行,还真不好说。在高并发情况下,可能执行的顺序就如下图:
超卖问题分析:
T1的时候,线程1执行从数据库查询操作,查询结果为1;然后CPU让出,线程2来执行,在T2时候,线程2也去执行数据库查询操作,查询结果也是1.然后线程2,让出CPU,T3时候,线程1得到了CPU执行权,执行扣除库存操作。T4时候线程得到了CPU执行权,同样执行扣除库存操作。当两个线程都执行完成后,数据库中的库存就成了-1了。
这只是有2个线程,当高并发的时候,有多个线程来查询库存,扣除库存。如果出现了上面情况,就会出现超卖情况。
超卖问题场景的解决方案
超卖问题就是典型的多线程安全问题,针对这一问题常见的解决方案就是加锁。锁分为乐观锁和悲观锁。我们来看看:
悲观锁:认为线程安全问题一定会发生的,因此在操作数据之前,先获取锁,确保线程串行执行。
例如:Synchronized、Lock都是悲观锁。
因为让线程串行了,所以,悲观锁的效率低。
乐观锁:认为线程安全问题不一定会发生,因此不加锁,只是在更新数据的时候,判断有没有其他线程对数据做了修改。
如果没有修改,则认为是安全的,自己才更新数据;
如果已经被其他线程修改了说明发生了安全问题,此时可以重试或者抛出异常。
乐观锁的关键是判断之前查询得到的数据是否被修改过,常见的方式有两种:
1:版本号法
每当数据被修改,版本号就+1
我们来看看还是上面多线程抢优惠券情况下,版本号法执行流程:
线程1,执行扣除库存后,版本号+1后,就是2。如下图:
我们再来看看线程2执行流程:
版本号法优化:
我们从上图的逻辑中可以看出,在查询库存的时候,同时把版本号也查询出来,在更新的时候,库存-1,版本号也-1.where条件是版本号=查询库存的时候的版本号。我们只需要观察版本号和库存关系:同时查询出来、同时-1.那么,我们可不可以优化下,只使用一个字段来实现呢?答案是可以的:我们就把库存作为版本号概念,在更新的时候,where 条件中的version=查询库存的时候的版本号这个条件换成:where id =10 and stock = #{stock}。这样就剩下一个字段。
其实,上面这个思路就是大名鼎鼎的CAS思想,也就是第二种常见的方案。
2:CAS法
我们来看看CAS法逻辑图:
知识小扩展:
针对CAS中自旋压力过大,我们可以使用Longadder这个类来解决。在Java8中提供了一个对AtomicLong改进的一个类:LongAdder.大量线程并发更新一个原子性的时候,天然的问题就是自旋,会导致并发性能问题,当然这个也比我们直接使用sync来得好。所以可以利用这个类,LongAdder来进行优化。
如果获取某个值,则会对cell和base值进行递增,最后返回一个完整的值。
好了,秒杀超卖问题分析完了,解决方案也有了。那么接下来,我们就来实现解决超卖问题的代码。
其实,我们只需要修改扣减库存的逻辑,只添加一个where条件即可。如下图:
修改完成之后,我们再使用JMeter模拟200个用户去秒杀抢优惠券。运行结果:
异常竟然是89.9%。比没修改前,异常率还增加了。我们再来看看结果树情况:
一上来,就库存不足了。我们z看看数据库中,库存情况:
优惠券领券了21张。为什么会出现这种情况呢?200个人来抢购100张优惠券,竟然才有21个人抢到了。这个肯定不是我们想要的结果。这个是什么原因导致的呢?其实这个就涉及到了CAS乐观锁的弊端了。我们重新分析:
如上图,假设刚开始,就有3个线程同时抢夺资源,其中线程3先执行了更新,将100更新成了99,然后线程1和线程2,就更新失败了。三个线程,只有一个更新成功了,就如同,我们在结果树上看到的一样。如下图:
那么失败的这两个,就抢不到了,导致我们库存有剩余。但是,咱们从真正的业务上来说,抢不到的依据是库存等于0,才算抢不到,而不是说我抢到之后,在修改的时候,别人不能够在抢成功了。我们线程1和线程2在抢的时候,库存还剩余99啊,这个是不符合实际业务的。这就是乐观锁方案的问题所在--成功率太低了。那么,我们对乐观锁法进行改进。
乐观锁法弊端改进
改进思路:在更新的时候,不再判断库存是否等于我手里的库存值。而是判断,库存是否大于0.如果大于,就执行扣除操作。
修改扣除库存相关代码:
修改完成之后,我们再使用JMeter模拟200个用户去秒杀抢优惠券。运行结果:
从上图中,我们看到异常率是50%。符合我们的预期。我们看看数据库中的库存:
订单表中也是100条订单。商品没有超卖,订单数量也正常。这样是不是很完美解决了超卖问题?
答案:否。我们可以看到,这个方案,直接是由数据库来处理的。我们知道,数据库本来就是比较宝贵的资源,在高并发情况下,这种方案,肯定是不行的。我们继续往下学习。
小总结
我们来总结下超卖这样线程安全问题,解决方案有哪些?
下一篇预告:
在下一篇中咱们将实现另外一个功能:一人一单的功能。在下一篇中,您将有如下收获:
1:悲观锁、乐观锁的使用场景;
2:synchronized关键字,在不同位置,锁的颗粒度是不同的,怎么优化呢;
3:toString方法之后,不能保证唯一,如果要保证唯一,需要在调用String的intern方法;
4:对spring事务有更深入了解-解决spring事务失效一种情况;
5:spring boot怎么开启对AspectJ的支持。
Redis实战11-实现优惠券秒杀下单相关推荐
- 【Redis学习05】优惠券秒杀及其优化
文章目录 1. 全局唯一ID 1.1 全局唯一ID介绍及生成策略 1.2 代码实现 1.3 总结 2. 优惠券秒杀下单 2.1 添加优惠券 2.2 优惠券秒杀功能 3. 超卖问题 3.1 问题分析 3 ...
- 视频教程- 19年录制Redis实战教程 高可用秒杀分布式锁布隆过滤器实战 SpringBoot教程整合-Java
19年录制Redis实战教程 高可用秒杀分布式锁布隆过滤器实战 SpringBoot教程整合 7年的开发架构经验,曾就职于国内一线互联网公司,开发工程师,现在是某创业公司技术负责人, 擅长语言有nod ...
- 【使用Redis分布式锁实现优惠券秒杀功能】-Redis学习笔记05
前言 本章节主要实现限时.限量优惠券秒杀功能,并利用分布式锁解决<超卖问题>.<一人一单问题>. 一.优惠券下单基本功能实现 1.功能介绍及流程图 2.代码实现 @Resour ...
- Redis应用案例之优惠券秒杀
概述 秒杀下单流程 下单核心思路:当我们点击抢购时,会触发右侧的请求,我们只需要编写对应的controller即可. 秒杀下单应该思考的内容: 下单需要判断两点: 秒杀是否开始或结束,如果尚未开始或已 ...
- Redis实战 - 11 Redis GEO 实现附近的人功能
各种社交软件里面都有附件的人的需求,在该应用中,我们查询附近1公里的食客,同时只需查询出20个即可. 文章目录 1. Redis GEO常用命令 2. 上传用户地理位置 1. RedisKeyCons ...
- Redis实战篇--优惠券秒杀
文章目录 Redis实战篇--优惠券秒杀 全局唯一ID 实现优惠券秒杀下单 超卖问题 一人一单 分布式锁 基于redis的分布式锁 Redis实战篇–优惠券秒杀 全局唯一ID 为什么需要全局唯一id? ...
- Redis(八) - Redis企业实战之优惠券秒杀
文章目录 一.全局唯一ID 1. 全局ID生成器 2. 全局唯一ID生成策略 3. Redis自增ID策略 二.实现优惠券秒杀下单 1. 添加优惠券 2. 编写添加秒杀券的接口 三.实现秒杀下单 四. ...
- 实战篇--优惠券秒杀
优惠券秒杀 全局唯一ID 当用户抢购时,就会生成订单并保存到tb_voucher_order这张表中,而订单表如果使用数据库自增ID就存在一些问题: id的规律性太明显 受单表数据量限制 全局ID生成 ...
- Redis 基础 - 优惠券秒杀《非集群》
参考 Redis基础 - 基本类型及常用命令 Redis基础 - Java客户端 Redis 基础 - 短信验证码登录 Redis 基础 - 用Redis查询商户信息 摘要 用Redis生成保证唯一性 ...
最新文章
- mysql第三章关系模型_一个MySQL关系模型只有三个关系(二维表)组成。_学小易找答案...
- PVUVIP之间的区别和联系
- Windows 2000配置Web服务器
- mysql数据库备份方式,跳槽大厂必看!
- fork-join详解(1)
- AttributeError: ‘SMOTE’ object has no attribute ‘fit_sample’
- Java的四种包访问权限
- concurrentbag 删除_你知道吗?这样删除iPhone中的APP腾出的空间会更大
- 为label或者textView添加placeHolder
- 浅谈iOS开发中的锁
- jquery实现多选框
- 训练序列与导频序列的概念辨析
- 解构沉浸式:AI+AR+Design 线下分享会【NSR主办的纽约场】
- 脾胃湿热吃什么药.体内湿气重有何表示 怎样去除
- echarts瀑布图_一种基于阶梯瀑布图的数据计算方法与流程
- Java微信支付API文档测试
- 实现图的邻接矩阵和邻接表的存储
- 视频显示相关名词解释
- vue-cli快速构建vue项目和本地模拟请求数据
- 百度地图最新版本sdk使用系列(六)-搜索周边美食,加油站等,并使用infoWindow展示
热门文章
- springboot+jsp酒店客房VIP客户管理系统java
- 具有大数据+数据开发+数据分析+算法工作经验的面试简历
- 练习瑕疵还有穿帮怎么修
- 想要打印数码照片?“Pixelmator Pro”教你如何自行裁剪出清晰生动的照片!
- 就在今天!Unix 之父的密码被破解了
- 获取文件编码格式 java_java实现获取文本文件的字符编码
- 图文转换html,语言运用 图文转换专题
- 计算机省二vf题库,湖南省计算机二级vf编程题库
- 同步通信与异步通信的主要区别
- Windows各版本GVLK密钥表