直接回答:单体应用靠数据库事务保证一致性,拆成微服务后每个服务有自己的库,事务跨不过去。Saga 的思路是不追求"全部成功或全部回滚",而是每做完一步就记录本地事务,失败时按相反顺序执行补偿操作,把强一致换成了最终一致。它适合业务流程长、参与者多的场景,但前提是每一步都有可逆的补偿动作。
单体时代不用想这个问题
举一个订票的例子:客户下一单,需要同时订机票、订车、订酒店,三个都要成功才算成行。
单体应用里这事特别简单——把三个写操作放进同一个数据库事务,数据库保证要么全部提交,要么全部回滚。我们连"中间状态"都不用考虑。
拆成微服务之后,问题出现在哪
三个服务各自有自己的数据库,就出现了两个绕不过去的现实:
- 事务边界跨不过服务,本地事务只能管自己那一个库;
- 分布式事务的协调成本很高,而且一旦参与方变多,失败路径的组合会迅速爆炸。
业界主流的四条路,成本从高到低大致是这样:
| 方案 | 思路 | 代价 |
|---|---|---|
| 2PC(两阶段提交) | 协调者先让所有参与者"准备",全部就绪再统一提交 | 阻塞、协调者单点、性能差,参与方多时几乎不可用 |
| TCC | 每个服务实现 Try / Confirm / Cancel 三个接口 | 需要为每个业务单独写补偿逻辑,侵入性强,但一致性强 |
| Saga | 正向流程 + 反向补偿,允许中间状态对外可见 | 需要保证幂等和补偿可行,适合长流程 |
| 本地消息表 / 事务消息 | 本地事务里同时写业务数据和消息,异步投递 | 实现简单,用于服务间的最终一致,不适合"必须立刻成功"的场景 |
Saga 的基本形态
Saga 把一个分布式事务拆成若干个本地事务,每个本地事务都对应一个补偿操作:
1 | T1 → T2 → T3 → ... → Tn |
比如下单流程是"扣库存 → 扣款 → 生成订单",任何一步失败,就反向执行已成功步骤的补偿动作:退款、回补库存。
Saga 有两种执行方式:
- 编排式(Orchestration):有一个中心协调者按顺序调用各服务,逻辑集中、容易看懂,但协调者要单独维护;
- 协作式(Choreography):各服务监听彼此的事件,谁也不需要中心,耦合低,但流程散在多个服务里,出问题时不好追。
补偿不是"回滚",这两点一定要分清
- 补偿是业务动作,不是数据库回滚。 已经发出去的短信、已经打包出库的货,物理上收不回来,只能用一个反向的业务动作去抵消。
- 中间状态对外可见。 因为每一步提交后就是真的提交了,其他查询可能看到"钱扣了但订单还没建"的时刻。业务上要么能容忍,要么用状态字段把它标出来(比如"处理中")。
用 Saga 必须满足的三个前提
- 补偿可行:每一步都有对应的反向操作,且反向操作不会引入新的不一致(比如补偿本身也可能失败,就需要重试直到成功);
- 幂等:正向操作和补偿操作都可能被重复执行,服务必须靠业务唯一键、去重表或状态机把重复请求吃掉;
- 可追踪:每一步的正向和补偿都要落库记录(Saga Log),进程崩了重启之后能接着往下走,而不是从头再来。
这三条里,幂等是最容易被忽略、也最容易出事的——超时重试是常态,没有幂等保护,重试一次就多扣一次钱。
什么时候不该用 Saga
- 流程很短、参与方只有两三个:用本地消息表加重试往往更简单;
- 中间状态绝对不能被看到(比如金融账务):该用 TCC 或者干脆别拆服务;
- 团队还没有统一的重试、幂等规范:先补这一课,再谈分布式事务框架。
一个务实的经验:优先努力把需要跨服务事务的业务收敛到同一个服务里。很多"分布式事务难题"其实是服务边界切错了造成的,改边界比引入框架划算得多。
小结
- 单体靠数据库事务,微服务没有这个便利,只能选一种折中;
- Saga 用正向流程加补偿换取可用性,把强一致换成最终一致;
- 配套必须做齐:补偿动作、幂等、日志与重试;
- 选型顺序建议是:能合并服务就合并 → 不能就用本地消息表 → 流程确实长再上 Saga → 中间状态绝对不能见人才考虑 TCC。
本文整理自我自己早先记录的一份分布式事务资料(内容源自华为姜宁在又拍云 Open Talk 上的分享《微服务场景下的数据一致性解决方案》),重新梳理成对比表并补上了实践中的判断依据,文字由 AI 协助改写后经我复核。
这篇笔记整理自我自己的实践记录,如果做法有出入,或者你踩过别的坑,欢迎到留言板一起聊聊。