使用乐观锁和 CAS 技术解决超卖问题

目录

一、背景介绍

二、乐观锁与 CAS 技术的实现方案

三、代码实现:使用乐观锁与 CAS 解决库存超卖问题

​编辑

四、代码解析

五、总结


在电商系统中,秒杀活动是一个非常典型的高并发场景。在这种场景下,如何保证库存数量的正确性,防止超卖(Overselling)是实现的难点之一。今天,我将介绍如何使用乐观锁和 CAS(Compare-And-Swap)技术来解决超卖问题,并通过代码示例进行详细说明。希望对大家在处理高并发库存扣减时有所帮助。


一、背景介绍

1.1 超卖问题的产生

在秒杀或抢购活动中,由于用户并发请求过多,多个线程可能同时读取到相同的库存数据,导致库存数量被多个线程同时修改。这样就容易出现“库存变成负数”的情况,即产生了超卖问题

超卖问题的典型场景:

  • 当多个用户几乎同时提交秒杀请求时,如果没有合适的库存锁机制,所有线程会读取到同样的库存数,并尝试同时进行扣减操作。
  • 结果就是库存被重复扣减,导致卖出的数量超过了实际库存,造成库存混乱。

1.2 解决超卖的常见方法

为了解决超卖问题,常见的方法有以下几种:

  • 悲观锁:

    • 使用数据库的行锁或分布式锁来防止多个线程同时修改库存。
    • 缺点:在高并发环境下,容易导致锁等待和阻塞,影响系统性能。
  • 乐观锁:

    • 通过数据库的版本号(Version)或 CAS 技术(Compare-And-Swap)来实现数据的原子性更新,避免并发写操作冲突。
    • 优点:在高并发场景下,不会阻塞线程,能够提高系统的吞吐量。

二、乐观锁与 CAS 技术的实现方案

乐观锁(Optimistic Locking)认为并发操作不会经常发生,因此在每次修改数据时,检查数据是否被其他线程修改过。如果数据没有被修改过,则执行更新操作;如果数据已经被其他线程修改,则放弃本次操作或进行重试。

在数据库操作中,乐观锁通常是通过版本号机制或 CAS 技术来实现的。

  • 版本号机制:

    • 在数据表中添加一个 version 字段,每次更新数据时,检查 version 是否与读取时的一致,如果一致,则更新,并将 version 加 1。
  • CAS(Compare-And-Swap):

    • 使用 SQL 中的条件更新语句,如 WHERE stock > 0,在扣减库存时,只有当库存量满足条件时,才进行更新操作,从而实现库存的原子性扣减。

三、代码实现:使用乐观锁与 CAS 解决库存超卖问题

以下是使用乐观锁与 CAS 技术实现库存扣减的代码示例:

@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Resource
    private ISeckillVoucherService seckillVoucherService;

    @Resource
    private RedisIdWorker redisIdWorker;

    @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.扣减库存 - 使用乐观锁和CAS
        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 orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        // 6.2.设置用户id
        Long id = UserHolder.getUser().getId();
        voucherOrder.setUserId(id);
        // 6.3.设置优惠券id
        voucherOrder.setVoucherId(voucherId);
        save(voucherOrder);
        // 7.返回订单id
        return Result.ok(orderId);
    }
}

四、代码解析
  1. 查询优惠券信息:

    • 通过 seckillVoucherService.getById(voucherId) 查询秒杀优惠券的详细信息,如开始时间、结束时间和库存量。
  2. 判断秒杀活动是否在有效期内:

    • 判断秒杀是否尚未开始(beginTime.isAfter(LocalDateTime.now()))或已结束(endTime.isBefore(LocalDateTime.now()))。
  3. 库存判断:

    • 在进行库存扣减前,先判断库存是否大于 0,如果小于 1,则直接返回“库存不足”的提示。
  4. 使用乐观锁与 CAS 扣减库存:

    • 通过 update().setSql("stock = stock - 1").eq("voucher_id", voucherId).gt("stock", 0) 的条件更新语句进行扣减库存操作。
    • 这里使用了 gt("stock", 0) 来确保只有在库存大于 0 时,才执行扣减操作,从而实现库存的原子性更新。
  5. 创建订单:

    • 在扣减库存成功后,为用户生成一个新的订单,并保存到数据库中。
  6. 返回订单 ID:

    • 返回创建的订单 ID 作为秒杀成功的结果。

五、总结

通过使用乐观锁与 CAS 技术,可以在高并发场景下有效防止库存超卖问题,保证库存操作的原子性和一致性。该方案相比悲观锁在高并发场景下具有更好的性能,同时避免了锁竞争和阻塞。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Takumilovexu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值