Springboot黑马点评(3)——优惠券秒杀
【还剩Redisson的最后两节没测试 后续补上】
另外,后期单独整理一份关于分布式锁笔记
1 优惠券秒杀实现
1.1 用户-优惠券订单设计
1.1.1 全局ID生成器
使用数据库自增ID作为订单ID存在问题

1.1.2 考虑全局唯一ID生成逻辑
![图片[1]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240826151258209-1682410329.png)

- 时间戳(Long类型 31bit):采取将当前时间与标定时间(自设定一个历史时间)相减,将其秒数相减,采用纪元秒数方法(不可时间节点之间直接相减)
long timeInSeconds = localDateTime.toEpochSecond(ZoneOffset.UTC);
当我们把本地日期时间localDateTime如2019-11-15T13:15:30转换为纪元秒时,那么结果将是1970-01-01T00:00:00Z到2019-11-15T13:15:30的时间差距,单位为秒,这也是我们平时说的时间戳
- 序列号(直接调用StringRedisTemplate中针对value值的自增长 即
opsValueFor().increment())
对于该部分id的生成,对应的key需要特殊考虑:
1)考虑到不同业务对应的下单序列值 要拼接keyPrefix作为业务区分
2)考虑到使用相同key对该业务的id无限制地自增,将超出Redis中对于键值的最大数量限制,且不同时间批次下单的用户没有区分度,会造成冗余,使用用户下单的当天日期作为key拼接进去,便于统计当日下单订单数

- 拼接得到总id
总id值 = 时间戳值左移32位(空余出序列号的位数)再和序列号作 “或” 运算


1.2 简单的秒杀券下单逻辑
数据库表中维护普通优惠券voucher以及秒杀优惠券killVoucher,秒杀优惠券除了具备普通优惠券的一般属性外,还具备库存、秒杀开始时间、秒杀结束时间。

1.2.1 用户下单秒杀券的逻辑:
(秒杀的前提是查验该用户是否登录状态,通用拦截器首先在该接口的最前端取到请求头的用户信息,添加到该请求对应的threadlocal中,统一刷新所有请求登录用户/非登录用户的ttl,再经过前置登录拦截器查验threadlocal中用户是否为空即验证某些必须登录才能访问的接口)
鉴权后,接口首先检查当前时间是否在秒杀时间段内,其次检查该秒杀券的库存是否为0,两层逻辑检查完之后,即可扣减该秒杀券的库存-1
创建用户-秒杀券的订单记录

1.2.2 超卖问题
![图片[2]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240827105625999-970513881.png)
![图片[3]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240827105656732-871181901.png)
聚合报告显示并没有让100个线程生成100个订单,即成功率100/200
![图片[4]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240827105828563-211878835.png)
即 多线程并发安全问题

1.2.3 超卖问题的解决方案

- 乐观锁:
![图片[5]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)
![图片[6]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)
【乐观锁存在的弊端】
![图片[7]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)
乐观锁容易在短时间内阻碍多线程争取足够库存 导致成功率极低
所以 改变约束条件:stock>0 提高并发的成功率
![图片[8]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240827145325598-1924727768.png)
(还有分段加锁方案)
1.3 一人一单逻辑实现
在建立订单时查询该用户id和该优惠券id的订单是否存在,需要保证相同优惠券 一人只能下一单,如果存在则扣除库存失败且不建立该订单。
但是该逻辑存在线程并发的问题,当同一用户的多线程并发访问时,将同时查询到订单不存在而创建新的订单,还是会存在一人多单。
1.3.1 解决一人多单问题
-
单机模式下解决方法:悲观锁
(因为该业务是在查询数据而非修改数据,无法作类似乐观锁似的修改检查)
![图片[9]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)
由于Transaction注解(事务注解)要处理的表单数据处理是在该代码块结束的时候提交事务,所以要保证加锁在提交事务之后释放锁,保证该事务处理已完成。(难点)

-
集群模式下解决方法:分布式锁
但synchronized()互斥锁 在多实例分布式部署到多台机器上 形成负载均衡的集群时存在问题
模拟多机部署环境
![图片[10]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)
调用nginx(8080)ip来访问模拟集群,在nginx端下同一用户请求该集群,由于负载均衡,请求会在集群中平均传到各个服务实例上,然而互斥锁只在同一实例中生效
![图片[11]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)
![图片[12]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)
(1) 集群模式下采用synchronized()存在的问题
不加锁的正常查询创建订单逻辑:
![图片[13]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)
加锁后
![图片[14]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)
加锁存在于集群模式中:
![图片[15]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)
![图片[16]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)
(2) 考虑使用“分布式”锁
![图片[17]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)
引出“分布式锁”:在分布式系统或者集群模式下,多线程可见且互斥的锁
![图片[18]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)
(3) Redis实现“互斥”锁
对于Redis实现互斥锁的问题,如果系统中的某个机器或微服务宕机了,其申请的setnx键值对将持续保留在系统中(即 持续加锁中),其他服务无法实现请求,将出现死锁的局面。
![图片[19]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)
实现Redis分布式锁初级版本
![图片[20]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)
![图片[21]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)
![图片[22]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)
初级版本存在的问题:
![图片[23]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)
![图片[24]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)
改造分布式锁初级版本
之前用的是 线程id(自动递增)
集群模式下 每个jvm都维护一列递增的线程id 容易存在重复的线程标识
因此 借助UUID+线程ID来避免重复
![图片[25]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)
![图片[26]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)
模拟当其一任务未结束的线程的key过期时,该并发任务解锁,其他线程获得锁之后,该任务尝试解锁时,核对了键值对中包含的线程信息,发现与自己的线程标识并不匹配,避免了误删其他线程锁的情况
![图片[27]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)
判断完 释放前阻塞
![图片[28]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)
Redis的事务和mysql的事务Transaction不同:
编写lua脚本
![图片[29]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)
![图片[30]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)
![图片[31]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)
基于Redis实现的弊端

-
可重入锁
在Redis实现基础上的改造方法:
改变之前字符串存储的方式,value存入类型改为HashMap,用来存储该线程方法嵌套加锁放锁时,每一层嵌套作各自的标记。
每一层嵌套加锁时,判断该key是否存在,进一步判断key内部存储的线程标识是否嵌套调用该方法的线程标识,修改其嵌套加锁标记+1
每一层嵌套放锁时,判断该key存储的线程标识是否嵌套调用该方法的线程标识,修改其嵌套加锁标记-1,若修改结果为0则意味着该嵌套是最后一层,放锁即结束线程所有的任务,这时便可删除该键值了。
![图片[32]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)
![图片[33]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)
![图片[34]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)





![图片[5]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240827111740698-848475611.png)
![图片[6]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240827112107333-1085231652.png)
![图片[7]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240827112954560-1217865389.png)
![图片[9]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240827154855065-2039337587.png)

![图片[10]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240827163547954-1226500872.png)
![图片[11]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240827165634795-227090430.png)
![图片[12]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240827165712306-1122490196.png)
![图片[13]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240827165756299-868613387.png)
![图片[14]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240827165824643-583510458.png)
![图片[15]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240827170043631-1931713956.png)
![图片[16]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240829145641339-1449853719.png)
![图片[17]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240829145826364-1410527897.png)
![图片[18]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240829150652600-1529747339.png)
![图片[19]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240829151324680-1503748717.png)
![图片[20]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240829151456031-1873046632.png)
![图片[21]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240830101824525-1542859546.png)
![图片[22]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240830110502496-722810057.png)
![图片[23]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240830163328962-2078300536.png)
![图片[24]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240830170130695-503840547.png)
![图片[25]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240830170319102-1294840610.png)
![图片[26]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240830171716199-594711990.png)
![图片[27]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240830172939800-2008125699.png)
![图片[28]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240830173640514-156652154.png)
![图片[29]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240831144955941-1503527268.png)
![图片[30]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240831145530856-1435213347.png)
![图片[31]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240831150721523-1531408979.png)

![图片[32]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240831164040103-1556895671.png)
![图片[33]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240831170951319-1156079683.png)
![图片[34]-Springboot实战——黑马点评之互斥锁 - 拾光赋-拾光赋](https://image.baidu.com/search/down?url=https://img2024.cnblogs.com/blog/3420577/202408/3420577-20240831171206574-723662414.png)

![表情[baoquan]-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/smilies/baoquan.gif)


暂无评论内容