在了解方案前先了解一下几个理论
理论
CAP
C consistency 一致性: 所有节点在同一时间访问时, 拿到的均为最新数据
A availability 可用性: 向非故障节点发起请求后, 每一次请求都能在有限时间内获得合理的响应(而非错误或超时)
P partition tolerate 分区容错性: 部分节点挂了, 依旧能正常对外提供服务
在分布式系统下,P是前提,仅能实现CP/AP
CP 一致性 + 分区容错: 当网络分区发生时,为了保证数据一致,系统会拒绝服务或报错,牺牲可用性(A)。适用于银行转账、交易系统
AP 可用性 + 分区容错: 当网络分区发生时,系统保证可用,但允许读取旧数据,牺牲强一致性(C),满足最终一致性。适用于大多数Web应用、社交媒体
CA 一致性 + 可用性: 仅可在单机环境下成立,常用数据库如mysql oracle等
BASE
BASE是下面几个单词的缩写, 此理论是互联网实践而来,是对CAP理论的演变和权衡结果.
Basically Available 基本可用: 在系统发生故障或高负载时, 必须保证系统核心功能可用, 非核心功能使用降级熔断等策略保证--基本可用
Soft-state 软状态: 允许系统存在短暂、无锁、可过渡的中间状态,节点间数据暂时不一致, 但不影响整体可用性.
与硬状态(ACID事务, 实时锁定)相对
Eventually Consistent 最终一致性: BASE理论强调最终一致性, 即在有限时间和有穷步后, 结果最终一致.
一致性的三个状态
一致性强调结果, 即数据最后能否一致
强一致性: 系统写了什么, 读出来就是什么
弱一致性: 不保证多久后读取数据是最新的, 甚至永远可能读不到最新值. 都不保证
最终一致性: 现在不一定一致, 但在有限时间后一定一致
幂等性
同一个操作执行多次, 结果相同
常见方案
XA 标准化分布式事务
这是对2PC 的标准化实现, 是X/Open 组织制定的、跨数据库/消息队列的分布式事务工业级标准协议. 流程参见2PC
支持XA 的有MySQL、Oracle、PostgreSQL、SQL Server、RocketMQ、Kafka 等几乎所有主流中间件
2PC 两阶段提交
借用XA 的角色定义: AP 业务应用(Application Program), TM 事务管理器(Transaction Manager), RM 参与者(Resource Manager)
- AP 向TM 发起全局事务
- TM向RM A, B发送要执行的SQl
- RM A, B开启事务, 执行业务SQL, 两个执行成功/失败均向TM 返回
- TM 根据返回决定向RM 发送commit或rollback
优点: 强一致, 开发简单. 适用于金融等系统
缺点: 性能差, 协调者节点挂了会导致锁无法释放(可用心跳包等方法解决), 极端情况下会导致一部分commit, 一部分rollback
3PC 三阶段提交
- CanCommit: 仅检查库存, 锁.
- PreCommit: 所有节点的Can 均返回成功时, 向所有节点发送PreCommit, 此步骤开启事务但不提交.
- DoCommit: 所有节点的PreCommit 均返回成功时,向所有节点发送DoCommit.
3PC 为所有节点增加了超时决策逻辑, 在PreCommit 后超时可选自动Abort/DoCommit, 在CanCommit 超时自动Abort.
极端情况下仍有可能导致协调者只给部分节点发送了DoCommit, 剩下节点可能Abort/DoCommit.
生产几乎不用
AT 自动事务模式 (Seata)
官方链接: https://seata.apache.org/zh-cn/docs/overview/what-is-seata 你可以阅读官方文档获取更详细的说明
三个角色
TM(Transaction Manager): 业务应用里的事务发起方
TC(Transaction Coordinator): Seata Server, 管理全局事务状态、协调各分支提交/回滚、维护全局锁
RM(Resource Manager): 由 Seata 代理数据源实现, 自动拦截 SQL、生成日志、执行回滚
一阶段执行流程
- 开启本地事务, 解析SQL 的操作(update等)、表(product),条件(where name = 'TXC')等相关的信息
- 查询前镜像:根据SQL定位到记录, 将记录保留一份
- 执行业务 SQL, 将执行后的结果当做后镜像: 根据前镜像的结果,通过 主键 定位数据
- 将前后镜像和业务SQL 组成一条回滚日志记录, 插入到UNDO_LOG 表中
- 向TC 申请指定表的指定主键的记录的全局锁
- 如果申请到全局锁, 本地事务提交(携带undo log一块).
如果拿锁失败则继续尝试, 超出尝试范围将放弃. 回滚本地事务, 释放本地锁 - 将本地事务结果上报给TC
二阶段执行流程
回滚
- 收到 TC 的分支回滚请求, 开启本地事务
- 根据 XID(全局事务锁ID) 和 Branch ID(分支事务ID) 查找到 UNDO_LOG 表中对应记录
- 那记录中的后镜像与当前数据比较, 如果不同, 则说明数据被当前全局事务之外的动作修改. 此时根据配置策略处理, 详见文档
- 根据 UNDO_LOG 的前镜像和 SQL 语句生成对应回滚语句
- 提交本地事务. 上报本地事务执行结果给 TC
提交
- 收到 TC 的分支提交请求, 将请求放入异步队列, 立刻响应处理成功结果给 TC
- 异步队列删除 UNDO_LOG 表的记录
更详细流程
下方解释和图表来自对豆包的总结, 不保证正确.
TC 是被动接收方, 通过 @GlobalTransactional 注解实现开启事务、提交、回滚、获取当前状态等方法. 根据context判断是rm还是tm, tc在一开始是不知道有多少rm的, 而是通过 tm 调用 rm, 然后 rm 主动携带 branchID xid 等向 tc 报道. 在完成后 tm 会根据执行向 tc 上报 commit/rollback
| 步骤 | 发起方 | 协议 / 动作 | 接收方 | 作用 |
|---|---|---|---|---|
| 1 | TM | GlobalBeginRequest | TC | 开启全局事务,获取 XID |
| 2 | TM | RPC 调用 (含 XID) | RM | 业务驱动 RM 干活 |
| 3 | RM | 执行本地 SQL, 生成 UNDO LOG | DB | 执行业务逻辑 |
| 4 | RM | BranchRegisterRequest (含 lockKey) | TC | RM 主动报到,TC 知道了 RM 的存在并分配全局锁 |
| 5 | RM | Commit Local Transaction | DB | 提交本地事务,释放本地锁 |
| 6 | TM | GlobalCommit/RollbackRequest | TC | 告诉 TC 是提交还是回滚 |
| 7 | TC | BranchCommit/RollbackRequest | RM | TC 主动推送命令,让 RM 执行二阶段操作 |
| 8 | RM | 删除 UNDO_LOG / 执行回滚 SQL | DB | 完成二阶段收尾 |
TCC Try-Confirm-Cancel
- 用户下单商品, 执行Try检查商品和预留字段的库存, 增加预留库存
- 操作1 无问题执行Confirm, 实际扣减商品库存, 删除预留库存.
操作1 存在问题执行Cancel, 取消预留库存, 执行其他恢复代码
优点: 性能好于2PC, 因为取消了事务导致的长锁(但可能需要短锁/乐观锁保证库存检查和预留)
缺点: 开发量大, 每个业务均需编写三个接口: TrySell, ConfirmSell, CancelSell, 需修改数据库表结构
衍生方案
通用型 TCC 异步化
try阶段不变, confirm/cancel 阶段基于 MQ 异步
异步确保型 TCC
借用 Seata 图片, 感觉类似于 RocketMQ 的半消息机制
- try阶段发消息给mq, 只做存储, 消费者无法读取到
- confirm/cancel 阶段消费者才可以读取

补偿型 TCC
https://seata.apache.org/zh-cn/blog/tcc-mode-applicable-scenario-analysis/#%E5%9B%9B%E8%A1%A5%E5%81%BF%E5%9E%8B-tcc-%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88 seata官网文章
补偿型 TCC 适用并发冲突少 / 外部业务, 只需要 Try 和 Cancel 两个接口即可实现
- 我向A, B发送Do请求
- 如果都成功则正常处理
如果A / B 出现问题则发送 Compensate 请求补偿
seata会自己解决原子性问题, 这里我懒得深入了解了
基于本地消息的最终一致性方案
- 拥有业务表A, 信息表B
- 执行业务, 首先启动事务, 事务中执行业务, 同时将消息插入消息表中. commit/rollback 保证业务和消息100%不会丢失
- 定时任务扫描信息表, 发送信息到MQ
- 消费方读取消息处理
优点: 实现简单, 不依赖外部消息中间件的事务, 本地消息绝对不会丢失
缺点: 业务表和消息表耦合, 定时任务有延迟
基于可靠消息的最终一致性方案
即借用外部消息中间件的事务功能, RocketMQ叫半消息机制, Kafka叫生产者事务(0.11引入), RabbitMQ叫AMQP事务

RocketMQ流程
极端情况下, 业务代码在执行commit/rollback时可能会出现网络波动导致没有正确处理, 这时会有事务回查(补偿)机制, RocketMQ 会主动询问此事务是commit还是rollback
Kafka
kafka流程同上, 但没有回查机制, 依靠三种状态: committed, aborted, incomplete和事务协调器兜底.
- 事务开启, 消息发送
- commit/abort没有送达
- 根据transaction.timeout.ms 配置, 超时将事务标记为Aborted
- 结束
消费者默认仅读取committed 消息, 可通过isolation.level=read_committed 配置
可以手写补偿机制完善.
RabbitMQ
基于AMQP协议的原生事务性能较差, 生产环境中几乎不用, 此事务流程同上
更多使用发布确认机制(流程不同上):
- 单个确认:

- 批量确认: 流程和单个确认相同, 是堆积到一定数量消息后, 将消息一次性发送过去等待整批响应. 缺点是无法确定批次中哪个消息出现问题
- 异步确认(性能最优): 但需要手动完善机制才能实现RocketMQ效果

最大努力通知方案

优点: 实现简单, 最柔, 整体流程无锁
缺点: 一致性最差
Saga
将长事务拆分为多个单步事务, 每个事务对应自己的补偿失误
定义事务为T, 取消为C
- 正常流程: T1->T2->T3->T4
- 失败失败:T1->T2->T3失败->C3->C2->C1
两种模式
编排式(生产常用)
由协调器调配: 先做1->再做2, 如果2失败协调器下令回滚1
协作式
服务直接靠消息/其他手段互相通知
A 做完发消息, B 监听做, B 失败发消息, A 自己回滚
结论
优点: 支持长事务, 并发高, 整体流程无锁(即不会像2PC 那样所有T 均上锁)
缺点: 补偿逻辑复杂, 中间状态会暴露给用户
衍生方案
嵌套 Saga
Saga 只允许两个层次的嵌套, 即 父Saga-多个子Saga. 而嵌套 Saga 则是将一个/多个子事务合并成一个独立、完整的 Saga 事务.
- 子 Saga 拥有自己完整的正向执行流和反向补偿流
- 子 Saga 对外暴露原子化的事务接口, 父 Saga 将其视为普通事务节点, 不感知其内部实现, 也不能单独调用子 Saga 中的内部某个事务
- 子 Saga 不能依赖父 Saga 的其他子 Saga 资源
执行流程
Root Saga 流程为 A → B → C,其中 B 是嵌套子 Saga(包含 B1→B2→B3)
- 正向:A 成功 → 子 Saga B 全流程执行成功 → C 成功,全局事务完成
- 异常:A 成功 → 子 Saga B 执行到 B2 失败 → 子 Saga B 先补偿 B2→补偿 B1,完成自回滚 → 向 Root Saga 返回失败 → Root Saga 补偿 A,全局回滚完成
基于事件溯源(Event Sourcing, ES)的事务方案
ES指: 不存储实体的当前状态,只存储所有导致状态变更的不可变事件.
比如update set a = 50 where id = 1; 这里我们不存储a=50这个结果, 而是存储 将 id 为 1 的记录, a 字段修改为 50 这个操作.
ES + Outbox Pattern
- 收到操作请求, 记录请求的操作到 Event Store 中,状态标记为 待发布.
- 按顺序投递存储的操作到 MQ, 状态标记为 已发布
- 下游监听 MQ, 执行本地事务并写入到自身 Event Store, 并且通过唯一 id 实现幂等性.
ES + Saga
- 订单服务接收请求, 在本地事务中生成
订单创建事件写入 Event Store, 发布到事件总线 - Saga 编排器监听订单创建成功事件, 触发库存服务扣减库存, 库存服务完成后生成
库存扣减成功事件写入自身 Event Store 并发布 - 编排器依次监听和触发, 直到整个 saga 事务结束
即使服务宕机也可以通过 ES 重放
CQRS(读写分离) + ES
CQRS: 命令查询职责分离, 将写操作和读操作完全拆分.
写侧(命令端):完全基于 ES 实现,只负责处理业务命令、生成不可变事件、写入 Event Store,保障写事务的原子性, 是整个系统唯一的事实源
读侧(查询端):通过监听写侧的事件流, 更新适配查询场景的读库(MySQL/ES/Redis 等),通过事件的可靠消费保障读写最终一致性
跨服务分布式事务通过「命令→事件→命令」的事件驱动流转实现,全程无锁、无耦合。
- 写侧处理:业务请求以 Command 形式提交到写服务,写服务验证命令合法性后,在本地事务中生成业务事件写入 Event Store,完成写侧事务,事件发布到事件总线
- 跨服务事务流转:下游服务监听对应事件, 转化为自身的业务 Command, 执行本地写操作并生成新的事件写入 Event Store, 完成跨服务事务的原子性流转
- 读侧同步:读侧的投影服务(Projection)监听事件总线,根据事件类型更新对应的读库(如订单列表写入 MySQL、热点数据写入 Redis)。
一致性保障:通过事件唯一 ID 实现消费幂等,确保事件只被处理一次. 读侧更新失败会自动重试,直到成功;若读侧数据出现不一致,可直接重放 Event Store 的事件,重新生成全量读库数据,彻底修复一致性问题。
事务回滚:跨服务事务失败时生成补偿事件,写侧 Event Store 持久化补偿事件,读侧投影服务同步更新读库,完成回滚后的全链路一致性。
参考
[]: https://seata.apache.org/zh-cn/docs/overview "Seata文档"
[]: https://javaguide.cn/distributed-system/protocol/cap-and-base-theorem.html "CAP & BASE理论详解"
[]: https://www.doubao.com/ "豆包"