应用层跨服务事务的全景图:从 2PC 的经典协调,到 TCC、Saga、本地消息表等业务化变体,以及它们与数据层共识算法 Paxos 的本质差异。
分布式系统里的"一致性问题"其实有两个不同的层次,理解它们的差异是理解分布式事务模式的前提——它们都叫"事务",但其实是两回事。
跨服务/跨资源的业务事务。多个独立系统之间业务操作的原子性。
典型场景——电商下单:
下单业务 ├─ 订单服务:创建订单 ├─ 库存服务:扣减库存 ├─ 支付服务:扣款 └─ 积分服务:增加积分
四个操作分布在不同服务、不同数据库上。业务上要求"要么全成功,要么全失败"。也叫分布式事务、业务事务。
副本之间的数据一致性。同一份数据的多个副本之间的一致性。
典型场景——MySQL 主从 / etcd 多副本:
一份数据存 3 份副本 ├─ 节点 A(主) ├─ 节点 B(从) └─ 节点 C(从)
客户端写一条数据,这 3 个副本必须达成一致——大家都认这条数据是 X=5。也叫一致性问题、共识问题。
| 维度 | 应用层事务 | 数据层事务 |
|---|---|---|
| 参与者 | 不同业务系统/数据库 | 同一份数据的多个副本 |
| 操作内容 | 不同的业务动作 | 相同的数据写入 |
| 目标 | 业务原子性(全成功或全失败) | 副本一致性(大家数据相同) |
| 失败处理 | 回滚已完成的步骤 | 选出多数派,少数服从多数 |
| 关心 | "业务要不要做" | "数据存的是什么" |
应用层:协调"不同事情",要么都做,要么都不做。
数据层:协调"同一件事",所有副本对它的状态达成共识。
这是分布式事务模式的核心命题:"在应用层上解决事务问题,只有'两阶段提交'这样的方式。"
为什么是 2PC?因为应用层事务的参与者是异构的、独立的——一个 MySQL、一个 Redis、一个第三方支付 API,它们各自有自己的状态,互不知晓。没有"投票选主"这种概念,必须有一个协调者去"逐个询问、统一指挥"。
原文说"只有两阶段提交这样的方式"——是说这一类思路,不是字面意义的 2PC 协议。这一类的家族包括 2PC、3PC、TCC、Saga、本地消息表等,它们的核心思想都是 2PC 那一套:先准备,再统一提交,失败就回滚/补偿。
协调多个独立资源的经典算法。基础版本,阻塞、有单点问题。
阶段一:准备(Prepare) 协调者 → 所有参与者:你能提交吗? 参与者:执行操作但不真正提交,回答"可以"或"不行" 阶段二:提交(Commit) 如果所有人都说"可以" → 协调者通知所有人正式提交 如果有人说"不行" → 协调者通知所有人回滚
典型实现:MySQL XA 事务。
在 2PC 基础上多加一个阶段,引入超时机制以减少阻塞,但代价是更复杂。
3PC 相较于 2PC:
3PC 理论上更优,但实际工程中很少直接使用——复杂度上升而收益有限。生产环境更倾向于放弃强一致性的 3PC,转而拥抱 TCC / Saga 这种业务层补偿模型。
业务层面的 2PC——每个操作自己实现"尝试 → 确认 → 取消"三个接口。
长事务方案——把一个大事务拆成若干个顺序子事务,每个子事务都有对应的补偿操作。
大事务 T = T1 + T2 + T3 + ... + Tn,每个 Ti 都有补偿 Ci:
正向:T1 → T2 → T3 → T4 (全部成功) 失败:T1 → T2 → T3 → ❌ 回滚: C3 ← C2 ← C1 (依次补偿已执行的步骤)
通过消息队列 + 本地事务实现最终一致性。利用本地数据库事务的原子性把"业务"和"消息"绑定在一起。
MQ 直接提供"半消息"语义:
讨论分布式事务模式时,必须把MySQL 内部的"两阶段提交"和分布式事务的 2PC区分清楚——它们同名但完全不是一回事。
主要指 InnoDB 的 redo log 与 Server 层的 binlog 之间的内部一致性协调机制,目的是保证这两份日志在崩溃恢复后保持一致,从而保证数据和主从复制的一致性。
InnoDB 用 redo log 保证事务持久性(crash-safe),Server 层用 binlog 做归档和主从复制。如果两者不能原子地写入,就会出现:
主从就此不一致,无法接受。
prepare,记录该事务的 XID。sync_binlog 控制)。prepare 改为 commit。这一步是逻辑标记,写入很轻。MySQL 重启时扫描 redo log,遇到事务按状态判断:
commit → 事务完成,正常恢复prepare 状态 → 拿 XID 去 binlog 里查
这不是分布式事务里的 2PC。 XA 协议的两阶段提交是跨多个资源管理器的,参与者是不同的数据库节点。MySQL 这里的 2PC 只是单机内部 InnoDB 引擎和 Server 层之间的协调,参与者只有两方且都在同一进程内,所以协调成本很低。
innodb_flush_log_at_trx_commit=1 和 sync_binlog=1 都设为 1 时(即"双 1 配置"),才能真正保证 crash-safe,否则两阶段提交的保证会被削弱。
在两阶段提交基础上的优化,把 prepare、sync binlog、commit 三个阶段做成流水线,多个事务的 fsync 合并成一次,大幅降低高并发下的 IOPS 压力。MySQL 5.6 之后默认开启。
原文:"迄今为止,在应用层上解决事务问题,只有'两阶段提交'这样的方式,而在数据层解决事务问题,Paxos 算法则是不二之选。"
| 问题 | 2PC 在数据层的缺陷 | Paxos 怎么解决 |
|---|---|---|
| 协调者宕机 | 整个流程卡死 | 没有固定协调者,谁活着谁推动 |
| 一个参与者挂掉 | 整个事务失败 | 多数派同意就行,少数挂了不影响 |
| 网络分区 | 阻塞或回滚 | 多数派那边继续工作 |
| 可用性 | 任何节点故障都影响整体 | 容忍 (N-1)/2 个节点故障 |
数据层的特点是参与者同质——它们是同一份数据的副本,地位平等。这种场景下,"投票表决"比"集中指挥"更合适,也更鲁棒。
理论上可以,但"杀鸡用牛刀"。业务事务的参与者本来就异构、关心的是业务流程,硬塞一个共识协议进去,复杂度爆炸且无收益。
可以,但可用性太差。如果 3 副本要 2PC 提交,任何一个副本挂了写入就失败,等于"3 副本反而不如单点"。Paxos 只要 2/3 副本活着就行。
"不二之选"略显绝对——Paxos 在数据层确实是强一致语境下的事实标准,但也有不走共识路线的方案:
但这些方案的一致性级别比 Paxos 弱。只要追求强一致性,Paxos 系(含 Raft、ZAB)确实是不二之选。
| 场景 | 用什么 | 属于哪一类 |
|---|---|---|
| 跨服务下单(订单+库存+支付) | Seata(AT/TCC/Saga)、本地消息表 | 2PC 家族 |
| MySQL XA 事务 | 2PC | 2PC 家族 |
| etcd / Consul 数据同步 | Raft | Paxos 家族 |
| ZooKeeper 数据同步 | ZAB | Paxos 家族 |
| TiDB / CockroachDB 副本一致性 | Raft | Paxos 家族 |
| Kafka Controller 选举 | ZooKeeper(ZAB)→ 新版本是 KRaft | Paxos 家族 |
业务事务 → 2PC 家族(2PC / 3PC / TCC / Saga / 本地消息表 / 事务消息)
存储一致性 → Paxos 家族(Basic Paxos / Multi-Paxos / Raft / ZAB)
| 微服务能力 | 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 多种分布式事务模式的统一接入。
不同架构对分布式事务的依赖程度截然不同——这反过来决定了是否需要引入这些复杂模式。
| 架构形态 | 分布式事务需求 | 说明 |
|---|---|---|
| 单体架构 | 不需要 | 所有操作在同一个本地事务里完成。 |
| SBA(Service-Based Architecture) | 较少 | 共享数据库,直接使用本地事务,避免微服务里头疼的分布式事务(Saga、TCC 等)。 |
| 微服务架构 | 频繁 | 服务边界清晰、各自数据库,跨服务调用必然涉及分布式事务。 |
"事务简单:共享数据库意味着可以直接使用本地事务,避免微服务里头疼的分布式事务(Saga、TCC 等)。"——这是 SBA 相对微服务的核心收益之一。许多团队盲目追求微服务而吃了分布式事务的亏,正是因为忽视了这一点。
分布式一致性问题
│
┌─────────────────┴─────────────────┐
│ │
应用层事务 数据层事务
(业务原子性) (副本一致性)
│ │
"不同事情都要做完" "同一件事大家认同"
│ │
2PC 家族 Paxos 家族
├─ 2PC(基础) ├─ Basic Paxos
├─ 3PC(减少阻塞) ├─ Multi-Paxos
├─ TCC(业务化 2PC) ├─ Raft(可读版)
├─ Saga(长事务补偿) └─ ZAB(ZooKeeper)
├─ 本地消息表
└─ 事务消息(半消息)