Redis 优惠券秒杀功能 ——单机

下单时判断两个条件:
1、是否在优惠券领取时间内

2、库存是否充足

整体流程为如下图

基本下单功能代码:

@Override
    @Transactional
    public Result seckillVoucher(Long voucherId) {
        // 1.查询优惠券
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        // 2.判断秒杀是否开始
        if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
            return Result.fail("秒杀未开始!");
        }
        // 3.判断秒杀是否结束
        if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
            return Result.fail("秒杀已结束!");
        }
        // 4.判断库存是否足够
        if (voucher.getStock() < 1) {
            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();
        long orderId = redisIdWorker.nextId("order");
        voucherorder.setId(orderId);
        voucherorder.setVoucherId(voucherId);
        Long userId = UserHolder.getUser().getId();
        voucherorder.setUserId(userId);
        save(voucherorder);
        // 7.返回结果
        return Result.ok(orderId);
    }

遇到的问题

超卖问题 高并发情况下 优惠券库存变为负数

在还未扣减库存时,另一个线程也开始查询

解决方法 加锁

悲观锁与乐观锁

使用乐观锁方案

判断查询得到的数据是否有被修改过

方案一:版本号法

方案二:CAS法 使用查询到的数据进行比较 替代版本

在扣减库存时增加条件

// 5.扣减库存
        boolean success = seckillVoucherService.update()
                .setSql("stock = stock - 1")
                .eq("voucher_id", voucherId).eq("stock",voucher.getStock())
                .update();

结果库存没有用完

修改条件

boolean success = seckillVoucherService.update()
                .setSql("stock = stock - 1")
                .eq("voucher_id", voucherId).gt("stock", 0)
                .update();

但是依旧在频繁访问数据库 ,还需要进行进一步的优化

修改秒杀使得一个用户只能获得一个订单

查询过程中依旧有并发问题 使用悲观锁方案

要注意锁的释放时机 以及事务是否生效

将用户id作为锁

@Override
    public Result seckillVoucher(Long voucherId) {
        // 1.查询优惠券
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        // 2.判断秒杀是否开始
        if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
            return Result.fail("秒杀未开始!");
        }
        // 3.判断秒杀是否结束
        if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
            return Result.fail("秒杀已结束!");
        }
        // 4.判断库存是否足够
        if (voucher.getStock() < 1) {
            return Result.fail("库存不足!");
        }
        Long userId = UserHolder.getUser().getId();
        synchronized(userId.toString().intern()) {
            // 创建代理对象 解决事务问题
            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            return proxy.createVoucherOrder(voucherId);
        }

    }

    @Transactional
    public  Result createVoucherOrder(Long voucherId) {
        // 一人一单
        Long userId = UserHolder.getUser().getId();
            int count = query().eq("user_id", userId).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();
            long orderId = redisIdWorker.nextId("order");
            voucherorder.setId(orderId);
            voucherorder.setVoucherId(voucherId);

            voucherorder.setUserId(userId);
            save(voucherorder);
            // 7.返回结果
            return Result.ok(orderId);
    }

解决了单机一人一单并发问题

但是集群模式下出现问题

多个JVM存在 有不同的锁 要让多个JVM使用同一把锁

  • 10
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值