RabbitMQ 消费异常:库存不足无法释放的定位与解决
在实际的电商项目中,订单取消后往往需要 释放商品库存,以便其他用户能够正常下单。但最近在调试 mall-portal 项目时,遇到了一次 RabbitMQ 消费异常,报错信息如下:
看起来是调用 订单取消逻辑 时,库存释放出现了问题。
问题定位
通过日志堆栈可以发现异常来自:
OmsPortalOrderServiceImpl.cancelOrder()
定位到代码:
//解除订单商品库存锁定
if (!CollectionUtils.isEmpty(orderItemList)) {for (OmsOrderItem orderItem : orderItemList) {int count = portalOrderDao.releaseStockBySkuId(orderItem.getProductSkuId(), orderItem.getProductQuantity());if(count==0){Asserts.fail("库存不足,无法释放!");}}
}
这里的逻辑是:
- 遍历订单商品。
- 调用 portalOrderDao.releaseStockBySkuId() 释放库存。
- 如果返回 count==0,直接抛出异常 库存不足,无法释放!。
问题就在这里:
订单取消可能被 RabbitMQ 重复消费(例如超时队列触发),当库存已经释放过一次,再次调用时 count==0,就会抛异常。
问题分析
这种场景其实并不是业务错误,而是 重复释放库存。
按照健壮性原则,应该记录一次警告日志,而不是抛出业务异常。否则,RabbitMQ 消费端会报错,消息堆积,影响后续逻辑。
解决方案
我们不再使用 Asserts.fail() 抛出异常,而是改成 日志告警 + 跳过,让系统能继续运行。
修改前:
if(count==0){Asserts.fail("库存不足,无法释放!");
}
修改后:
if (count == 0) {log.warn("订单 {} 的商品 SKU {} 库存释放失败,可能已释放过或不存在锁定记录",orderId, orderItem.getProductSkuId());continue;
}
这样做的好处:
- 避免了 RabbitMQ 消费端抛异常。
- 出现问题时仍然会有日志记录,方便排查。
- 系统能正确处理重复消费场景,保证幂等性。
总结
这次问题的根本原因是:
- 消息队列触发了重复消费。
- 代码缺乏 幂等性设计,直接抛异常。
最终通过 日志警告代替异常 的方式解决了问题。
👉 在分布式系统中,幂等性 是非常关键的设计点。任何可能被 重复执行 的操作(如库存释放、积分返还、优惠券回滚),都应该具备幂等保证,否则很容易出错。
🚀 后续可以考虑在 releaseStockBySkuId 方法里直接做幂等控制,这样调用方就不需要关心重复释放问题。