柔性事务:Saga、TCC 与事务性发件箱

05-事务与数据系统 核心 约 25 分钟 #Saga#TCC#事务性发件箱#最终一致 更新 2026-10-02
当前状态:未学
本文基于模型知识整理(生成时未联网核对),关键结论建议对照经典文献复核。

一句话定义

柔性事务放弃跨服务的强原子性,改用"本地事务 + 补偿/确认"把长流程拆成一串可回滚的本地事务:Saga 用逆序补偿、TCC 用预留-确认-取消、事务性发件箱用同事务写消息解决"改库与发消息不一致",共同换取高可用与低锁成本。

为什么重要

微服务架构下每个服务各有数据库,XA(kp-023)因阻塞与吞吐问题普遍被弃用;柔性事务是跨服务一致性的主流答案。但它的"最终一致"不是免费的:补偿要自己写、隔离性要自己管(脏读/丢失更新风险)、幂等与去重要自己兜底(kp-029)。

前置知识

kp-023(2PC 为何不适用)、kp-008(消息队列语义)。

核心概念

  • Saga:长事务 T = T1…Tn,每个 Ti 配补偿 Ci;失败时逆序执行 C(n-1)…Ci+1 回滚已完成部分。两种协调方式:

- 编排式(choreography):服务间靠事件驱动,各自订阅触发下一步——松耦合,但流程分散难追踪; - 协同式(orchestration):中央协调器(状态机)驱动步骤——流程集中可视,但协调器是单点逻辑(不是单点数据)。

  • TCC(Try-Confirm/Cancel):Try 预留资源(冻结金额、预占库存),Confirm 确认扣减,Cancel 释放预留。把"锁资源"显式业务化,隔离性更好,但每个参与方要写三个接口,且必须幂等、允许空回滚(Cancel 先于 Try 到达)。
  • 事务性发件箱(transactional outbox):业务数据与"待发消息"在同一本地事务中写入 outbox 表,后台轮询/CDC 把消息投递到 MQ——解决"库改了消息没发 / 消息发了库没改"的双写不一致。
  • BASE:基本可用(Basically Available)、软状态(Soft state)、最终一致(Eventual consistency)——柔性事务的总纲。

原理与机制

Saga 的隔离性缺口:Saga 中间态对外可见(T1 提交、T2 未执行),其他事务可能读到"半成品"(脏读)或在其上并发修改导致补偿后矛盾(丢失更新)。对策:语义锁(业务状态字段标记"处理中")、交换律补偿设计、或对关键步骤用 TCC 预留隔离。

补偿 ≠ 回滚:数据库回滚是"从没发生过";Saga 补偿是"发生过再撤销"(退款、解冻、释放库存)。因此补偿必须幂等、可重试、可空跑(补偿时业务可能已自己回滚),且不可补偿的步骤(发短信)只能放在最后或接受副作用。

发件箱 + 幂等消费组合:outbox 保证"不丢",消费端幂等(去重表/唯一键)保证"不重"——两个模式拼起来才是完整的至少一次正确链路(与 kp-008 投递语义、kp-029 幂等工程闭环)。

图示

Saga(协同式):
 订单服务 ──创建订单──► 事件 ──► 库存服务扣减 ──► 事件 ──► 支付服务扣款
                                        ✗ 扣款失败
              ◄──逆序补偿: 恢复库存 + 取消订单◄──
TCC:
 Try: 冻结账户100元(可用余额-100, 冻结+100)
 Confirm: 冻结-100, 已扣-100   /  Cancel: 冻结-100(释放)
Outbox:
 [本地事务] 业务表 + outbox(id,payload) 同事务提交 → 轮询投递MQ → 消费幂等

实例或案例

  • 电商下单:订单(本地事务) → outbox 投递 → 库存/TCC 预占 → 支付;失败走取消链路。
  • Seata(阿里开源):AT(自动补偿)/TCC/Saga/XA 四模式,是国内柔性事务落地最广的框架。
  • 跨行转账:TCC 冻结-确认模式是银行系统标准打法;补偿入账为冲正交易。

常见误区

  • 误区一:"柔性事务没有一致性问题"。它把原子性问题换成了补偿正确性 + 隔离性管理问题,复杂度没有消失只是换了形态。
  • 误区二:"补偿逻辑可以后补"。补偿与正向逻辑同等重要,缺失补偿测试是柔性事务事故的头号来源。
  • 误区三:"发件箱投完消息就删"。必须确认 MQ 成功接收(ACK)后才能标记,否则窗口内消息丢失;且 outbox 要有归档策略防表膨胀。

与其他知识点的关系

  • kp-008/029:投递语义与幂等是柔性事务的运行时底座。
  • kp-023:XA 是强原子性对照物,理解它才知道柔性事务放弃了什么。
  • kp-032:真实系统(如支付宝单元化)的柔性事务落地形态。

自测题

  1. Saga 与 2PC 的根本取舍?

答:2PC 阻塞持锁换强原子性;Saga 无全局锁、可用性高,但中间态可见(弱隔离)且需自写补偿,一致性为最终一致。

  1. TCC 的"空回滚"是什么问题?

答:Try 因网络延迟未到达,Cancel 先被执行;Cancel 必须能识别"无预留可释放"并幂等成功,否则悬挂或报错阻断流程。

  1. 事务性发件箱解决哪个双写问题?

答:业务落库与消息发送跨两个系统无法原子;把消息写进同库同事务,再异步投递,保证两者不脱节。

延伸阅读

  • Hector Garcia-Molina & Salem, "Sagas"(SIGMOD 1987)。
  • Chris Richardson《Microservices Patterns》(Saga/outbox 模式专著)。
  • Chris Richardson, "PATTERN: Transactional outbox"(microservices.io)。