Redis代金卷(优惠卷)秒杀案例-单应用版

news/2025/2/2 13:53:03/

优惠卷表:优惠卷基本信息,优惠金额,使用规则  包含普通优惠卷和特价优惠卷(秒杀卷)

优惠卷的库存表:优惠卷的库存,开始抢购时间,结束抢购时间.只有特价优惠卷(秒杀卷)才需要填写这些信息

优惠卷订单表

卷的表里已经有一条普通优惠卷记录

下面首先新增一条秒杀优惠卷记录

{
  "shopId": 1,
  "title": "100元代金券",
  "subTitle": "周一至周五均可使用",
  "rules": "全场通用\\n无需预约\\n可无限叠加\\n不兑现、不找零\\n仅限堂食",
  "payValue": 8000,
  "actualValue": 10000,
  "type": 1,
  "stock": 100,
  "beginTime": "2022-01-25T10:09:17",
  "endTime": "2022-01-26T12:09:04"
}
 

返回一个订单

实现秒杀下单

就是往下面这张表中添加数据  创建订单  

这里暂时只需要添加主键id  购买的代金卷id  

对应的还需要去扣减库存

关于订单的主键

使用Redis生成全局唯一ID示例-CSDN博客

控制器

业务层

传入的优惠卷id

@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {@Autowiredprivate ISeckillVoucherService seckillVoucherService;@Autowiredprivate RedisIdWorker redisIdWorker;@Override@Transactional(rollbackFor = Exception.class)public Result seckillVoucher(Long voucherId) {//1.查询优惠券SeckillVoucher byId = seckillVoucherService.getById(voucherId);if (byId.getBeginTime().isAfter(LocalDateTime.now())) {//2.判断秒杀是否开始return Result.fail("秒杀尚未开始");}if (byId.getEndTime().isBefore(LocalDateTime.now())) {//3.判断秒杀是否结束return Result.fail("秒杀已经结束");}if (byId.getStock() < 1) {//4.判断库存是否充足return Result.fail("库存不足");}//5.扣减库存boolean success = seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).update();if(!success){return Result.fail("库存不足");}//6.创建订单VoucherOrder voucherOrder = new VoucherOrder();//6.1.订单idlong order = redisIdWorker.nextId("order");voucherOrder.setId(order);voucherOrder.setUserId(1L);//登录用户先写死voucherOrder.setVoucherId(voucherId);save(voucherOrder);return Result.ok(order);}
}

以上代码,在高并发场景下会出现超卖现象

以下用乐观锁方式解决超卖问题

用乐观锁 CAS法解决超卖问题

就是在更新时候添加个条件

当200个并发线程打进去之后

虽然解决了并发安全问题,但是出现成功率低的问题   200个线程进来  没有卖完 很多都失败了

如何解决??

修改下条件  where id=? and stock>0 即可

这样就可以解决超卖问题

以上是用JMeter并发测试的,从结果看,系统还存在一个问题,例如对方开启并发访问的工具,这样会导致所有订单都是同一个用户购买的情况,或者说一个人购买了好几单

在订单表中出现如图情况

这样的漏洞,就好比黄牛了,那么系统如何实现一人一单,就是说一个用户最多下一个订单的需求

其实很简单  只要满足user_id 和 voucher_id唯一即可

就是做个查询,该用户有没有下单

如果这样写  并发情况下还是会出现问题

因为当你去判断count>0时候可能已经有10个线程都完成了查询   在判断count的时候  都查出来等于0的情况

@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {@Autowiredprivate ISeckillVoucherService seckillVoucherService;@Autowiredprivate RedisIdWorker redisIdWorker;@Overridepublic Result seckillVoucher(Long voucherId) {//1.查询优惠券SeckillVoucher byId = seckillVoucherService.getById(voucherId);if (byId.getBeginTime().isAfter(LocalDateTime.now())) {//2.判断秒杀是否开始return Result.fail("秒杀尚未开始");}if (byId.getEndTime().isBefore(LocalDateTime.now())) {//3.判断秒杀是否结束return Result.fail("秒杀已经结束");}if (byId.getStock() < 1) {//4.判断库存是否充足return Result.fail("库存不足");}Long id = UserHolder.getUser().getId();//表示获取用户id//id.toString()实际是new了一个String对象,然后调用intern()方法,这个方法会去常量池中查找有没有这个对象,如果有就返回这个对象,如果没有就添加到常量池中,然后返回这个对象synchronized (id.toString().intern()){//如果这样调用,前面有个this 事务使用代理对象去执行的//return createVoucherOrder(voucherId);IVoucherOrderService proxy = (IVoucherOrderService)AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);}}@Transactional(rollbackFor = Exception.class)public Result createVoucherOrder(Long voucherId) {Long id = UserHolder.getUser().getId();////一人一单的查询Integer count = query().eq("user_id", id).eq("voucher_id", voucherId).count();if(count > 0){return Result.fail("您已经购买过一次了");}//5.扣减库存boolean success = seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).gt("stock", 0).update();if(!success){return Result.fail("库存不足");}//6.创建订单VoucherOrder voucherOrder = new VoucherOrder();//6.1.订单idlong order = redisIdWorker.nextId("order");voucherOrder.setId(order);voucherOrder.setUserId(id);//登录用户先写死voucherOrder.setVoucherId(voucherId);save(voucherOrder);Result.ok(order);}
}

以上方案仅适用于单机版本


http://www.ppmy.cn/news/1568709.html

相关文章

深度学习学习笔记(第31周)

目录 摘要 Abstracts 1、全卷积网络&#xff08;FCN&#xff09;的简单介绍 1.1、CNN与FCN的比较 2、FCN上采样理论讲解 2.1、双线性插值上采样 2.2、反卷积上采样 2.3、反池化上采样 2、 FCN具体实现过程 3、 FCN模型实现过程 3.1、模型训练 3.2、FCN模型的简单总结…

在ubuntu下一键安装 Open WebUI

该脚本用于自动化安装 Open WebUI&#xff0c;并支持以下功能&#xff1a; 可选跳过 Ollama 安装&#xff1a;通过 --no-ollama 参数跳过 Ollama 的安装。自动清理旧目录&#xff1a;如果安装目录 (~/open-webui) 已存在&#xff0c;脚本会自动删除旧目录并重新安装。完整的依…

PostgreSQL 数据查询操作(排序、筛选、连接、分组、子查询)

&#xff08;1&#xff09;排序查询与筛选查询操作 排序查询使用ORDER BY&#xff0c;对某个表中的列1按照列升序&#xff08;ASC&#xff0c;默认升序&#xff09;或降序&#xff08;DESC&#xff09;排序&#xff0c;排序后展示列1和列2两列。此外&#xff0c;也可以按照多列…

《深入分析 TNN、MNN 和 NCNN:为不同硬件平台挑选最佳深度学习框架》

文章目录 TNN、MNN 和 NCNN 开源深度学习框架对比引言框架概述TNN (Tencent Neural Network)MNN (Mobile Neural Network)NCNN (Neural Network Compression and Inference) 逻辑推理图神经网络推理流程性能对比内存消耗对比硬件支持对比结论个人观点与推荐1. 针对移动端应用2.…

在 AMD GPU 上使用 vLLM 的 Triton 推理服务器

Triton Inference Server with vLLM on AMD GPUs — ROCm Blogs 2025年1月8日&#xff0c;作者&#xff1a;Fabricio Flores&#xff0c;Tiffany Mintz&#xff0c;Eliot Li&#xff0c;Yao Liu&#xff0c;Ted Themistokleous&#xff0c;Brian Pickrell&#xff0c;Vish Vadl…

Janus-Pro 论文解读:DeepSeek 如何重塑多模态技术格局

Janus-Pro&#xff1a;多模态领域的璀璨新星——技术解读与深度剖析 一、引言 在人工智能的浩瀚星空中&#xff0c;多模态理解与生成模型犹如耀眼的星座&#xff0c;不断推动着技术边界的拓展。Janus-Pro作为这一领域的新兴力量&#xff0c;以其卓越的性能和创新的架构&#x…

Vue.js 深度解析:响应式、虚拟 DOM 与编译优化的艺术

1. 响应式系统的底层实现 1.1 依赖收集与派发更新 Vue 3 的响应式系统基于 Proxy API 重构&#xff0c;其核心在于&#xff1a; 依赖收集&#xff1a;通过 track 函数建立 target -> key -> effect 的映射关系 派发更新&#xff1a;通过 trigger 函数根据依赖关系触发…

四.3 Redis 五大数据类型/结构的详细说明/详细使用( hash 哈希表数据类型详解和使用)

四.3 Redis 五大数据类型/结构的详细说明/详细使用&#xff08; hash 哈希表数据类型详解和使用&#xff09; 文章目录 四.3 Redis 五大数据类型/结构的详细说明/详细使用&#xff08; hash 哈希表数据类型详解和使用&#xff09;2.hash 哈希表常用指令(详细讲解说明)2.1 hset …