在了解方案前先了解一下几个理论
理论
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 组成一条回滚日志记录, 插入到UNDO_LOG 表中
- 向TC 申请指定表的指定主键的记录的全局锁
- 如果申请到全局锁, 本地事务提交(携带undo log一块).
如果拿锁失败则继续尝试, 超出尝试范围将放弃. 回滚本地事务, 释放本地锁 - 将本地事务结果上报给TC
TCC Try-Confirm-Cancel
- 用户下单商品, 执行Try检查商品和预留字段的库存, 增加预留库存
- 操作1 无问题执行Confirm, 实际扣减商品库存, 删除预留库存.
操作1 存在问题执行Cancel, 取消预留库存, 执行其他恢复代码
优点: 性能好于2PC, 因为取消了事务导致的长锁(但可能需要短锁/乐观锁保证库存检查和预留)
缺点: 开发量大, 每个业务均需编写三个接口: TrySell, ConfirmSell, CancelSell, 需修改数据库表结构
基于本地消息的最终一致性方案
- 拥有业务表A, 信息表B
- 执行业务, 首先启动事务, 事务中执行业务, 同时将消息插入消息表中. commit/rollback 保证业务和消息100%不会丢失
- 定时任务扫描信息表, 发送信息到MQ
- 消费方读取消息处理
优点: 实现简单, 不依赖外部消息中间件的事务, 本地消息绝对不会丢失
缺点: 业务表和消息表耦合, 定时任务有延迟
基于可靠消息的最终一致性方案
即借用外部消息中间件的事务功能, RocketMQ叫半消息机制, Kafka叫生产者事务(0.11引入), RabbitMQ叫AMQP事务

RocketMQ流程
RocketMQ 在执行commit/rollback时可能会出现网络波动导致没有正确处理, 这时会有事务回查(补偿)机制, mq会主动询问此事务是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 均上锁)
缺点: 补偿逻辑复杂, 中间状态会暴露给用户