Redis 如何实现防止超卖和库存扣减操作?

本文探讨了电商项目中防止超卖的库存操作问题,分析了基于数据库单库存和多库存的不足,并重点介绍了如何利用Redis的incrby特性来实现并发安全的库存扣减,以提升系统性能。同时,提到了在Redis丢失库存数据时的恢复策略,以及具体实现包括lua脚本和分布式锁的应用。
摘要由CSDN通过智能技术生成

电商当项目经验已经非常普遍了,不管你是包装的还是真实的,起码要能讲清楚电商中常见的问题,比如库存的操作怎么防止商品被超卖

  • 解决方案
  • 分析
    • 基于数据库单库存
    • 基于数据库多库存
    • 基于redis
  • 基于redis实现扣减库存的具体实现
    • 初始化库存回调函数(IStockCallback)
    • 扣减库存服务(StockService)
    • 调用
6617e057ef743b4d5238bf969131e0a3.jpeg

在日常开发中有很多地方都有类似扣减库存的操作,比如电商系统中的商品库存,抽奖系统中的奖品库存等。

解决方案

  1. 使用mysql数据库,使用一个字段来存储库存,每次扣减库存去更新这个字段。
  2. 还是使用数据库,但是将库存分层多份存到多条记录里面,扣减库存的时候路由一下,这样子增大了并发量,但是还是避免不了大量的去访问数据库来更新库存。
  3. 将库存放到redis使用redis的incrby特性来扣减库存。

分析

在上面的第一种和第二种方式都是基于数据来扣减库存。

基于数据库单库存

第一种方式在所有请求都会在这里等待锁,获取锁有去扣减库存。在并发量不高的情况下可以使用,但是一旦并发量大了就会有大量请求阻塞在这里,导致请求超时,进而整个系统雪崩;而且会频繁的去访问数据库,大量占用数据库资源,所以在并发高的情况下这种方式不适用。

基于数据库多库存

第二种方式其实是第一种方式的优化版本,在一定程度上提高了并发量,但是在还是会大量的对数据库做更新操作大量占用数据库资源。

基于数据库来实现扣减库存还存在的一些问题:

  • 用数据库扣减库存的方式,扣减库存的操作必须在一条语句中执行,不能先selec在update,这样在并发下会出现超扣的情况。如:
update  number  set x=x -1  where x >  0
  • MySQL自身对于高并发的处理性能就会出现问题,一般来说,MySQL的处理性能会随着并发thread上升而上升,但是到了一定的并发度之后会出现明显的拐点,之后一路下降,最终甚至会比单thread的性能还要差。
  • 当减库存和高并发碰到一起的时候,由于操作的库存数目在同一行,就会出现争抢InnoDB行锁的问题,导致出现互相等待甚至死锁,从而大大降低MySQL的处理性能,最终导致前端页面出现超时异常。

基于redis

针对上述问题的问题我们就有了第三种方案,将库存放到缓存,利用redis的incrby特性来扣减库存,解决了超扣和性能问题。但是一旦缓存丢失需要考虑恢复方案。比如抽奖系统扣奖品库存的时候,初始库存=总的库存数-已经发放的奖励数,但是如果是异步发奖,需要等到MQ消息消费完了才能重启redis初始化库存,否则也存在库存不一致的问题。项目实战(点击下载):SpringBoot+SpringCloud+Mybatis+Vue电商项目实战

基于redis实现扣减库存的具体实现

  • 我们使用redis的lua脚本来实现扣减库存
  • 由于是分布式环境下所以还需要一个分布式锁来控制只能有一个服务去初始化库存
  • 需要提供一个回调函数,在初始化库存的时候去调用这个函数获取初始化库存

初始化库存回调函数(IStockCallback )

/**
 * 获取库存回调
 * @author yuhao.wang
 */

public  interface IStockCallback {

  /**
  * 获取库存
  * @return
  */

  int getStock();
}

扣减库存服务(StockService)

/**
 * 扣库存
 *
 * @author yuhao.wang
 */

@Service
public  class StockService {
    Logger logger = LoggerFactory.getLogger(StockService .class);

     /**
     * 不限库存
     */

     public  static  final  long UNINITIALIZED_STOCK = - 3L;

     /**
     * Redis 客户端
     */

     @Autowired
&nbsp;&nbsp;&nbsp;&nbsp; private&nbsp;RedisTemplate<String,&nbsp;Object>&nbsp;redisTemplate;

&nbsp;&nbsp;&nbsp;&nbsp; /**
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;执行扣库存的脚本
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/

&nbsp;&nbsp;&nbsp;&nbsp; public&nbsp; static&nbsp; final&nbsp;String&nbsp;STOCK_LUA;

&nbsp;&nbsp;&nbsp;&nbsp; static&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /**
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;@desc&nbsp;扣减库存Lua脚本
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;库存(stock)-1:表示不限库存
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;库存(stock)0:表示没有库存
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;库存(stock)大于0:表示剩余库存
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;@params&nbsp;库存key
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;@return
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;&nbsp;&nbsp;-3:库存未初始化
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;&nbsp;&nbsp;-2:库存不足
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;&nbsp;&nbsp;-1:不限库存
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;&nbsp;&nbsp;大于等于0:剩余库存(扣减之后剩余的库存)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;redis缓存的库存(value)是-1表示不限库存,直接返回1
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/

&
Spring Boot整合Redisson实现库存扣减防止超卖,通常会涉及到使用Redisson提供的分布式锁以及KeySet监听库存变动。这里是一个简化的示例代码: 首先,在pom.xml文件中添加Redisson的依赖: ```xml <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>4.x.x</version> </dependency> ``` 然后创建一个RedisService类: ```java import org.redisson.RedisRunner; import org.redisson.RedissonClient; import org.redisson.api.RLock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class RedisService { private static final String INVENTORY_KEY = "inventory_key_30"; private static final int REDIS_LOCK_TIMEOUT = 5000; // 5秒 @Autowired private RedissonClient redissonClient; public void reduceStock(int quantity) { try (RLock lock = redissonClient.lock(INVENTORY_KEY)) { if (lock.tryLock(REDIS_LOCK_TIMEOUT, TimeUnit.MILLISECONDS)) { try { Long currentStock = redissonClient.getAtomicLong(INVENTORY_KEY).get(); if (currentStock >= quantity) { redissonClient.getAtomicLong(INVENTORY_KEY).decrementAndGet(); } else { throw new InsufficientStockException("库存不足"); } } finally { lock.unlock(); } } else { throw new LockAcquireTimeoutException("获取锁失败"); } } catch (Exception e) { log.error("扣减库存异常", e); } } } ``` 在这个例子中,我们假设有一个名为`INVENTORY_KEY`的键用于存储库存值,并使用分布式锁防止并发操作导致的超卖。如果库存足够,就执行扣减操作;否则抛出异常。 最后,记得在Spring配置中启动Redisson服务: ```java @Configuration @EnableConfigurationProperties(RedissonProperties.class) public class RedissonConfig { @Bean public RedissonClient redissonClient(RedissonProperties properties) { return RedisRunner.create().config(properties.toConfig()).start(); } } ``` 这只是一个基础的示例,实际应用中还需要处理更复杂的场景,比如使用线程池、异常处理、事务等。同时,库存扣减后可能会触发事件通知,可以考虑使用Redisson的发布订阅功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值