问题
1,感觉事物复杂,直接订单中心创建订单,然后发送mq扣减库存。会存在什么问题,执行发送mq扣减存在,不能说明订单一定执行成功了吗,为什么还要检查本地事物
简化方案的问题分析与解决方案
您提出的简化方案("直接创建订单然后发MQ扣减库存")确实更简单,但存在一些关键问题需要解决。下面我会分析问题原因并提供改进建议。
一、简化方案的问题分析
1. 直接方案代码示例
public String createOrder(OrderDTO orderDTO) { // 1. 创建订单 orderMapper.createOrder(orderDTO); // 2. 发送扣减库存消息 mqProducer.send( "stock_deduction_topic", JSON.toJSONString(orderDTO) ); return "订单创建成功"; }
2. 存在的问题
(1) 消息发送与订单创建不一致
场景1:订单创建成功 → MQ发送失败 → 库存未扣减但订单已存在(多卖)
场景2:订单创建失败 → MQ发送成功 → 库存被扣减但无订单(少卖)
(2) 缺乏事务保证
数据库操作和MQ发送不是原子操作
系统崩溃可能导致中间状态不一致
(3) 无法处理库存不足
发送消息时不知道库存是否充足
需要额外的回滚机制
二、为什么需要检查本地事务
1. 核心原因:操作非原子性
数据库操作和MQ发送是两个独立操作,无法保证同时成功或失败
2. 典型故障场景:
时间轴: 1. 开始事务 2. 插入订单记录 3. 提交事务 → 成功 4. 发送MQ → 服务器突然宕机(未成功) 结果:订单存在但库存未扣减
3. 事务消息的价值:
半消息机制:先确认MQ可送达,再执行本地事务
事务检查:解决网络分区等极端情况下的状态不一致
三、折中的改进方案
方案1:本地消息表(简化版)
@Transactional public String createOrder(OrderDTO orderDTO) { // 1. 创建订单(状态为"待处理") orderMapper.createOrder(orderDTO); // 2. 写入本地消息表(同库事务保证) localMessageService.saveMessage( "stock_deduction", orderDTO.getOrderId(), JSON.toJSONString(orderDTO) ); return "订单创建中"; } // 定时任务扫描本地消息表并发送MQ @Scheduled(fixedRate = 5000) public void sendPendingMessages() { List<LocalMessage> messages = localMessageService.getPendingMessages(); messages.forEach(msg -> { try { mqProducer.send(msg.getTopic(), msg.getContent()); localMessageService.markAsSent(msg.getId()); } catch (Exception e) { log.error("消息发送失败", e); } }); }
优点:
利用数据库事务保证订单和消息的一致性
实现相对简单
无需引入复杂的事务消息
缺点:
消息有短暂延迟
需要额外维护消息表
方案2:异步确认+状态补偿
public String createOrder(OrderDTO orderDTO) { // 1. 创建临时订单(状态为"初始化") orderMapper.createTemporaryOrder(orderDTO); // 2. 发送MQ(普通消息) mqProducer.send("stock_deduction", JSON.toJSONString(orderDTO)); return "订单处理中"; } // 库存服务处理完成后回调 public void confirmOrder(String orderId) { orderMapper.updateStatus(orderId, "CONFIRMED"); } // 定时补偿未完成订单 @Scheduled(fixedRate = 60000) public void compensatePendingOrders() { List<Order> pendingOrders = orderMapper.getPendingOrders(); pendingOrders.forEach(order -> { // 重新发送或取消订单 }); }
四、方案选择建议
方案 | 复杂度 | 一致性 | 适用场景 |
---|---|---|---|
完整事务消息 | 高 | 强 | 金融、支付等强一致性场景 |
本地消息表 | 中 | 最终 | 大多数电商订单场景 |
异步确认 | 低 | 最终 | 可容忍短暂不一致的场景 |
五、关键结论
简单发MQ方案的主要风险是"订单与库存状态不一致"
检查本地事务是为了解决分布式系统下的原子性问题
折中方案可以在保证可靠性的同时降低复杂度
最终选择应基于业务对一致性的要求
如果您的业务可以接受秒级的最终一致性,本地消息表方案是最佳平衡点。对于普通电商场景,这种方案已经能很好地满足需求,同时避免了复杂的事务消息实现。