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;
    }
  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
你好!实现点赞和评论功能可以使用Spring Boot作为后端框架,Redis作为缓存数据库MySQL作为持久化数据库,UniApp作为前端框架。以下是一个简单的实现步骤: 1. 创建Spring Boot项目并配置MySQLRedis的连接。 2. 创建实体类,如User、Post等,用于表示用户和帖子。 3. 创建MySQL数据库表,包括用户表和帖子表。 4. 使用Spring Data JPA或MyBatis等框架进行数据库操作,实现用户和帖子的增删改查功能。 5. 在帖子表中添加字段用于存储点赞数和评论数。 6. 在后端编写点赞和评论的接口,分别处理点赞和评论的业务逻辑。 - 点赞接口可以接收用户ID和帖子ID,将点赞数加一,并更新数据库。 - 评论接口可以接收用户ID、帖子ID和评论内容,将评论数加一,并将评论保存到数据库。 7. 使用Redis进行缓存,可以将点赞数和评论数存储在Redis中,以提高访问速度。 - 在点赞接口中,先查询Redis中是否存在点赞数,如果存在则直接返回;否则从数据库中查询并存储到Redis中。 - 在评论接口中,同样先查询Redis中是否存在评论数,如果存在则直接返回;否则从数据库中查询并存储到Redis中。 8. 在UniApp中使用Vue.js等技术进行前端开发,调用后端提供的接口实现点赞和评论的功能。 - 在页面中展示帖子列表,并显示点赞数和评论数。 - 点击点赞按钮时,调用后端的点赞接口,并更新页面上的点赞数。 - 点击评论按钮时,调用后端的评论接口,并更新页面上的评论数。 以上是一个简单的实现方案,具体的细节还需要根据你的实际需求进行调整和完善。希望对你有所帮助!如有更多问题,请随时提问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

ambition_test

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

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

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

打赏作者

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

抵扣说明:

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

余额充值