Redis框架(十一):大众点评项目 乐观锁解决超卖问题 悲观锁解决一人一单问题

SpringCloud章节复习已经过去,新的章节Redis开始了,这个章节中将会回顾Redis实战项目 大众点评
主要依照以下几个原则

  1. 基础+实战的Demo和Coding上传到我的代码仓库
  2. 在原有基础上加入一些设计模式,stream+lamdba等新的糖
  3. 通过DeBug调试,进入组件源码去分析底层运行的规则和设计模式

代码会同步在我的gitee中去,觉得不错的同学记得一键三连求关注,感谢:
Redis优化-链接: RedisVoucherCASProject
在这里插入图片描述

在这里插入图片描述

需求:乐观锁解决超卖问题 悲观锁解决一人一单问题

超卖问题

当秒杀进行时,如果有大量用户同时点击,就容易出现一种情况
大量库存被减到负数,这肯定是不友好的
下面是通过JMX进行的压测,模拟并发实现
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

一人一单
同样,我们不希望一个人拿到所有的秒杀商品

所以我们需要对这两种情况进行处理,锁机制就来了

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
版本号:就是通过定义版本Id,去进行判断,通过sql中的where条件就可以实现乐观锁
在这里插入图片描述
CAS:就是自旋,查看数据是否被改变,没有变就是ok的

业务代码

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 张大树
 * @since 2022-12-12
 */
@Service
@Slf4j
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
    @Resource
    private ISeckillVoucherService seckillVoucherService;

    @Resource
    private RedisIDProductor redisIDProductor;

和上一节相同,可以参考上一节

    @Override
    public Result seckillVoucher(Long voucherId) {

        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);

        if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
            return Result.fail("秒杀尚未开始");
        }

        if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
            return Result.fail("秒杀已经结束");
        }

        if (voucher.getStock() < 1) {
            return Result.fail("库存不足");
        }

实现一人一单功能,
为了保证一人一单,使用了悲观锁synchronized,需要注意

  1. synchronized想要保证效率,不要直接加载类上,判断出,一人一单为了是保证每个人下一单,那个我们直接锁ID,就可以了,这样每个id都有各自的线程可以走,不是所有都锁住!
  2. userId.toString()本身返回的是一个new类型 return new String(buf, true);,所以我们必须保证每次拿到的ID的对象是同一个,这里就是用了intern方法,它回去常量池中查找对应的id,保证了唯一性
  3. 这里的 @Transactional也需要注意,如果直接 调用的this.createVoucherOrder方法,没有实际对象,是事务失效的几种情况之一,所以需要找代理对象来实现!
        //实现一人一单
        Long userId = UserHolder.getUser().getId();

        //intern()是去常量池中去找userId,处理锁失效,事务失效
        synchronized(userId.toString().intern()) {
            IVoucherOrderService currentProxy = (IVoucherOrderService) AopContext.currentProxy();
            return currentProxy.createVoucherOrder(voucherId);
            /*调用的this.create方法,没有实际对象,是事务失效的几种情况之一,所以需要找代理对象来实现
            return 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("已经购买");
        }

为了防止超卖,其本身就是不断更新数量,所以使用了乐观锁,CAS方法,需要注意

  1. CAS: 通过设定判断库存数量来进行,适合更新数据使用
  2. 注意MP的使用seckillVoucherService.update() .setSql("stock = stock - 1") .eq("voucher_id", voucherId) .gt("stock", 0)//CAS: 通过设定判断库存数量来进行,适合更新数据使用 .update();
        //验证结束,扣减库存
        boolean flag = seckillVoucherService.update()
                .setSql("stock = stock - 1")
                .eq("voucher_id", voucherId)
                .gt("stock", 0)//CAS: 通过设定判断库存数量来进行,适合更新数据使用
                .update();

        if (!flag) {
            return Result.fail("库存不足");
        }

封装订单

        //封装订单
        VoucherOrder voucherOrder = new VoucherOrder();
        long orderId = redisIDProductor.nextId("order");
        //订单Id、用户Id、优惠券Id
        voucherOrder.setId(orderId).setUserId(userId).setVoucherId(voucherId);
        save(voucherOrder);


        return Result.ok(orderId);

    }
}

总结

如果引入代理对象 IVoucherOrderService currentProxy = (IVoucherOrderService) AopContext.currentProxy(); return currentProxy.createVoucherOrder(voucherId);
一定要注意加依赖,开启扫描注解

@EnableAspectJAutoProxy(exposeProxy = true)
@MapperScan("com.hmdp.mapper")
@SpringBootApplication
public class HmDianPingApplication {

    public static void main(String[] args) {
        SpringApplication.run(HmDianPingApplication.class, args);
    }

}

            
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>

在这里插入图片描述
在这里插入图片描述

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值