一、问题描述
商品库存为100个,但用户数量有1000或者更多。 每个用户购买的数量不一样,所以得确保库存既不能多发,也不能少发。
不多发,即不会出现库存已经卖完了,但还是有用户能够购买。 不少发,即避免库存数量大于实际商品数量的情况发生。
二、下单步骤
下单:用户选中商品,提交订单。 预占库存:系统在用户下单时,就会预占库存,以确保库存不会在支付过程中被其他用户抢走。 支付:用户完成支付,系统确认支付成功。 减扣库存:支付成功后,系统才会真正减库存。 取消订单:用户若取消订单,库存需要回退。 回退预占库存:若订单未支付,库存需要及时回退。
三、预占库存的时机
1. 方案一:加入购物车时预占库存
2. 方案二:下单时预占库存
3. 方案三:支付时预占库存
方案二,即下单时预占库存,是最合适的选择。这时用户已经有了明确的购买意图,且下单后订单会有时效性,超时未支付的订单会被取消,库存得到回退。
四、重复下单问题
用户点击过快,重复提交请求。 网络延时,用户刷新页面或重复点击。 网络框架重复请求。 用户恶意行为,故意发起重复请求。
UI拦截:比如在提交订单后,把按钮设置为置灰,避免用户重复点击。 Token校验:每次提交订单时生成一个唯一的Token,防止重复提交。可以通过Redis来存储Token,利用自增功能来判断是否已经使用过。
// 创建Token
String token = UUID.randomUUID().toString();
redisTemplate.opsForValue().set("order_token_" + token, token);
// 校验Token有效性
String storedToken = redisTemplate.opsForValue().get("order_token_" + token);
if(storedToken == null || !storedToken.equals(token)) {
// Token无效,重复提交
throw new IllegalStateException("订单已提交,不能重复提交");
}
五、如何安全减扣库存
1. 方法1:不安全的库存操作
UPDATE
语句来更新库存:UPDATE product SET stock = stock - 1 WHERE product_id = 100 AND stock > 0;
2. 方法2:使用乐观锁
UPDATE product SET stock = stock - 1, version = version + 1
WHERE product_id = 100 AND stock > 0 AND version = 1;
3. 方法3:使用Redis分布式锁
SETNX
来尝试获取锁,获取成功的请求才能进行库存操作。boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent("lock:product:100", "locked");
if (lockAcquired) {
// 执行库存更新操作
redisTemplate.opsForValue().increment("product:100:stock", -1);
}
4. 方法4:使用Redis存储库存信息
INCRBY
原子操作来确保库存的安全:long stock = redisTemplate.opsForValue().decrement("product:100:stock", 1);
if (stock < 0) {
// 库存不足,回滚操作
redisTemplate.opsForValue().increment("product:100:stock", 1);
throw new IllegalStateException("库存不足");
}
六、订单时效与库存回退
// 订单超时未支付
@Scheduled(fixedDelay = 60000)
public void checkOrderTimeout() {
List<Order> orders = orderRepository.findTimeoutOrders();
for (Order order : orders) {
// 取消订单并回退库存
cancelOrderAndRestoreStock(order);
}
}
结论
-END-
以上,就是今天的分享了,看完文章记得右下角给何老师点赞,也欢迎在评论区写下你的留言。