🔥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 后端翻车实录 真实线上事故复盘 性能优化 & 架构设计实战
📌 扫码关注👇