分布式事务方案整理笔记
站在高层次的架构选型角度,目前的分布式事务方案主要分为 强一致性(刚性事务)和最终一致性(柔性事务,基于 BASE 理论) 两大阵营。具体可以归纳为以下五个主要流派:
1. 强一致性方案:两阶段提交 (2PC) / XA
这是最传统的分布式事务模型,追求强一致性(ACID)。它依赖于一个全局的事务协调者(Coordinator)来调度各个本地资源管理器(Participant)。
-
执行过程:分为 Prepare(准备阶段)和 Commit/Rollback(提交/回滚阶段)。
-
架构特点:资源在整个事务执行期间会被完全锁定。
-
适用场景:对数据一致性要求极高、并发量较小的内部系统(如核心账务系统的底层数据库层面)。
-
痛点:性能极差,存在同步阻塞问题;协调者单点故障风险高。在实际的高并发 Java 或 Go 微服务开发中,极少直接使用纯粹的 XA 方案。
在分布式事务的演进史上, 两阶段提交(Two-Phase Commit, 简称 2PC) 是最基础、也是最正统的强一致性解决方案。而 XA 是 X/Open 组织提出的一套基于 2PC 协议的分布式事务处理标准规范(很多关系型数据库如 MySQL, Oracle 等都实现了底层的 XA 接口)。
从架构设计的角度来看,2PC 的核心思想是 “引入一个中心化的协调者来统一调度所有节点的执行状态” 。
1. 2PC 的核心角色
在 2PC 模型中,存在两个绝对的核心角色:
- 事务协调者 (Transaction Coordinator, TC):整个分布式事务的大脑,负责向各个参与者发送指令并汇总结果。
- 事务参与者 (Transaction Participant / Resource Manager, RM):实际执行具体的业务操作和数据库写操作的节点(通常就是具体的微服务或其底层的数据库)。
2. 两阶段的执行流程
顾名思义,2PC 将整个事务的提交过程分为了两个阶段:
第一阶段:准备阶段 (Prepare Phase / 投票阶段)
在这个阶段,协调者试图“摸底”所有参与者是否都具备了提交事务的条件。
- 发送指令:协调者向所有参与者发送
Prepare请求。 - 本地执行与锁定:参与者接收到请求后,开启本地数据库事务,执行相关的 SQL 操作,** 但并不执行最终的
Commit**。此时,参与者会记录 Undo 和 Redo 日志,并且 锁定相关的数据库资源(例如触发行锁)。 - 反馈结果:参与者根据本地执行情况,向协调者返回
Yes(准备就绪)或No(执行失败,比如余额不足、锁等待超时等)。
第二阶段:提交/回滚阶段 (Commit/Rollback Phase / 执行阶段)
协调者根据第一阶段收集到的投票结果,决定整个事务的结果。
- 情形 A:所有参与者都返回
Yes
- 协调者向所有参与者发送
Commit请求。 - 参与者收到后,正式提交本地数据库事务,释放第一阶段占用的数据锁和隔离资源。
- 参与者向协调者返回
Ack(确认完成)。整个分布式事务宣告成功。
- 情形 B:任何一个参与者返回
No,或者协调者等待某参与者超时
- 协调者向所有参与者发送
Rollback请求。 - 参与者收到后,利用第一阶段记录的 Undo 日志回滚本地事务,释放占用的数据锁。
- 参与者向协调者返回
Ack。整个分布式事务宣告失败回滚。
3. 一个直观的例子:跨行转账
假设我们要进行一笔跨行转账:从 A 银行(参与者 1)向 B 银行(参与者 2)转账 1000 元。
阶段一:Prepare
- 协调者对 A 银行和 B 银行下达
Prepare指令。 - A 银行:检查账户发现有 5000 元。执行
UPDATE account SET balance = balance - 1000 WHERE id = A。此时 A 的账户这行数据被加了排他锁,但事务未提交。A 银行回复Yes。 - B 银行:执行
UPDATE account SET balance = balance + 1000 WHERE id = B。此时 B 的账户这行数据也被加了排他锁,事务未提交。B 银行回复Yes。
阶段二:Commit
- 协调者收到两个
Yes,下达Commit指令。 - A 银行提交底层数据库事务,释放 A 账户的锁。
- B 银行提交底层数据库事务,释放 B 账户的锁。
- 转账彻底完成,强一致性得到保证。
如果在阶段一,A 银行发现余额不足,或者 B 银行的数据库宕机导致超时,协调者在阶段二就会发送 Rollback,A 和 B 银行都会撤销刚才的操作并释放锁,仿佛什么都没发生过。
4. 架构师视角的 Trade-off (权衡与痛点)
在系统架构设计的考点中,理解 2PC 的致命缺陷比理解它的流程更重要。这也解释了为什么在 Java、Go 等高并发微服务生态中,原生的 XA/2PC 方案很少被直接应用于核心业务链路。
- 同步阻塞(最致命):在阶段一到阶段二的整个过程中,所有参与者的相关数据行都被加上了锁。如果有其他业务想要修改这些数据,就必须阻塞等待。这在高并发场景下是灾难性的,极其吞吐量。
- 单点故障风险:协调者 (Coordinator) 是绝对的单点。如果协调者在阶段二发出
Commit指令前宕机,所有参与者都会处于“锁定资源并傻等”的僵死状态。 - 极端情况下的数据不一致(脑裂问题):在阶段二中,如果协调者发出了
Commit,但由于网络分区(Network Partition),只有部分参与者收到了Commit并提交,另一部分没收到,此时系统就出现了真正的数据不一致。
基于 2PC 的这些痛点,业界后来演化出了诸如阿里 Seata 的 AT 模式(它本质上是一种改进版的、非阻塞的 2PC 变种,通过全局锁和本地回滚日志来释放底层数据库锁)。
2. 柔性事务 - 业务补偿型:TCC (Try-Confirm-Cancel)
TCC 是一种应用层的两阶段提交方案,它将 XA 的数据库层面的资源锁定操作提到了业务代码层面来实现。
执行过程:
1. Try:完成所有业务检查,预留必须的业务资源(例如冻结库存、冻结账户资金)。
2. Confirm:真正执行业务,只使用 Try 阶段预留的资源。
3. Cancel:如果任何一个服务的 Try 失败,执行 Cancel 释放预留的资源。
-
架构特点:并发度高,因为锁的粒度变成了业务层面的“冻结状态”,不会长时间锁住数据库行。
-
适用场景:对实时性要求高、周期短的强业务逻辑(如金融交易、电商下单)。
-
痛点:对业务代码的侵入性极强,开发者必须为每一个操作实现这三个接口,且需要自行处理幂等性、空回滚和悬挂问题。
从系统架构的演进来看,TCC(Try-Confirm-Cancel) 是为了解决 2PC/XA 在高并发场景下由于长时间锁定数据库资源而导致性能低下的问题,应运而生的一种业务层面的两阶段提交方案。
它属于柔性事务(最终一致性)的范畴。TCC 放弃了底层数据库级别的强锁定,而是要求开发人员在业务代码层面实现资源的锁定、确认和释放。
1. TCC 的核心理念:将锁“业务化”
在 2PC 中,资源管理器通常是数据库本身,锁定的是数据行(比如 MySQL 的排他锁)。而在 TCC 模式中,整个分布式事务的协调直接与业务服务打交道。
业务服务必须为每一个参与分布式事务的操作提供三个强约定的接口:
-
Try(尝试执行业务):
- 核心动作:完成所有业务检查(保证一致性),并预留必需的业务资源(属于准隔离级别)。
- 特点:这里对数据的修改会立刻提交本地数据库事务,释放数据库锁。锁的粒度变成了业务字段(如“冻结金额”、“锁定库存”)。
-
Confirm(确认执行业务):
- 核心动作:真正执行业务。
- 约束:只使用 Try 阶段预留的业务资源。如果所有 Try 都成功,Confirm 理论上必须成功(如果因网络失败,框架会不断重试)。
-
Cancel(取消执行业务):
- 核心动作:释放 Try 阶段预留的业务资源。
- 触发:只要有任何一个服务的 Try 阶段失败(或超时),全局事务协调者就会调用所有已成功 Try 的服务的 Cancel 接口进行业务回滚。
2. 一个直观的例子:电商下单(扣减库存 + 扣减余额)
假设用户购买一台价值 10000 元的电脑。这个业务跨越了两个微服务:库存服务和账户服务。为了实现 TCC,我们在数据库表设计上必须做出改变,引入“冻结(预留)”字段。
- 库存表原有字段:
可用库存 (available_stock) - 库存表改造为:
可用库存 (available_stock),冻结库存 (frozen_stock) - 账户表改造为:
可用余额 (available_balance),冻结余额 (frozen_balance)
阶段一:Try 阶段
全局事务协调者(如 Seata 等框架)调用下游服务的 Try 接口。
- 库存服务 Try:检查可用库存是否 > 0。如果是,执行本地事务:
UPDATE stock SET available_stock = available_stock - 1, frozen_stock = frozen_stock + 1(注意:此时本地数据库事务已直接提交,其他事务可以继续修改库存表,并发度极高)。 - 账户服务 Try:检查可用余额是否 >= 10000。如果是,执行本地事务:
UPDATE account SET available_balance = available_balance - 10000, frozen_balance = frozen_balance + 10000。
阶段二:Confirm 或 Cancel 阶段
情形 A:两个 Try 都成功(执行 Confirm) 协调者调用下游的 Confirm 接口,清理预留资源。
- 库存服务 Confirm:真正扣除库存。
UPDATE stock SET frozen_stock = frozen_stock - 1。 - 账户服务 Confirm:真正扣款。
UPDATE account SET frozen_balance = frozen_balance - 10000。
情形 B:某个 Try 失败,比如账户余额不足(执行 Cancel) 库存服务 Try 成功了,但账户服务 Try 失败。协调者决定回滚,调用已成功的库存服务的 Cancel 接口。
- 库存服务 Cancel:释放冻结库存,加回可用库存。
UPDATE stock SET available_stock = available_stock + 1, frozen_stock = frozen_stock - 1。
3. 架构师视角的 Trade-off (权衡与痛点)
TCC 将原本数据库层面该干的脏活累活,全部推给了业务开发人员。
核心优势:
- 极高的并发性能:因为 Try 阶段对资源做业务层面的预留后就立马提交了本地数据库事务,不存在长事务阻塞数据库行锁的问题。非常适合电商大促、金融支付、抢购等对吞吐量要求极高且时间极短的核心链路。
致命痛点(落地难点):
- 业务代码侵入性极强:原来只需要写一段 CRUD 代码,现在为了接入 TCC,开发人员必须绞尽脑汁为每一个操作写对应的 Try、Confirm、Cancel 三个方法。研发成本成倍增加。
- 必须处理“三大异常”(由于网络不可靠,TCC 框架必然会引入重试机制):
- 幂等性要求:Confirm 和 Cancel 可能会被协调者重复调用多次,代码必须保证多次调用的结果和一次一样(不能多扣钱、多退钱)。
- 空回滚问题:由于网络拥堵,Try 请求可能根本没到达参与者,或者丢包了。接着协调者发现超时,直接发出了 Cancel 请求。此时参与者的 Cancel 逻辑必须能够识别出“我压根没收到过 Try”,从而直接返回成功,什么都不做。
- 悬挂问题(防悬挂):这是最棘手的。Try 请求由于网络拥堵迟迟未到,协调者判定超时,发送了 Cancel 请求并执行完毕(空回滚)。过了几分钟,那个迷路的 Try 请求终于到达了参与者!如果不做防范,这个 Try 就会锁定资源,但整个全局事务其实早就结束了,导致这部分资源永远被冻结(悬挂)。
在实际的大型系统中,为了解决以上三大异常,通常需要引入一张独立的**“事务控制表”**来记录每个分支事务的执行状态(已Try、已Confirm、已Cancel),并在业务执行前先做状态机校验。
3. 柔性事务 - 长事务型:Saga 模式
Saga 核心思想是将一个长分布式事务拆分成多个本地短事务($T_1, T_2, \dots, T_n$),并为每个本地事务提供一个对应的补偿操作($C_1, C_2, \dots, C_n$)。
执行过程:如果正向事务 $T_1$ 到 $T_k$ 成功,但在 $T_{k+1}$ 失败,系统会自动反向执行补偿操作 $C_k$ 到 $C_1$(或者继续重试 $T_{k+1}$)。Saga 通常分为编排式 (Choreography)(基于事件驱动)和控制式 (Orchestration)(基于中心控制器调度)。
架构特点:没有 Prepare 阶段,直接提交本地事务。适用场景:业务流程长、跨度大、或者需要对接无法改造的第三方遗留系统的场景(例如跨国订酒店、机票租车连环预订)。
痛点:缺乏隔离性(Isolation)。某个本地事务提交后,如果整个 Saga 还没结束,它的修改已经被其他事务看到了(脏读),因此常常需要在业务层面增加“状态机”来应对隔离性缺失的影响。
在分布式架构中,Saga 模式是一种专门为“长事务”设计的柔性事务(最终一致性)解决方案。与 TCC 需要在业务层面预留资源不同,Saga 直接在各个参与节点执行并提交真实的本地事务。
从架构师的视角来看,Saga 的核心哲学是:“既然无法避免失败,那就优雅地提供一种反向补偿机制来擦屁股。”
1. Saga 的核心理念:正向操作与反向补偿
在 Saga 模式中,一个全局分布式事务会被拆解为一个由一系列本地事务组成的序列:$T_1, T_2, \dots, T_n$。
对于每一个本地事务 $T_i$,系统都必须提供一个对应的补偿操作 $C_i$。补偿操作的作用在语义上是撤销 $T_i$ 所造成的业务影响。
- 理想情况(全部成功):系统依次按照 $T_1 \rightarrow T_2 \rightarrow \dots \rightarrow T_n$ 的顺序执行。每个 $T_i$ 执行完毕后,直接提交本地数据库事务,释放数据库锁。
- 异常情况(中途失败):如果在执行到 $T_k$ 时发生业务失败,Saga 引擎会立即掉头,按照相反的顺序自动执行之前已成功步骤的补偿操作:$C_{k-1} \rightarrow \dots \rightarrow C_2 \rightarrow C_1$。最终使得整个系统恢复到初始状态(或者至少是业务上可接受的一致性状态)。
2. Saga 的两种调度流派
Saga 模式在落地时,最大的架构难点在于“谁来控制这些 $T$ 和 $C$ 的执行顺序”。目前主要分为两种流派:
-
事件编排式 (Choreography):去中心化。每个微服务在本地事务提交后发布一个 MQ 事件,下游微服务监听到事件后触发自己的本地事务。
-
特点:无单点瓶颈,服务间耦合低;但链路太长时,排查问题如同无头苍蝇,极其困难。
-
中心控制式 (Orchestration):引入一个中央调度的“状态机引擎”(Saga Orchestrator)。由这个中心节点统一下发指令,告诉各个服务现在该执行哪个 $T$ 或哪个 $C$。
-
特点:全局视图清晰,流程好追踪;但中心引擎可能成为性能瓶颈或单点故障。
3. 一个直观的例子:跨国旅行连环预订
假设一个在线旅游平台(OTA)提供一个“机酒车打包套餐”,用户一键下单,系统需要跨越三个完全独立的微服务甚至外部供应商:
- 航空服务:预订机票 ($T_1$),取消机票 ($C_1$)
- 酒店服务:预订酒店 ($T_2$),取消酒店 ($C_2$)
- 租车服务:预订车辆 ($T_3$),取消车辆 ($C_3$)
执行序列:
- 协调者触发航空服务执行 $T_1$:在航空系统内真实扣除机票座位,本地事务提交。
- 协调者触发酒店服务执行 $T_2$:在酒店系统内真实扣除房间,本地事务提交。
- 协调者触发租车服务执行 $T_3$:发现该地区车辆已租完,业务失败。
补偿序列(自动触发回滚):
- 协调者收到 $T_3$ 失败的信号。
- 协调者调用酒店服务的 $C_2$:在酒店系统内释放刚才订的房间,并可能记录一笔“系统退房”流水。
- 协调者调用航空服务的 $C_1$:在航空系统内释放刚才订的机票。
整个分布式事务结束,用户收到“预订失败,全额退款”的通知。
4. 架构师视角的 Trade-off (权衡与痛点)
核心优势:
- 无阻塞、性能高:每个本地事务执行完就立马提交释放底层数据锁,完全不需要像 2PC 那样苦苦等待全局协调。
- 极佳的向后兼容性:非常适合对接那些无法进行 TCC 改造的第三方遗留系统(只要对方能提供一个撤销/退款 API 即可)。
- 适合超长事务:比如一个跨国订单流程可能长达几十分钟甚至几天,Saga 是唯一解。
致命痛点(隔离性缺失):
Saga 最大的架构缺陷在于缺乏隔离性(Isolation),其本质相当于数据库的 Read Uncommitted(读未提交)。
在上面的例子中,当 $T_1$(机票)和 $T_2$(酒店)提交后、且整个 Saga 还没结束前,如果另一个并发用户的请求或者后台跑批任务去查数据,会看到房间已经被订走了(脏读)。如果随后 Saga 发生了回滚执行了 $C_2$,那么之前读到这部分数据的系统就产生了一致性问题。
为了解决缺乏隔离性带来的脏读、脏写问题,架构师通常需要在业务表里增加“中间状态”(比如订单状态标记为“打包中”而不是“已完成”),或者通过业务逻辑进行兜底。
4. 柔性事务 - 异步确保型:可靠消息 / 本地消息表
这种方案的核心是利用消息队列(MQ)和本地关系型数据库的事务来实现解耦和最终一致性。
执行过程:在更新业务数据的同时,在同一个本地数据库事务中插入一条“消息记录”。只要本地事务提交成功,这条消息就一定落盘了。然后通过后台定时任务或 Binlog 监听,将本地消息表中的数据投递到 MQ 中,下游服务消费 MQ 来执行后续业务。
架构特点:彻底的异步化,极大地提升了系统的吞吐量和可用性。
适用场景:上下游服务之间不需要同步返回结果的场景(例如用户注册成功后发放积分、发送欢迎邮件)。
痛点:依赖消息的可靠投递,下游的消费端必须保证接口的绝对幂等性。
5. 柔性事务 - 最大努力通知型 (Best Effort Delivery)
这是可靠消息模式的降级版本,也是最边缘的柔性事务方案。
执行过程:上游系统在业务完成后,尽最大努力将结果通知给下游(比如通过 MQ 重试推送,或者通过 HTTP 接口不断重推,频率按 1s, 5s, 30s, 1m 递减)。如果多次通知依然失败,上游不再主动推送。
架构特点:上游不保证下游一定能收到,但会提供一个对账/查询接口供下游主动来拉取状态。
适用场景:跨主体的系统对接。例如支付宝/微信支付成功后的异步回调通知。
从系统解耦和高并发架构的视角来看,可靠消息/本地消息表模式(在微服务设计模式中通常被称为 Transactional Outbox Pattern,事务发件箱模式)是应用最广泛的柔性事务方案之一。
它的核心哲学是:“上游只管把自己该做的事做完,并通过可靠的机制通知下游,下游就算重试无数次也必须把事办成。” 这种模式彻底放弃了跨服务的同步调用,转而拥抱彻底的异步化和最终一致性。
1. 核心理念:巧妙利用本地数据库事务
在纯粹的微服务环境中,更新本地数据库和发送 MQ(消息队列)消息是两个独立的操作,无法保证原子性(即可能出现数据写成功了但消息没发出去,或者消息发了但数据回滚了)。
本地消息表方案的绝妙之处在于,它利用了关系型数据库自身的本地事务特性,将“业务数据写操作”和“消息记录写操作”绑定在同一个本地事务中。
2. 执行流程:发件箱与异步投递
整个流程通常分为上下游两个阶段:
阶段一:上游业务执行与消息落盘
- 开启本地数据库事务。
- 执行业务操作(例如
INSERT INTO users ...)。 - 在同一个数据库中,向一张专门的“本地消息表”(Outbox Table)插入一条待发送的消息记录(状态为“待发送”)。
- 提交本地事务。由于是同一个数据库,这两步要么同时成功,要么同时失败。这保证了消息绝对不会丢。
阶段二:消息的可靠投递与下游消费
- 消息抓取:上游系统通过一个后台定时任务(轮询本地消息表),或者更现代的做法是利用 CDC 工具(如 Canal、Debezium)监听数据库的 Binlog。
- 投递到 MQ:将抓取到的消息投递到消息队列(如 Kafka, RabbitMQ)。
- 状态更新:投递成功后,将本地消息表中的那条记录状态更新为“已发送”或直接删除。如果投递失败,后台任务会不断重试。
- 下游消费:下游微服务从 MQ 消费这条消息,执行自身的业务逻辑。
3. 一个直观的例子:用户注册发放新手积分
假设我们有 用户服务 和 积分服务。业务要求:用户注册成功后,必须给该用户发放 100 积分。这是一个典型的上下游不需要同步等待的场景。
- 用户服务(上游):
- 接收到注册请求,开启本地 MySQL 事务。
- 向
user表插入一条新用户记录:Aki,ID=1001。 - 向
local_message_outbox表插入一条记录:{"topic": "user_registered", "user_id": 1001, "points": 100},状态为PENDING。 - 提交事务。此时前端直接给用户返回“注册成功”。
- 消息投递中间件:
- 独立的 Relay 进程(或者定时任务)扫描到这条
PENDING消息。 - 将消息发送到 Kafka 的
user_events主题中。 - 收到 Kafka 的 Ack 后,将刚才那条消息记录标记为
DONE。
- 积分服务(下游):
- 监听 Kafka 的
user_events主题,拉取到这条消息。 - 执行本地事务:给 ID=1001 的用户账户增加 100 积分。
- 如果执行失败(比如数据库抖动),利用 Kafka 的消费机制不断重试,直到成功。最终,用户在几秒钟后看到了自己的 100 积分。
4. 架构师视角的 Trade-off (权衡与痛点)
核心优势:
- 极致的可用性和吞吐量:上游业务完全不依赖下游的存活状态。哪怕积分服务宕机了 2 个小时,用户依然可以正常注册。消息会堆积在 MQ 里,等积分服务恢复后慢慢消费。
- 业务解耦:上游不需要知道到底有几个下游需要这条消息(也许明天又加了一个“注册发优惠券”的服务,只需要订阅同一个 MQ Topic 即可)。
致命痛点与技术挑战:
- 下游必须保证绝对的幂等性:由于 MQ 保证的是“至少一次投递 (At-least-once)”,加上重试机制,下游服务可能会多次收到同一条发放积分的消息。积分服务必须在本地建立一张“消息消费防重表”(或利用唯一索引),确保处理过的消息直接返回成功,绝不能给用户发两次积分。
- 轮询数据库的性能损耗:如果使用定时任务高频轮询本地消息表,会对业务数据库造成额外的读取压力。这也是为什么目前主流的高并发架构更倾向于基于 Binlog 的异步解析方案。
很多现代的 MQ 中间件(比如 RocketMQ)已经原生支持了“事务消息”功能,它把本地消息表的设计理念直接固化到了 MQ Server 端,使得开发者甚至不需要建这张本地表了。
总结
从架构选型和工程落地的角度来看,将这五种分布式事务方案进行横向对比,可以帮助我们在面对具体业务场景时做出最合理的权衡。
以下是这五大流派的详细多维度对比表:
| 分布式事务方案 | 一致性级别 | 性能与吞吐量 | 实现难度 | 代码侵入程度 | 可维护性 | 核心痛点与挑战 | 典型适用场景 |
|---|---|---|---|---|---|---|---|
| 1. 2PC / XA (强一致性) | 强一致性 (ACID) | 极低 底层资源被长时间锁定,并发极差。 | 低 大多由数据库或中间件直接支持,开发者感知弱。 | 极低 几乎无需修改业务代码。 | 中 依赖中心化协调者,发生故障时需人工排查死锁状态。 | 同步阻塞严重;单点故障风险;不适合微服务高并发。 | 内部后台管理系统、并发量极低但要求绝对一致的跨库操作。 |
| 2. TCC (业务补偿) | 最终一致性 (BASE) | 高 无长事务数据库锁,并发能力强。 | 极高 需处理幂等、空回滚、防悬挂三大异常。 | 极高 一个接口需拆分为 Try、Confirm、Cancel 三个方法。 | 低 业务逻辑被严重切割,补偿代码庞大,测试与联调成本极高。 | 对开发团队的工程能力要求极高,极其容易写出 Bug。 | 高并发、高频次的核心交易链路(如电商扣减库存、金融转账)。 |
| 3. Saga (长事务型) | 最终一致性 (BASE) | 高 一阶段直接提交本地事务,无阻塞。 | 中等 需编写对应的反向补偿逻辑及状态机流转。 | 高 每个正向操作都必须强制提供补偿接口。 | 中 若使用编排式会极难追踪;使用控制式(状态机)可维护性尚可。 | 缺乏隔离性,易产生脏读/脏写,需在业务侧做状态防重防并发。 | 流程极长、跨越多个第三方系统或遗留系统的业务(如机酒车连环预订)。 |
| 4. 可靠消息 / 本地消息表 | 最终一致性 (BASE) | 极高 彻底异步解耦,上游写完即走。 | 中等 需维护本地消息表,编写后台定时补偿/轮询任务。 | 低 仅在正常业务代码后附加写入消息的操作,下游需做幂等。 | 高 上下游完全解耦,某一方宕机不影响全局链路。 | 高频轮询数据库可能拖垮性能(可通过 Binlog 监听缓解);过度依赖 MQ。 | 上下游无需实时同步结果的场景(如注册发积分、支付后状态扭转)。 |
| 5. 最大努力通知 | 最终一致性 (BASE) | 极高 尽力而为,对主干链路几乎无影响。 | 低 只需提供重试机制和主动查询接口即可。 | 极低 通常作为独立模块存在。 | 极高 设计简单,责任边界清晰(查不到是下游的问题)。 | 实时性差,在极端情况下可能通知失败,强依赖下游主动对账。 | 跨主体的系统对接、边缘非核心业务(如微信支付成功后的异步回调)。 |
架构师选型建议总结:
- 能不用分布式事务,就绝对不用:优先考虑能否通过合并服务、粗粒度 API 或共享数据库来避免跨网络调用。
- 核心链路重异步:如果业务允许延迟,可靠消息/本地消息表 永远是第一选择,因为它的解耦性和吞吐量最优秀。
- 强同步选框架:如果业务必须强同步且对并发要求极高,再考虑引入 Seata(AT 或 TCC 模式),并做好踩坑的准备。