系统架构 · 复习笔记 整理自历史对话

分布式事务模式

应用层跨服务事务的全景图:从 2PC 的经典协调,到 TCC、Saga、本地消息表等业务化变体,以及它们与数据层共识算法 Paxos 的本质差异。

§ 01

核心区分:两种"事务"

分布式系统里的"一致性问题"其实有两个不同的层次,理解它们的差异是理解分布式事务模式的前提——它们都叫"事务",但其实是两回事。

应用层事务

跨服务/跨资源的业务事务。多个独立系统之间业务操作的原子性。

典型场景——电商下单

下单业务
  ├─ 订单服务:创建订单
  ├─ 库存服务:扣减库存
  ├─ 支付服务:扣款
  └─ 积分服务:增加积分

四个操作分布在不同服务、不同数据库上。业务上要求"要么全成功,要么全失败"。也叫分布式事务、业务事务。

数据层事务

副本之间的数据一致性。同一份数据的多个副本之间的一致性。

典型场景——MySQL 主从 / etcd 多副本

一份数据存 3 份副本
  ├─ 节点 A(主)
  ├─ 节点 B(从)
  └─ 节点 C(从)

客户端写一条数据,这 3 个副本必须达成一致——大家都认这条数据是 X=5。也叫一致性问题、共识问题。

两种问题的本质区别

维度应用层事务数据层事务
参与者不同业务系统/数据库同一份数据的多个副本
操作内容不同的业务动作相同的数据写入
目标业务原子性(全成功或全失败)副本一致性(大家数据相同)
失败处理回滚已完成的步骤选出多数派,少数服从多数
关心"业务要不要做""数据存的是什么"
▸ 核心记忆点

应用层:协调"不同事情",要么都做,要么都不做。

数据层:协调"同一件事",所有副本对它的状态达成共识。

§ 02

为什么应用层只能用 2PC 这一类

这是分布式事务模式的核心命题:"在应用层上解决事务问题,只有'两阶段提交'这样的方式。"

为什么是 2PC?因为应用层事务的参与者是异构的、独立的——一个 MySQL、一个 Redis、一个第三方支付 API,它们各自有自己的状态,互不知晓。没有"投票选主"这种概念,必须有一个协调者去"逐个询问、统一指挥"。

▸ 注意

原文说"只有两阶段提交这样的方式"——是说这一类思路,不是字面意义的 2PC 协议。这一类的家族包括 2PC、3PC、TCC、Saga、本地消息表等,它们的核心思想都是 2PC 那一套:先准备,再统一提交,失败就回滚/补偿。

§ 03

2PC · 两阶段提交

基础版

协调多个独立资源的经典算法。基础版本,阻塞、有单点问题。

提交流程

1
阶段一:准备(Prepare) — 协调者询问所有参与者"你能提交吗?"参与者执行操作但不真正提交,回答"可以"或"不行"。
2
阶段二:提交(Commit) — 如果所有人都说"可以",协调者通知所有人正式提交;如果有人说"不行",协调者通知所有人回滚。
阶段一:准备(Prepare)
  协调者 → 所有参与者:你能提交吗?
  参与者:执行操作但不真正提交,回答"可以"或"不行"

阶段二:提交(Commit)
  如果所有人都说"可以" → 协调者通知所有人正式提交
  如果有人说"不行"     → 协调者通知所有人回滚
优点
  • 逻辑简单,是分布式事务的基础范式
  • 强一致性保证
缺点
  • 阻塞:所有参与者锁定资源等协调者指令
  • 单点问题:协调者宕机整个流程卡死
  • 任何一个参与者挂掉 → 整个事务失败

典型实现:MySQL XA 事务。

§ 04

3PC · 三阶段提交

优化版

在 2PC 基础上多加一个阶段,引入超时机制以减少阻塞,但代价是更复杂。

3PC 相较于 2PC:

  • 多了一个"预提交"阶段,让参与者能够判断协调者是否健康
  • 引入超时机制,参与者在长时间收不到协调者指令时能自行决策
  • 核心目的是减少 2PC 中"协调者挂了,参与者全卡死"的尴尬
▸ 实际权衡

3PC 理论上更优,但实际工程中很少直接使用——复杂度上升而收益有限。生产环境更倾向于放弃强一致性的 3PC,转而拥抱 TCC / Saga 这种业务层补偿模型

§ 05

TCC · Try-Confirm-Cancel

业务化 2PC

业务层面的 2PC——每个操作自己实现"尝试 → 确认 → 取消"三个接口。

三个阶段

T
Try — 尝试执行业务,预留资源(如冻结余额、预占库存),不真正完成业务。
C
Confirm — 所有 Try 都成功,确认提交业务,真正完成扣减
C
Cancel — 任何一个 Try 失败,调用所有已 Try 的服务的 Cancel 接口,释放预留资源
优点
  • 不依赖底层资源管理器的 XA 支持
  • 业务无关性强,跨异构系统
  • 无长事务锁定,并发度高
缺点
  • 业务侵入性强:每个操作要写三个接口
  • 需要保证 Confirm / Cancel 的幂等性空回滚处理
  • 实现成本高,适合金融/支付类核心场景
§ 06

Saga · 长事务补偿

补偿型

长事务方案——把一个大事务拆成若干个顺序子事务,每个子事务都有对应的补偿操作。

核心思想

大事务 T = T1 + T2 + T3 + ... + Tn,每个 Ti 都有补偿 Ci

正向:T1 → T2 → T3 → T4         (全部成功)
失败:T1 → T2 → T3 → ❌
回滚:       C3 ← C2 ← C1       (依次补偿已执行的步骤)

两种执行模式

  • 编排式(Choreography):通过事件驱动,每个服务监听上一步的事件,自己决定下一步。去中心化但难以追踪。
  • 编制式(Orchestration):有一个中央协调器统一编排顺序、监控状态、触发补偿。可控性高。
优点
  • 适合长事务(跨小时甚至跨天)
  • 无资源锁定,最终一致性
  • 各步骤独立,性能好
缺点
  • 中间状态对外可见(隔离性差)
  • 补偿逻辑设计复杂,业务侵入
  • 难以应对"操作不可逆"的场景(如已发短信)
§ 07

本地消息表 / 事务消息

最终一致性

通过消息队列 + 本地事务实现最终一致性。利用本地数据库事务的原子性把"业务"和"消息"绑定在一起。

本地消息表方案

1
业务操作和"发消息记录"写在同一个本地事务里(同一个数据库 commit)。
2
后台任务定时扫描消息表,把未发送的消息投递到 MQ
3
下游服务消费消息执行对应业务,幂等处理,处理完更新消息表状态。
4
如果消息丢失或下游失败,重试机制保证最终送达

事务消息方案(RocketMQ 风格)

MQ 直接提供"半消息"语义:

  • 发送方先发"半消息"到 MQ,消费者看不到
  • 本地事务执行成功 → 通知 MQ"提交" → 消息对消费者可见
  • 本地事务失败 → 通知 MQ"回滚" → 消息丢弃
  • 如果发送方挂了,MQ 主动回查事务状态
优点
  • 实现相对简单,依赖成熟基础设施(MQ)
  • 无强一致性开销,性能好
  • 解耦上下游服务
缺点
  • 只保证最终一致性,存在时间窗口不一致
  • 消费方必须实现幂等
  • 不适合"对外可见的即时一致性"场景
○ ○ ○
§ 08

易混淆:MySQL 内部 2PC

讨论分布式事务模式时,必须把MySQL 内部的"两阶段提交"分布式事务的 2PC区分清楚——它们同名但完全不是一回事。

MySQL 的两阶段提交是什么 单机内部

主要指 InnoDB 的 redo log 与 Server 层的 binlog 之间的内部一致性协调机制,目的是保证这两份日志在崩溃恢复后保持一致,从而保证数据和主从复制的一致性。

为什么需要

InnoDB 用 redo log 保证事务持久性(crash-safe),Server 层用 binlog 做归档和主从复制。如果两者不能原子地写入,就会出现:

主从就此不一致,无法接受。

提交流程

1
Prepare 阶段:InnoDB 把 redo log 写入并刷盘,状态标记为 prepare,记录该事务的 XID。
2
写 binlog:Server 层把 binlog 写入并刷盘(受 sync_binlog 控制)。
3
Commit 阶段:InnoDB 把 redo log 中该事务状态从 prepare 改为 commit。这一步是逻辑标记,写入很轻。

崩溃恢复怎么用它

MySQL 重启时扫描 redo log,遇到事务按状态判断:

▸ 关键区分

这不是分布式事务里的 2PC。 XA 协议的两阶段提交是跨多个资源管理器的,参与者是不同的数据库节点。MySQL 这里的 2PC 只是单机内部 InnoDB 引擎和 Server 层之间的协调,参与者只有两方且都在同一进程内,所以协调成本很低。

关键参数 · 双 1 配置

innodb_flush_log_at_trx_commit=1sync_binlog=1 都设为 1 时(即"双 1 配置"),才能真正保证 crash-safe,否则两阶段提交的保证会被削弱。

组提交(Group Commit)

在两阶段提交基础上的优化,把 prepare、sync binlog、commit 三个阶段做成流水线,多个事务的 fsync 合并成一次,大幅降低高并发下的 IOPS 压力。MySQL 5.6 之后默认开启。

§ 09

2PC 家族 vs Paxos 家族

原文:"迄今为止,在应用层上解决事务问题,只有'两阶段提交'这样的方式,而在数据层解决事务问题,Paxos 算法则是不二之选。"

为什么数据层用 Paxos 而不用 2PC

问题2PC 在数据层的缺陷Paxos 怎么解决
协调者宕机整个流程卡死没有固定协调者,谁活着谁推动
一个参与者挂掉整个事务失败多数派同意就行,少数挂了不影响
网络分区阻塞或回滚多数派那边继续工作
可用性任何节点故障都影响整体容忍 (N-1)/2 个节点故障

数据层的特点是参与者同质——它们是同一份数据的副本,地位平等。这种场景下,"投票表决"比"集中指挥"更合适,也更鲁棒。

Paxos 家族

为什么不能"反着用"

用 Paxos 做业务事务?

理论上可以,但"杀鸡用牛刀"。业务事务的参与者本来就异构、关心的是业务流程,硬塞一个共识协议进去,复杂度爆炸且无收益。

用 2PC 做数据复制?

可以,但可用性太差。如果 3 副本要 2PC 提交,任何一个副本挂了写入就失败,等于"3 副本反而不如单点"。Paxos 只要 2/3 副本活着就行。

▸ Paxos 之外的选择

"不二之选"略显绝对——Paxos 在数据层确实是强一致语境下的事实标准,但也有不走共识路线的方案:

  • 最终一致性系统(Dynamo、Cassandra):用 Quorum 读写 + 向量时钟解决冲突,不强求强一致。
  • CRDT(无冲突复制数据类型):数据结构本身保证最终收敛,连投票都不要。

但这些方案的一致性级别比 Paxos 弱。只要追求强一致性,Paxos 系(含 Raft、ZAB)确实是不二之选。

§ 10

真实场景对应表

场景用什么属于哪一类
跨服务下单(订单+库存+支付)Seata(AT/TCC/Saga)、本地消息表2PC 家族
MySQL XA 事务2PC2PC 家族
etcd / Consul 数据同步RaftPaxos 家族
ZooKeeper 数据同步ZABPaxos 家族
TiDB / CockroachDB 副本一致性RaftPaxos 家族
Kafka Controller 选举ZooKeeper(ZAB)→ 新版本是 KRaftPaxos 家族
▸ 清晰分工

业务事务 → 2PC 家族(2PC / 3PC / TCC / Saga / 本地消息表 / 事务消息)

存储一致性 → Paxos 家族(Basic Paxos / Multi-Paxos / Raft / ZAB)

开源工具映射 SPRING CLOUD ALIBABA

微服务能力Spring Cloud 组件
服务注册与发现Eureka、Consul、Nacos
配置中心Spring Cloud Config、Nacos Config
服务调用OpenFeign、RestTemplate
熔断降级Resilience4j、Sentinel
分布式事务Seata(非 Spring Cloud 官方但常配合使用)

阿里 Spring Cloud Alibaba 中:Seata 提供 AT / TCC / Saga / XA 多种分布式事务模式的统一接入。

§ 11

架构选型对分布式事务的影响

不同架构对分布式事务的依赖程度截然不同——这反过来决定了是否需要引入这些复杂模式。

架构形态分布式事务需求说明
单体架构不需要所有操作在同一个本地事务里完成。
SBA(Service-Based Architecture)较少共享数据库,直接使用本地事务,避免微服务里头疼的分布式事务(Saga、TCC 等)。
微服务架构频繁服务边界清晰、各自数据库,跨服务调用必然涉及分布式事务。
▸ SBA 的关键优势

"事务简单:共享数据库意味着可以直接使用本地事务,避免微服务里头疼的分布式事务(Saga、TCC 等)。"——这是 SBA 相对微服务的核心收益之一。许多团队盲目追求微服务而吃了分布式事务的亏,正是因为忽视了这一点。

§ 12

一句话回到原文

分布式系统有两种不同的"一致性问题"——业务层面要多个服务协同完成一个业务(应用层事务),用 2PC 这类协议;存储层面要多个副本对同一份数据达成共识(数据层事务),用 Paxos 这类协议。

记忆框架

▸ 三层记忆图
                    分布式一致性问题
                          │
        ┌─────────────────┴─────────────────┐
        │                                   │
   应用层事务                            数据层事务
   (业务原子性)                       (副本一致性)
        │                                   │
   "不同事情都要做完"                  "同一件事大家认同"
        │                                   │
   2PC 家族                            Paxos 家族
   ├─ 2PC(基础)                      ├─ Basic Paxos
   ├─ 3PC(减少阻塞)                  ├─ Multi-Paxos
   ├─ TCC(业务化 2PC)                ├─ Raft(可读版)
   ├─ Saga(长事务补偿)               └─ ZAB(ZooKeeper)
   ├─ 本地消息表
   └─ 事务消息(半消息)

选型决策树