mq事物消息

发布于:2025-07-05 ⋅ 阅读:(20) ⋅ 点赞:(0)

问题

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 -> {
        // 重新发送或取消订单
    });
}

四、方案选择建议

方案 复杂度 一致性 适用场景
完整事务消息 金融、支付等强一致性场景
本地消息表 最终 大多数电商订单场景
异步确认 最终 可容忍短暂不一致的场景

五、关键结论

  1. 简单发MQ方案的主要风险是"订单与库存状态不一致"

  2. 检查本地事务是为了解决分布式系统下的原子性问题

  3. 折中方案可以在保证可靠性的同时降低复杂度

  4. 最终选择应基于业务对一致性的要求

如果您的业务可以接受秒级的最终一致性,本地消息表方案是最佳平衡点。对于普通电商场景,这种方案已经能很好地满足需求,同时避免了复杂的事务消息实现。