springboot 结合mysql、redis+lua 实现库存扣减方案,防止超卖

springboot 结合mysql、redis+lua 实现库存扣减方案,防止超卖

表结构

库存表:
在这里插入图片描述
订单表:
在这里插入图片描述

方案1:采用mysql 自带行级锁

select * from t_stock for update;
当前事务提交之后,其他线程才能获取锁。判断库存是否大于或等于当前需要购买的数量,否则返回库存不足。
    @Transactional
    @Override
    public int createOrderForUpdate(Integer productId, Integer count) {
        Stock stock = stockMapper.selectForUpdate(productId);
        if (stock.getStock() < count) {
            throw new StockLackException("库存不足");
        }
        Order order = new Order();
        order.setUserId((int) Thread.currentThread().getId());
        order.setProductId(productId);
        order.setCount(count);
        order.setOrderTime(new Date());
        // 创建订单
        baseMapper.insert(order);
        // 扣减库存
        stockMapper.stockForUpdate(stock.getId(), count);
        return 1;
    }

方案2:基于版本号的乐观锁

update t_stock set stock = stock - #{count}, version = version + 1
where version = #{version} and stock >= #{count} and id = #{id}
返回结果为此次更新影响的行数,如果影响的行数大于0,表示此次更新库存充足,否则返回库存不足。
    @Transactional
    @Override
    public int createOrderByVersion(Integer productId, Integer count) {
        Stock stockByProductId = getStockByProductId(productId);
        Integer version = stockByProductId.getVersion();
        int i = stockMapper.stockByVersion(stockByProductId.getId(), count, version);
        if (i > 0) {
            // 不存在并发,创建订单
            Order order = new Order();
            order.setUserId((int) Thread.currentThread().getId());
            order.setProductId(productId);
            order.setCount(count);
            order.setOrderTime(new Date());
            baseMapper.insert(order);
            return 1;
        }
        throw new StockLackException("库存不足");
    }

方案3:基于redis 分布式锁

每次只允许一个线程操作,库存减为0时,返回库存不足。
	@Override
    public int createOrderByRedisLock(Integer productId, Integer count) {
        String key = "stock:" + productId;
        int stock = (int) redisTemplate.opsForValue().get(key);
        if (stock <= 0) {
            throw new StockLackException("库存不足");
        }
        RLock lock = redissonClient.getLock(LOCK_KEY);
        if (lock.tryLock()) {
            try {
                int stock1 = (int) redisTemplate.opsForValue().get(key);
                if (stock1 >= count) {
                    Order order = new Order();
                    order.setUserId((int) Thread.currentThread().getId());
                    order.setProductId(productId);
                    order.setCount(count);
                    order.setOrderTime(new Date());
                    baseMapper.insert(order);
                    redisTemplate.opsForValue().decrement(key, count);
                } else {
                    throw new StockLackException("库存不足");
                }
            } finally {
                lock.unlock();
            }
        }
        return 1;
    }

方案4:redis + lua 脚本原子操作

local key = KEYS[1]               -- 获取第一个参数作为键名
local incrementBy = tonumber(ARGV[1])  -- 获取第二个参数作为增量值,并将其转换为数字类型
local stock = redis.call("GET", key)  -- 通过GET命令获取键的当前值
if nil == stock or not stock then
    return -2  -- 库存还未初始化
elseif tonumber(stock) >= incrementBy then
    return redis.call('DECRBY', key, incrementBy)  -- 库存充足
else
    return -1  -- 库存不足
end
原理与方案3类似,将库存判断与扣减的过程原子化,省去加锁的过程。
返回 -2 时,表示库存未初始化,需要先初始化库存到缓存中,而且只能有一个线程执行初始化的操作,
所以这里也需要加锁,初始化之前进行一次非空判断,防止重复初始化;初始化完成后,重新校验一次库存。
    @Override
    public int createOrderByRedisLua(Integer productId, Integer count) {
        // >=0 库存充足  -1 库存不足   -2 库存未初始化
        long validateStock = validateStock(productId, count);
        if (-2 == validateStock) {
            String key = "stock:" + productId;
            RLock lock = redissonClient.getLock(LOCK_KEY);
            if (lock.tryLock()) {
                try {
                    Object o = redisTemplate.opsForValue().get(key);
                    if (o == null) {
                        Stock stockByProductId = getStockByProductId(productId);
                        redisTemplate.opsForValue().set(key, stockByProductId.getStock());
                    }
                } finally {
                    lock.unlock();
                }
            }
            validateStock = validateStock(productId, count);
        }
        if (-1 == validateStock) {
            throw new StockLackException("库存不足");
        }
        if (validateStock >= 0) {
            Order order = new Order();
            order.setUserId((int) Thread.currentThread().getId());
            order.setProductId(productId);
            order.setCount(count);
            order.setOrderTime(new Date());
            baseMapper.insert(order);
        }
        return 1;
    }
    private long validateStock(Integer productId, Integer count) {
        long stock = redisTemplate.execute(defaultRedisScript,
                Collections.singletonList("stock:" + productId), count);
        return stock;
    }
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

ambition_test

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

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

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

打赏作者

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

抵扣说明:

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

余额充值