redis库存预减
项目实际方案设计视图如下:

1.方案介绍
在电商秒杀系统中,库存预减是一种优化策略,用于提高在高并发环境下库存处理的效率和准确性。传统的库存处理方式是在用户下单后,立即从数据库中扣减库存。 然而,在高并发场景下,这种方式可能会导致超卖(即库存已售罄但仍有用户下单成功)或库存不一致的问题。 库存预减策略则是在用户发起秒杀请求时,先尝试在内存中或缓存(如Redis)中预减库存,成功后再进行数据库层面的操作。
2.实现细节
项目中的原始的数据库操作(如直接在代码中执行 seckillGoods.setStockCount(seckillGoods.getStockCount() - 1);
后调用 seckillGoodsService.updateById(seckillGoods);
)不具有原子性, 因为它包含了两个步骤:读取库存和更新库存。在高并发的环境下,这种操作方式可能会导致超卖问题。
Redis库存预减操作是一个关键步骤,用于在高并发环境下避免超卖问题。Redis的decrement
操作提供了原子性,确保了在多个用户 同时尝试减少库存时,库存数量能够正确地逐一递减,而不是出现负数或不一致的情况。
- 使用Redis的
decrement
操作:这是Redis提供的一个原子性操作,用于将存储在键中的数值减一。如果键不存在,它会先将其值初始化为0,然后再执行减一操作。这个特性非常适合用于库存的预减。 - 检查减一后的结果:执行
decrement
操作后,需要检查结果是否小于0。如果小于0,说明在多个请求并发执行时,库存已经被减到0以下了,这时需要恢复库存(虽然在实际操作中,这一步可能是不必要的,因为通常不会允许库存变为负数,但这里为了完整性而提及)。 - 处理并发问题:由于
decrement
是原子性的,因此它自然解决了并发下的库存超减问题。但是,我们还需要在业务逻辑层面确保不会重复抢购(如上例中的Redis订单缓存检查)。
3.代码示例
- 商品存入Redis的代码
// 假设list是一个包含商品信息的List<GoodsVo>
List<GoodsVo> list = // 初始化或获取商品列表
// 遍历商品列表,将每个商品及其库存数量存入Redis
list.forEach(goodsVo -> {
// 构造键,格式为 "seckillGoods:goodsid"
String key = "seckillGoods:" + goodsVo.getId();
// 使用RedisTemplate的opsForValue().set方法设置键值对
// 其中value为商品的库存数量,这里直接调用goodsVo.getStockCount()获取
redisTemplate.opsForValue().set(key, String.valueOf(goodsVo.getStockCount()));
});
注意:set
方法的第二个参数是String
类型,因此如果getStockCount()
返回的是非字符串类型(如Integer
),则需要将其转换为String
。
- redis库存预减操作
// 假设goodsId是我们要减少库存的商品ID
long goodsId = // 获取商品ID
// 使用Redis的DECR操作进行库存预减
// 如果键不存在,DECR会将其值设置为0然后再减1
Long stockAfterDecrement = redisTemplate.opsForValue().decrement("seckillGoods:" + goodsId);
// 检查预减后的库存数量
if (stockAfterDecrement < 0) {
// 理论上,由于我们是在确认库存足够后才进行预减的,这里不应该发生
// 但如果发生了(可能是由于并发或之前的逻辑错误),我们可以选择记录日志、发送报警等
// 在这里,我们简单地将库存恢复为0(或根据实际情况决定)
redisTemplate.opsForValue().set("seckillGoods:" + goodsId, "0");
// 设置错误信息,可能用于返回给前端或记录日志
String errorMessage = "库存异常,已恢复为0";
// 根据需要处理错误,比如返回错误信息给前端
// return new ResponseEntity<>(errorMessage, HttpStatus.INTERNAL_SERVER_ERROR);
} else if (stockAfterDecrement == 0) {
// 库存已减为0,表示商品已售罄
// 可以进行后续操作,如记录销售信息、更新数据库等
// ...
}
// 如果库存大于0,则继续后续的抢购逻辑...
// ...
预减操作总结
- 准备键:首先,需要准备一个Redis键,该键与商品的ID相关联,用于存储该商品的库存数量。
- 执行预减:使用Redis的
DECR
命令对库存数量进行原子性减少。这个操作会返回减少后的库存数量。 - 检查结果:检查
DECR
操作后的结果。如果结果小于0,说明在并发情况下出现了库存超减的问题(尽管在正常情况下,这应该是通过先检查库存再减少的逻辑来避免的)。如果库存为0,则表示商品已售罄。 - 处理异常:如果库存减到了负数,根据业务需求,可能需要记录日志、发送报警,或者将库存重置为0(尽管这通常不是最佳做法,因为它可能会掩盖潜在的并发问题)。
- 继续后续逻辑:如果库存减少后仍然大于0,则可以继续执行后续的抢购逻辑,如创建订单、更新数据库等。