当前位置: 首页 > news >正文

优惠券秒杀(二)

库存超卖问题分析

库存超卖问题其本质就是多个线程操作共享数据产生的线程安全问题,即当一个线程在执行操作共享数据的多条代码的过程中,其他线程也参与了进来,导致了线程安全问题的产生。例如:线程1发送请求,查询库存,发现库存大于1,在还没来及扣除库存时,线程2甚至线程3等发送请求,发现这个数量也是大于1,那么这多个线程都会去扣除库存,最终多个线程都去扣除库存,此时就会出现库存的超卖问题。

image-20230707172612925
image-20230707172612925
  • 多线程安全问题常见的解决方案就是加锁

    • 悲观锁:认为线程安全问题一定会发生,因此在操作数据之前先获取锁,确保线程串行执行,synchronized、lock都属于悲观锁,属于同步锁,让线程串行执行,优点是简单粗暴,缺点是性能一般
    • 乐观锁:认为线程安全问题不一定会发生,只是在更新数据时,判断有没有其他线程对数据做了修改,优点是性能好,缺点是存在成功率问题
      • 如果没有修改,则认为是安全的,该线程进行数据更新
      • 如果已经被其他线程修改,则发生了线程安全问题,此时可以重试或异常
  • 使用乐观锁解决库存超卖问题

    • 乐观锁的关键是判断之前查询得到的数据是否被修改过,常见的方式有两种

      • 版本号法

        • 在数据库等中设置一个版本号字段,每次操作数据会对版本号进行加一操作,当每次读取数据时,读出版本号字段 ,在修改数据的时候,需要判断读出的版本号和数据库是否一致,如果一致,则表明在自己操作该数据的过程中,没有其他线程操作该数据,如果版本号不一致,则表明数据以及被别人修改过了。

          image-20230710111424101
          image-20230710111424101
      • CAS

        • 在扣减库存时,将现在库存和之前查询的库存做对比,如果一样,则说明没有其他线程在中间修改过库存数据,则认为是线程安全的,如果不一样,则说明有线程在中间修改过库存数据。

          image-20230710143222845
          image-20230710143222845

乐观锁解决超卖问题

  • 使用在扣减库存时,将现在库存和之前查询的库存做对比,如果一样,则表明没有人在此中间修改过库存,则认定线程安全,扣除库存

    @Override
    public Long seckillVoucher(Long voucherId) {

        // 查询秒杀优惠券信息
        SeckillVoucher seckillVoucher = seckillVoucherService.getById(voucherId);
        //判断秒杀是否开始和结束
        LocalDateTime beginTime = seckillVoucher.getBeginTime();
        LocalDateTime endTime = seckillVoucher.getEndTime();
        //如果当前时间 在开始时间之后 再结束时间之前 则表明秒杀能进行
        LocalDateTime localDateTime = LocalDateTime.now();
        if ( localDateTime.isBefore(beginTime) || localDateTime.isAfter(endTime) ){
            return null;
        }
        //获取库存量
        Integer stock = seckillVoucher.getStock();
        if (ObjectUtil.isNull(stock) || ObjectUtil.isNotNull(stock) && stock.intValue() <= 0){
            return null;
        }
        //扣减库存 将现在库存和之前查询的库存做对比,如果一样,则表明没有人在此中间修改过库存,则认定线程安全,扣除库存
        boolean update = seckillVoucherService.update(
                new LambdaUpdateWrapper<SeckillVoucher>().setSql("stock = stock - 1").eq(SeckillVoucher::getVoucherId, voucherId)
                        .eq(SeckillVoucher::getStock, stock)
        );
        if (!update){
            return null;
        }
        //创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        //创建订单ID
        long orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        //获取用户ID
        Long id = UserHolder.getUser().getId();
        voucherOrder.setUserId(id);
        // 代金券id
        voucherOrder.setVoucherId(voucherId);
        this.save(voucherOrder);
        return orderId;
    }

    该方式通过测试能够发现,失败率很高,这主要是由于当多个人都拿到库存时,只有一个能够扣减成功,其他的由于.eq(SeckillVoucher::getStock, stock)无法完成扣减。

    其实在超卖问题中,我们需要保证的是在没用库存的情况下,不能再进行库存扣减,所有需要保证的是库存大于0,所有可以设置.gt(SeckillVoucher::getStock, 0)

    @Override
    public Long seckillVoucher(Long voucherId) {

        // 查询秒杀优惠券信息
        SeckillVoucher seckillVoucher = seckillVoucherService.getById(voucherId);
        //判断秒杀是否开始和结束
        LocalDateTime beginTime = seckillVoucher.getBeginTime();
        LocalDateTime endTime = seckillVoucher.getEndTime();
        //如果当前时间 在开始时间之后 再结束时间之前 则表明秒杀能进行
        LocalDateTime localDateTime = LocalDateTime.now();
        if ( localDateTime.isBefore(beginTime) || localDateTime.isAfter(endTime) ){
            return null;
        }
        //获取库存量
        Integer stock = seckillVoucher.getStock();
        if (ObjectUtil.isNull(stock) || ObjectUtil.isNotNull(stock) && stock.intValue() <= 0){
            return null;
        }
        //扣减库存 将现在库存和之前查询的库存做对比,如果一样,则表明没有人在此中间修改过库存,则认定线程安全,扣除库存
        boolean update = seckillVoucherService.update(
                new LambdaUpdateWrapper<SeckillVoucher>().setSql("stock = stock - 1").eq(SeckillVoucher::getVoucherId, voucherId)
                        .gt(SeckillVoucher::getStock, 0)
        );
        if (!update){
            return null;
        }
        //创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        //创建订单ID
        long orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        //获取用户ID
        Long id = UserHolder.getUser().getId();
        voucherOrder.setUserId(id);
        // 代金券id
        voucherOrder.setVoucherId(voucherId);
        this.save(voucherOrder);
        return orderId;
    }

本文由 mdnice 多平台发布

http://www.lryc.cn/news/97285.html

相关文章:

  • selenium的java方式打开IE浏览器
  • 分类评估指标
  • OpenCV:图像直方图计算
  • 用QFramework来重构 祖玛游戏
  • 生活杂记-显示器尺寸
  • 在CSDN学Golang云原生(Kubernetes Pod无状态部署)
  • @Bean的作用
  • 【论文阅读22】Label prompt for multi-label text classification
  • EasyExcel数据导出功能封装
  • 通过web.xml来配置servlet程序
  • umi 创建的项目中,如何配置多个环境变量
  • Mysql 5.7 连接数爆满 清理连接数
  • HTTPS工作原理
  • 十大基础算法
  • Java---第八章(字符串-----String,StringBuilder 和 StringBuffer)
  • k8s集群的部署
  • 设计模式——观察者模式
  • 在Debian 12 上安装 PHP 5.6, 7.4
  • 微服务——统一网关Getway
  • [ELK安装篇]:基于Docker虚拟容器化(主要LogStash)
  • 纪录片《打铁文艺社》:从全美高中生电影节到多项国际赞誉,聚焦城市公共艺术的蜕变之路
  • VLAN---虚拟局域网
  • 新的CoolSiC™槽沟MOSFET技术,用于低栅氧化物应力和高性能
  • 【开源项目】低代码数据可视化开发平台-Datav
  • 【自动话化运维】Ansible常见模块的运用
  • 深入理解C语言中的字符指针初始化与用法
  • es添加索引命令行和浏览器添加索引--图文详解
  • Java 大数字运算之 BigDecimal 类
  • MySQL 8.0 OCP (1Z0-908) 考点精析-架构考点1:二进制日志文件(Binary log)
  • MY.CNF