如果早点知道这个坑,我可能少熬 3 次夜

17 阅读2分钟

🔥java 后端翻车实录 · 第 01 篇

事故现场

凌晨 1:47,监控告警把我吵醒。

订单状态全部卡在 「处理中」, 库存已经扣了, 但用户页面显示:下单失败

客服、运营、产品,三方同时 @ 我。


当时的我

“不可能是事务问题。”

我记得很清楚,这段代码我写得很自信

  • 加了 @Transactional
  • 没有手动 catch 异常
  • 测试环境压测通过

我甚至在群里说了一句:

“等我 5 分钟,应该是 MQ 抖了一下。”

现在想想,这句话非常危险。


关键代码

@Transactional
public void createOrder(OrderDTO dto) {
    orderMapper.insert(dto);
    asyncService.sendOrderMsg(dto);
}

sendOrderMsg 是这样的:

@Async
public void sendOrderMsg(OrderDTO dto) {
    mqTemplate.send(dto);
}

技术真相

@Transactional 管不到 @Async。

  • 事务还没提交
  • 异步线程已经发了消息
  • 消费端查不到订单 → 业务失败

于是出现了最恶心的结果:

数据写了一半,世界却以为你失败了


为什么当时没发现?

因为:

  • 测试环境并发低
  • MQ 延迟小
  • 本地事务提交很快

线上只是把问题提前放大了。


正确做法(记住这 3 条)

原则 1:事务边界只放在同步逻辑

@Transactional
public void createOrder(OrderDTO dto) {
    orderMapper.insert(dto);
}

原则 2:事务提交后再发消息

  • 事务消息
  • Outbox 表
  • 或 Spring 事务监听器
// 使用 Spring Events + @TransactionalEventListener
@TransactionalEventListener(phase = AFTER_COMMIT)
public void afterCommit(OrderCreatedEvent event) {
    mqTemplate.send(event);
}

原则 3:不要相信“看起来没问题”

线上环境,才是真实世界。

完整代码

// 业务入口
@Service
@RequiredArgsConstructor
public class OrderService {
​
    private final OrderMapper orderMapper;
    private final ApplicationEventPublisher eventPublisher;
​
    @Transactional
    public void createOrder(OrderDTO dto) {
        // 1. 落库
        Order order = new Order();
        order.setOrderId(dto.getOrderId());
        order.setStatus("INIT");
        orderMapper.insert(order);
​
        // 2. 发布“订单已创建”事件(注意:此时还没提交)
        eventPublisher.publishEvent(new OrderCreatedEvent(order.getOrderId()));
    }
}
// 事件对象
@Getter
@AllArgsConstructor
public class OrderCreatedEvent {
    private final String orderId;
}
​
// 事务提交后监听
@Component
@RequiredArgsConstructor
public class OrderCreatedEventListener {
​
    private final MqTemplate mqTemplate;
​
    /**
     * AFTER_COMMIT:事务提交成功后才会执行
     */
    @TransactionalEventListener(
        phase = TransactionPhase.AFTER_COMMIT
    )
    public void onOrderCreated(OrderCreatedEvent event) {
        mqTemplate.send("order-created-topic", event.getOrderId());
    }
}

📌 注意

有人看到这可能有一个疑问,eventPublisher.publishEvent(new OrderCreatedEvent(order.getOrderId()))这段代码是怎么找到OrderCreatedEventListener这个类的。

说明你不是“照抄代码”,而是在真正理解 Spring 的机制 👍,后面我会出一篇文章来讲解这个。


总结

你以为 Spring 在帮你兜底,其实它只是在旁边看着你犯错。

如果你觉得这篇文章对你有帮助, 我在公众号 「臻大虾」 持续分享: Java 后端翻车实录 真实线上事故复盘 性能优化 & 架构设计实战

📌 扫码关注👇

微信图片_20260203174758_155_96.jpg