先喝口水,再看一眼分布式系统,然后你会发现:没有事务,心里没底;有了事务,系统要命。
作为一名写了很多年 Java 的老兵,今天我们来聊一个在微服务世界里既不完美、但很实用的方案——Saga 分布式事务模式。
一、为什么需要 Saga?(问题从哪来)
在单体应用里,事务是这样的:
BEGIN A 表扣钱 B 表加库存 C 表创建订单 COMMIT / ROLLBACK一切都很美,直到:
- 服务被拆成了微服务
- 数据库被拆成了多个库
- 本地事务失效了
你会发现:
分布式系统里,没有一个“全局数据库事务”在等你。
传统方案的问题
2PC / XA:
- 强一致
- 强依赖
- 强性能杀手
于是我们开始思考:
能不能不要强一致,但系统还能跑?
答案就是:Saga。
二、Saga 是什么?(一句话版本)
Saga = 一连串本地事务 + 对应的补偿事务
换句话说:
- 每一步都先提交
- 如果后面失败了
- 就按相反顺序补偿回去
听起来是不是有点像:
“做错了事,靠后悔药解决。”
没错,但这药在分布式系统里,很值钱。
三、Saga 的核心思想(重要但不复杂)
1️⃣ 本地事务优先
每个服务:
- 只操作自己的数据库
- 使用本地事务(Spring @Transactional 那套)
2️⃣ 失败 ≠ 回滚
Saga 的世界里:
- 失败不是 rollback
- 而是:
执行补偿操作(Compensation Action)
3️⃣ 最终一致性
- 中间状态可能不一致
- 但最终能“对上账”
一句话总结:
Saga 不保证你时时开心,但保证你最后不崩。
四、Saga 的两种经典实现方式
方式一:编排式 Saga(Orchestration)⭐ 推荐
有一个指挥官,统一调度流程。
优点:
- 流程清晰
- 易于监控
- 逻辑集中
缺点:
- 指挥官可能变胖
但对 Java 后端来说,这个“胖”是可以接受的。
方式二:编舞式 Saga(Choreography)
没有指挥官,全靠服务自己“看消息办事”。
优点:
- 去中心化
缺点:
- 链路难追踪
- Debug 靠信仰
一旦系统大了,没人知道谁在跳哪支舞。
五、一个下单场景的 Saga 示例(Java 思路)
正向流程
- 创建订单(Order Service)
- 扣减余额(Payment Service)
- 扣减库存(Inventory Service)
补偿流程
- 库存失败 → 退款 → 取消订单
伪代码示例(编排式)
publicvoidcreateOrderSaga(CreateOrderCmdcmd){try{orderService.create(cmd);paymentService.pay(cmd);inventoryService.deduct(cmd);}catch(Exceptione){inventoryService.compensate(cmd);paymentService.refund(cmd);orderService.cancel(cmd);throwe;}}看起来很普通,对吧?
但它解决的是分布式事务这个“世纪难题”。
六、Saga 的几个关键设计点(血泪经验)
1️⃣ 补偿必须是幂等的
- 可能被调用多次
- 不能多退钱、多加库存
幂等不是优化,是底线。
2️⃣ 补偿 ≠ 完全回滚
- 有些操作不可逆(比如发短信)
- 补偿只能“业务上对等”
分布式系统里,
时间不能倒流,只能对冲。
3️⃣ 一定要有状态表
- Saga 实例状态
- 当前执行到哪一步
- 是否已补偿
否则:
系统一重启,你会失忆。
七、Saga vs 2PC(快速对比)
| 维度 | Saga | 2PC |
|---|---|---|
| 一致性 | 最终一致 | 强一致 |
| 性能 | 高 | 低 |
| 实现复杂度 | 中 | 高 |
| 可用性 | 高 | 低 |
| 微服务友好度 | ⭐⭐⭐⭐⭐ | ⭐ |
结论一句话:
微服务时代,用 Saga;银行核心,用 2PC。
八、Java 技术栈中的 Saga 实践
常见组合:
- Spring Boot
- Spring Transaction
- 消息队列(Kafka / RocketMQ)
- 状态表 + 定时补偿
进阶框架:
- Axon
- Eventuate Tram
- 自研 Saga Coordinator(很多公司最终都会走这条路)