分布式事务几种方案

在了解方案前先了解一下几个理论

理论

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)

  1. AP 向TM 发起全局事务
  2. TM向RM A, B发送要执行的SQl
  3. RM A, B开启事务, 执行业务SQL, 两个执行成功/失败均向TM 返回
  4. TM 根据返回决定向RM 发送commit或rollback

优点: 强一致, 开发简单. 适用于金融等系统

缺点: 性能差, 协调者节点挂了会导致锁无法释放(可用心跳包等方法解决), 极端情况下会导致一部分commit, 一部分rollback

3PC 三阶段提交

  1. CanCommit: 仅检查库存, 锁.
  2. PreCommit: 所有节点的Can 均返回成功时, 向所有节点发送PreCommit, 此步骤开启事务但不提交.
  3. 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、生成日志、执行回滚

执行流程

  1. 开启本地事务, 解析SQL 的操作(update等)、表(product),条件(where name = 'TXC')等相关的信息
  2. 查询前镜像:根据解析得到的条件信息,生成查询语句,定位数据
  3. 执行业务 SQL, 将执行后的结果当做后镜像: 根据前镜像的结果,通过 主键 定位数据
  4. 将前后镜像和业务SQL 组成一条回滚日志记录, 插入到UNDO_LOG 表中
  5. 向TC 申请指定表指定主键的记录的全局锁
  6. 如果申请到全局锁, 本地事务提交(携带undo log一块).
    如果拿锁失败则继续尝试, 超出尝试范围将放弃. 回滚本地事务, 释放本地锁
  7. 将本地事务结果上报给TC

TCC Try-Confirm-Cancel

  1. 用户下单商品, 执行Try检查商品和预留字段的库存, 增加预留库存
  2. 操作1 无问题执行Confirm, 实际扣减商品库存, 删除预留库存.
    操作1 存在问题执行Cancel, 取消预留库存, 执行其他恢复代码

优点: 性能好于2PC, 因为取消了事务导致的长锁(但可能需要短锁/乐观锁保证库存检查和预留)

缺点: 开发量大, 每个业务均需编写三个接口: TrySell, ConfirmSell, CancelSell, 需修改数据库表结构

基于本地消息的最终一致性方案

  1. 拥有业务表A, 信息表B
  2. 执行业务, 首先启动事务, 事务中执行业务, 同时将消息插入消息表中. commit/rollback 保证业务和消息100%不会丢失
  3. 定时任务扫描信息表, 发送信息到MQ
  4. 消费方读取消息处理

优点: 实现简单, 不依赖外部消息中间件的事务, 本地消息绝对不会丢失

缺点: 业务表和消息表耦合, 定时任务有延迟

基于可靠消息的最终一致性方案

即借用外部消息中间件的事务功能, RocketMQ叫半消息机制, Kafka叫生产者事务(0.11引入), RabbitMQ叫AMQP事务

通用流程 image-20260215233206772

RocketMQ流程

RocketMQ 在执行commit/rollback时可能会出现网络波动导致没有正确处理, 这时会有事务回查(补偿)机制, mq会主动询问此事务是commit还是rollback

Kafka

kafka流程同上, 但没有回查机制, 依靠三种状态: committed, aborted, incomplete和事务协调器兜底.

  1. 事务开启, 消息发送
  2. commit/abort没有送达
  3. 根据transaction.timeout.ms 配置, 超时将事务标记为Aborted
  4. 结束

消费者默认仅读取committed 消息, 可通过isolation.level=read_committed 配置

可以手写补偿机制完善.

RabbitMQ

基于AMQP协议的原生事务性能较差, 生产环境中几乎不用, 此事务流程同上

更多使用发布确认机制(流程不同上):

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

最大努力通知方案

image-20260216001622681

优点: 实现简单, 最柔, 整体流程无锁

缺点: 一致性最差

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 方案

异步 TCC / 分级 TCC 方案

基于事件溯源(Event Sourcing, ES)的事务方案

我来吐槽

*