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

news/2025/2/1 8:56:33 标签: redis, 数据库, 缓存

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

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

优惠卷订单表

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

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

{
  "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 {


    @Autowired
    private ISeckillVoucherService seckillVoucherService;

    @Autowired
    private 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.订单id
        long 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 {


    @Autowired
    private ISeckillVoucherService seckillVoucherService;

    @Autowired
    private RedisIdWorker redisIdWorker;

    @Override
    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("库存不足");
        }
        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.订单id
        long order = redisIdWorker.nextId("order");
        voucherOrder.setId(order);
        voucherOrder.setUserId(id);//登录用户先写死
        voucherOrder.setVoucherId(voucherId);
        save(voucherOrder);
        Result.ok(order);
    }
}

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


http://www.niftyadmin.cn/n/5839226.html

相关文章

【协议详解】卫星通信5G IoT NTN SIB33-NB 信令详解

一、SIB33信令概述 在5G非地面网络&#xff08;NTN&#xff09;中&#xff0c;卫星的高速移动性和广域覆盖特性使得地面设备&#xff08;UE&#xff09;需要频繁切换卫星以维持连接。SIB32提供了UE预测当前服务的卫星覆盖信息&#xff0c;SystemInformationBlockType33&#x…

使用Pygame制作“俄罗斯方块”游戏

1. 前言 俄罗斯方块&#xff08;Tetris&#xff09; 是一款由方块下落、行消除等核心规则构成的经典益智游戏&#xff1a; 每次从屏幕顶部出现一个随机的方块&#xff08;由若干小方格组成&#xff09;&#xff0c;玩家可以左右移动或旋转该方块&#xff0c;让它合适地堆叠在…

vue相关的页面和js编写

vue和后端调用 1 前端vue的查询接口&#xff08;在src下的api中的对应目录编写调用后端的请求方法&#xff09; dispord.js文件 import request from /utils/request //封装了ajaxconst api_name /pms/dispord //对应后端controller上面的公共的请求路径 -- 到nginx中了…

UE5 GAS RPG Character Classes

在正常的游戏中&#xff0c;我们应该考虑如何去初始化角色属性&#xff0c;并且要给角色分好类型。比如&#xff0c;在我们游戏中&#xff0c;我们如何去初始化小兵的属性&#xff0c;并且还要实现小兵随着等级的增长而增加属性。而且就是小兵也有类型的区分&#xff0c;比如我…

新月智能护甲系统CMIA--未来战场的守护者

新月智能护甲系统&#xff08;Crescent Moon Intelligent Armor System&#xff0c;简称CMIA&#xff09; 新月智能护甲系统&#xff08;CMIA&#xff09;是新月结合了她多年的研究成果&#xff0c;开发出的一款高度智能化的个人防护装备。这款护甲集成了先进的环境监测、生命…

玩转大语言模型——配置图数据库Neo4j(含apoc插件)并导入GraphRAG生成的知识图谱

系列文章目录 玩转大语言模型——使用langchain和Ollama本地部署大语言模型 玩转大语言模型——ollama导入huggingface下载的模型 玩转大语言模型——langchain调用ollama视觉多模态语言模型 玩转大语言模型——使用GraphRAGOllama构建知识图谱 玩转大语言模型——完美解决Gra…

VUE之组件通信(一)

1、props 概述&#xff1a;props是使用频率最高的一种通信方式&#xff0c;常用与&#xff1a;父<——>子。 若 父传子&#xff1a;属性值是非函数。若 子传父&#xff1a;属性值是函数。 父组件&#xff1a; <template><div class"father">&l…

Python NumPy(7):连接数组、分割数组、数组元素的添加与删除

1 连接数组 函数描述concatenate连接沿现有轴的数组序列stack沿着新的轴加入一系列数组。hstack水平堆叠序列中的数组&#xff08;列方向&#xff09;vstack竖直堆叠序列中的数组&#xff08;行方向&#xff09; 1.1 numpy.concatenate numpy.concatenate 函数用于沿指定轴连…