在高并发秒杀、抢购等场景下,订单系统极易出现"超卖"问题,即实际售出的商品数量超过库存。本文将详细介绍超卖的成因、常见解决方案,重点讲解如何通过Redis实现高效、可靠的防超卖机制,并给出实用代码和优化建议。
超卖指的是在并发环境下,多个用户同时下单,导致实际售出商品数量超过库存。例如,库存只有10件,但最终卖出了12件。
其中,基于Redis的方案因高性能、易扩展、支持分布式而被广泛采用。
// 初始化库存
redisTemplate.opsForValue().set("product_stock:1001", 10);
// 下单接口伪代码
public String placeOrder(Long productId) {
Long stock = redisTemplate.opsForValue().decrement("product_stock:" + productId);
if (stock < 0) {
// 库存不足,回滚
redisTemplate.opsForValue().increment("product_stock:" + productId);
return "库存不足,抢购失败";
}
// 生成订单,写入数据库
// ...
return "下单成功";
}
优点:操作简单,性能高。
缺点:存在并发下"库存回滚"不及时、订单与库存不一致等问题。
为保证订单和库存操作的一致性,可引入Redis分布式锁:
public String placeOrderWithLock(Long productId) {
String lockKey = "lock:product:" + productId;
String clientId = UUID.randomUUID().toString();
Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 5, TimeUnit.SECONDS);
if (!locked) {
return "系统繁忙,请稍后重试";
}
try {
Long stock = redisTemplate.opsForValue().get("product_stock:" + productId);
if (stock == null || stock <= 0) {
return "库存不足";
}
redisTemplate.opsForValue().decrement("product_stock:" + productId);
// 生成订单
// ...
return "下单成功";
} finally {
// 释放锁(需确保只释放自己加的锁)
if (clientId.equals(redisTemplate.opsForValue().get(lockKey))) {
redisTemplate.delete(lockKey);
}
}
}
注意:分布式锁要保证"加锁、业务、解锁"三步的原子性,推荐使用Redisson等成熟组件。
利用Redis的Lua脚本,可以将库存判断和扣减、订单写入等操作合并为原子操作:
// Lua脚本内容
String luaScript = """
local stock = redis.call('get', KEYS[1])
if (tonumber(stock) <= 0) then
return -1
end
redis.call('decr', KEYS[1])
return 1
""";
// Java调用
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);
Long result = redisTemplate.execute(redisScript, Collections.singletonList("product_stock:1001"));
if (result == -1) {
return "库存不足";
} else {
// 生成订单
// ...
return "下单成功";
}
优点:彻底避免并发下的库存超卖和回滚问题。
@RestController
public class SeckillController {
@Autowired
private StringRedisTemplate redisTemplate;
@PostMapping("/seckill")
public String seckill(@RequestParam Long productId) {
String luaScript = """
local stock = redis.call('get', KEYS[1])
if (tonumber(stock) <= 0) then
return -1
end
redis.call('decr', KEYS[1])
return 1
""";
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);
Long result = redisTemplate.execute(redisScript, Collections.singletonList("product_stock:" + productId));
if (result == -1) {
return "库存不足";
}
// 发送MQ消息异步创建订单
// ...
return "抢购成功,订单生成中";
}
}
通过Redis的原子操作、分布式锁和Lua脚本,可以高效、可靠地实现订单防超卖。实际生产中建议结合消息队列、幂等校验、补偿机制等手段,进一步提升系统的健壮性和可扩展性。
本文系统介绍了超卖问题的成因、Redis防超卖的多种实现方式及优化建议,适合电商、秒杀等高并发场景下的开发者参考。