分布式事务常见解决方案总结
分布式事务常见解决方案总结
在微服务架构和分布式系统中,分布式事务是一个无法回避的核心问题。传统的单机事务(ACID)无法满足跨数据库、跨服务的事务一致性需求。本文整理了 30+ 道分布式事务相关的“学习”题,涵盖理论基础、常见解决方案及实战场景。
基础理论
1. 什么是分布式事务?为什么需要分布式事务?
📝 通俗解释
就像**“结婚”**。
- 本地事务:你自己买个冰淇淋,一手交钱一手交货,自己就能决定。
- 分布式事务:结婚是两个家庭的事。男方(系统A)出房,女方(系统B)出车。
- 必须双方都准备好,才能领证(提交事务)。
- 如果女方反悔了,男方也不能单方面领证,必须把房子收回来(回滚)。
在微服务里,一个下单操作可能涉及“扣库存(库存服务)”、“扣余额(账户服务)”、“生成订单(订单服务)”,这几个服务都在不同的服务器上,必须要么一起成功,要么一起失败。
- 定义:分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。简单来说,就是一次大的操作由不同的小操作组成,这些小操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。
- 原因:
- 数据库分库分表:当数据库单表数据量过大进行拆分后,原本在同一个数据库的事务操作变成了跨数据库的操作。
- SOA/微服务架构:业务被拆分成多个服务(如订单服务、库存服务、积分服务),一个业务流程需要调用多个服务,每个服务操作自己的数据库,本地事务无法保证全局一致性。
2. CAP 定理是什么?分布式事务中如何权衡?
- C (Consistency) 一致性:在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
- A (Availability) 可用性:在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
- P (Partition tolerance) 分区容错性:以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在 C 和 A 之间做出选择。
- 权衡:在分布式系统中,P 是必须满足的。因此只能在 CP 和 AP 之间做选择。
- CP(强一致性):如 Zookeeper,牺牲可用性,保证数据一致。银行转账等场景通常要求强一致。
- AP(高可用性):如 Eureka、Nacos(AP模式),牺牲一致性(保证最终一致性),保证服务高可用。互联网业务(如点赞、评论)通常优先选择 AP。
3. 什么是 BASE 理论?
BASE 是 Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent(最终一致性)的缩写。
- 基本可用:分布式系统在出现不可预知故障的时候,允许损失部分可用性(如响应时间增长、功能降级)。
- 软状态:允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
- 最终一致性:系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。
4. 刚性事务与柔性事务的区别?
- 刚性事务:遵循 ACID 原则,强一致性。通常基于 XA 协议(如 2PC),由数据库层面保证。
- 柔性事务:遵循 BASE 理论,最终一致性。通常在业务层面实现,如 TCC、Saga、消息队列等。
常见解决方案
5. 介绍一下 2PC(两阶段提交)的流程。
- 阶段一:准备阶段 (Prepare):事务协调者(Coordinator)向所有参与者(Participant)发送事务内容,询问是否可以提交事务,并等待所有参与者答复。参与者执行本地事务操作(写 Undo/Redo Log),但不提交。
- 阶段二:提交阶段 (Commit):
- 如果所有参与者都返回 Yes,协调者发送 Commit 请求,参与者正式提交事务,释放锁资源。
- 如果任一参与者返回 No 或超时,协调者发送 Rollback 请求,参与者回滚事务,释放锁资源。
6. 2PC 有什么缺点?
📝 通俗解释
- 太慢(同步阻塞):队长喊“预备”的时候,所有人都得保持姿势不能动,也不能干别的事。
- 队长挂了(单点故障):队长喊完“预备”突然晕倒了,队员们不知道该跑还是该散,只能一直傻站着。
- 部分跑(数据不一致):队长喊“跑”,前排听到了跑了,后排没听到还在等,结果队伍乱了。
- 同步阻塞:执行过程中,所有参与者都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。
- 单点故障:协调者是核心,一旦挂掉,参与者会一直阻塞。尤其是阶段二,协调者挂了,参与者不知道该提交还是回滚。
- 数据不一致:阶段二中,如果协调者只向部分参与者发送了 Commit 请求就挂了,导致部分参与者提交,部分未提交。
- 保守策略:没有完善的超时机制,任意一个节点失败都会导致整个事务失败。
7. 3PC(三阶段提交)是对 2PC 的哪些改进?
📝 通俗解释
2PC 是“直接喊预备”。3PC 是先问一句“大家都在吗?”,然后再喊预备。
- 多了一个阶段(CanCommit):先确认大家都活着,网络都通,再开始锁资源。减少了无谓的资源浪费。
- 超时机制:如果队长晕了,队员等了一会儿没听到指令,就自动提交(虽然可能出错,但比一直傻站着强)。
3PC 将 2PC 的准备阶段一分为二,形成了 CanCommit、PreCommit、DoCommit 三个阶段。
- 改进点:
- 引入超时机制:在参与者和协调者中都引入超时机制,解决了 2PC 的单点故障导致的阻塞问题。
- 减少阻塞:CanCommit 阶段不执行事务,只是询问,减少了资源锁定时间。
- 缺点:虽然降低了阻塞,但数据不一致问题依然存在(如 PreCommit 阶段协调者挂了,参与者超时未收到 DoCommit 消息,会自动提交,若此时协调者本意是回滚,则导致不一致)。
8. 什么是 TCC 补偿性事务?
📝 通俗解释
就像**“买机票”**。
- Try(尝试):选座,系统先把这个座位锁住(预留资源)。
- Confirm(确认):付款成功,系统正式出票(扣减资源)。
- Cancel(取消):付款超时或失败,系统把锁住的座位释放出来给别人买(释放资源)。
TCC 是 Try-Confirm-Cancel 的缩写,属于应用层的两阶段提交。
- Try:尝试执行业务,完成所有业务检查(一致性),预留必须业务资源(准隔离性)。
- Confirm:确认执行业务,不作任何业务检查,只使用 Try 阶段预留的业务资源。Confirm 操作满足幂等性。
- Cancel:取消执行业务,释放 Try 阶段预留的业务资源。Cancel 操作满足幂等性。
9. TCC 的优缺点是什么?
- 优点:
- 性能高:不持有数据库连接锁,事务粒度小。
- 控制力强:业务逻辑完全由业务层控制,可以实现自定义的补偿逻辑。
- 缺点:
- 侵入性强:每个业务逻辑都需要实现 Try、Confirm、Cancel 三个接口,代码量大。
- 开发成本高:需要处理幂等性、空回滚、悬挂等异常情况。
10. TCC 中如何解决“空回滚”问题?
- 现象:Try 阶段因为网络问题没收到,或者 Try 阶段失败了,事务管理器直接调用 Cancel。此时 Try 没执行,Cancel 却执行了。
- 解决:在 Cancel 接口中,先查询 Try 是否执行成功(如查询事务记录表),如果 Try 没执行,则 Cancel 不做任何操作,直接返回成功。
11. TCC 中如何解决“幂等”问题?
- 现象:Confirm 或 Cancel 阶段由于网络重试,可能被调用多次。
- 解决:通过事务 ID + 状态机(或唯一索引)来保证 Confirm 和 Cancel 的幂等性。执行前检查事务状态,如果已经执行过,直接返回成功。
12. TCC 中如何解决“悬挂”问题?
📝 通俗解释
现象:退款(Cancel)比下单(Try)先到。
- Try 请求堵在路上了。
- 事务管理器等不及了,发了 Cancel(执行成功)。
- 这时候迟到的 Try 到了,如果执行了,就**“悬”在那了(因为不会再有 Cancel 来清理它了,资源永远被锁住)。
解决:下单(Try)前先查一下“有没有被取消过”**。如果发现已经有对应的 Cancel 记录,就拒绝下单。
- 现象:Try 阶段请求网络拥堵超时,事务管理器认为失败,触发了 Cancel。之后 Try 请求才到达。此时 Cancel 已经执行过(释放了资源),Try 再执行(预留资源)就会导致资源被锁住无法释放(因为不会再有 Cancel 了)。
- 解决:在 Try 方法中,检查当前事务 ID 是否已经执行过 Cancel。如果已经执行过 Cancel,则 Try 拒绝执行。
13. 什么是本地消息表方案?
📝 通俗解释
就像**“记在本子上”**。
- 我要给你转账,但我怕银行系统挂了。
- 我先把“要给B转账100元”这件事,写在我自己的笔记本(本地数据库表)上,同时扣掉我的钱。这俩操作在一个本地事务里,绝对安全。
- 然后我有个定时闹钟(定时任务),每隔几分钟看一眼笔记本,把还没办的事去办了(发消息给B)。
- B 办完了告诉我一声,我再把笔记本上的这行划掉。
- 原理:利用本地数据库事务,将业务操作和写入消息表放在同一个事务中提交。然后通过定时任务轮询消息表,将消息推送到 MQ。下游服务消费 MQ 成功后,回调通知上游删除消息或更新消息状态。
- 流程:
- A 服务:开启事务 -> 执行业务 -> 插入消息表 -> 提交事务。
- A 服务:定时任务轮询消息表 -> 发送 MQ。
- B 服务:消费 MQ -> 执行业务(幂等) -> 发送成功确认。
- A 服务:收到确认 -> 删除消息/更新状态。
- 优点:实现简单,不依赖分布式事务中间件。
- 缺点:与业务库耦合,高并发下数据库压力大。
14. 什么是 RocketMQ 事务消息?
RocketMQ 提供了事务消息功能,类似于“二阶段提交”。
- 流程:
- 发送方发送 Half Message(半消息),MQ 接收成功但不投递给消费方。
- 发送方执行本地事务。
- 根据本地事务结果,发送 Commit 或 Rollback 给 MQ。
- Commit:MQ 将消息投递给消费方。
- Rollback:MQ 删除消息。
- 回查机制:如果 MQ 没收到 Commit/Rollback(如发送方挂了),MQ 会回调发送方检查本地事务状态。
- 优点:解耦了业务和消息发送,保证了最终一致性。
15. 什么是 Saga 事务?
Saga 是一种长事务解决方案,将长事务拆分为多个本地短事务。
- 模式:每个本地事务都有一个对应的补偿事务。
- 恢复策略:
- 向后恢复(Backward Recovery):如果某个步骤失败,逆序执行前面所有已完成步骤的补偿操作(类似回滚)。
- 向前恢复(Forward Recovery):如果某个步骤失败,不断重试该步骤或执行备用逻辑,直到成功(适用于必须要成功的场景)。
- 适用场景:业务流程长、业务参与者多、对一致性要求不高的场景。
16. Seata 是什么?有哪些模式?
Seata 是阿里开源的一站式分布式事务解决方案。
- AT 模式:基于 XA 演进,无侵入,自动化。核心是 Undo Log。一阶段提交业务数据和 Undo Log,二阶段自动回滚或删除 Undo Log。
- TCC 模式:同标准 TCC,需要手动实现三个接口。
- Saga 模式:长事务解决方案,基于状态机引擎。
- XA 模式:利用数据库本身的 XA 协议支持,无侵入,但性能较差。
17. Seata AT 模式的工作原理?
- 一阶段:
- 解析 SQL,查询更新前的数据(Before Image)。
- 执行 SQL,更新数据。
- 查询更新后的数据(After Image)。
- 将 Before Image 和 After Image 组装成 Undo Log,与业务数据在同一个事务中提交。
- 二阶段(提交):异步删除 Undo Log。
- 二阶段(回滚):利用 Before Image 还原数据。回滚前会校验 Dirty Write(脏写),对比当前数据库数据和 After Image 是否一致,不一致说明被人工或其他事务修改过,需人工介入。
18. Seata AT 模式如何解决脏写(Dirty Write)问题?
📝 通俗解释
就像**“抢厕所”。
虽然是分布式,但 AT 模式引入了全局锁**。
事务 A 在修改这行数据前,必须先去 TC(事务协调者)那领一把锁。
如果事务 B 也想改这行数据,去领锁时发现被 A 领走了,B 就得在门口排队等待。
只有等 A 提交了或回滚了,把锁还回去,B 才能拿到锁去修改。
这样就防止了 A 改了一半被 B 覆盖掉。
- 全局锁:Seata 在一阶段提交前,会向 TC(事务协调者)申请全局锁(基于主键)。如果拿不到锁,会重试。只有拿到全局锁,才能提交本地事务。
- 校验:回滚时会校验数据库当前值是否等于 After Image,如果不同,说明数据被中间修改了,产生脏写。
实战场景
19. 订单支付场景,如何保证最终一致性?
📝 通俗解释
就像**“点外卖”**。
- 你付完钱(支付服务),订单状态变“已支付”。
- 这时候不需要让骑手立刻出现在你家门口。
- 系统发个消息给商家(库存服务)和骑手(配送服务)。
- 商家和骑手收到消息后,慢慢做饭、慢慢送。
- 只要保证最后饭能送到你手上就行(最终一致性)。
- 场景:用户支付成功后,需要更新订单状态、扣减库存、增加积分、通知发货。
- 方案:使用 RocketMQ 事务消息或本地消息表。
- 支付服务收到回调,开启本地事务:更新订单状态为“已支付”,记录消息到本地表(或发送 Half Message)。
- 事务提交后,发送消息到 MQ。
- 库存服务、积分服务、发货服务订阅 MQ,消费消息执行各自逻辑。
- 消费端需实现幂等性(防止重复消费)。
- 如果消费失败,MQ 会重试,直到成功或进入死信队列(人工处理)。
20. 跨银行转账场景,适合用什么分布式事务方案?
📝 通俗解释
适合用 TCC 或 消息队列。
- TCC:像**“转账确认”**。
- A 银行先冻结 100 块(Try)。
- B 银行说收到通知了。
- A 银行正式扣款(Confirm)。
- 消息队列:像**“汇款单”**。
- A 银行扣了钱,开张汇款单寄给 B 银行。
- B 银行收到单子,给你加钱。
- 方案:TCC 或 消息队列。
- TCC:
- Try:A 银行冻结 100 元,B 银行不操作。
- Confirm:A 银行扣除冻结的 100 元,B 银行增加 100 元。
- Cancel:A 银行解冻 100 元。
- 消息队列:
- A 银行扣钱,发送消息。B 银行收消息加钱。需考虑 B 银行加钱失败的情况(通常银行内部系统会保证成功,或人工对账)。
21. 分布式事务中,如何保证接口的幂等性?
- 数据库唯一索引:利用数据库主键或唯一约束,重复插入会报错。
- Token 机制:操作前先申请 Token,执行时校验并删除 Token。
- 分布式锁:Redis / Zookeeper 锁,同一 RequestID 只能执行一次。
- 状态机:根据业务状态判断,如订单状态只能从“未支付”变为“已支付”,重复请求无法更新。
22. 最大努力通知(Best Effort Notification)是什么?
- 定义:发起通知方尽最大的努力将业务处理结果通知到接收方,但不保证一定成功。
- 特点:
- 有一定的重试机制(如 1s, 5s, 10s, 30s...)。
- 提供查询接口,允许接收方主动查询结果(校对机制)。
- 场景:微信/支付宝支付回调。
23. 分布式事务方案的选型原则?
- 刚性事务(2PC/XA):并发量不大,对一致性要求极高,企业内部系统。
- TCC:并发量大,对实时性要求高,业务逻辑可拆分(Try/Confirm/Cancel)。
- RocketMQ 事务消息/本地消息表:并发量大,允许异步,对最终一致性敏感,如电商下单。
- Saga:业务流程长,跨系统多。
- Seata AT:新项目,希望开发成本低,对性能要求不是极度苛刻(相比 TCC)。
24. 在微服务中,为什么不建议过度依赖分布式事务?
📝 通俗解释
因为**“太贵了”**。
- 性能差:本来一个本地事务 1ms 搞定,分布式事务要网络通信、要等锁,可能要 100ms。
- 容易崩:涉及的系统越多,出问题的概率越大。只要一个环节卡住,整个链条都得等着。
- 太复杂:开发、调试、排错都让人头秃。
能拆分服务边界避免分布式事务,就尽量避免。
- 性能损耗:分布式事务涉及网络通信、锁资源等待,性能远低于本地事务。
- 复杂性:引入了额外的组件(协调者、MQ)和异常处理逻辑(幂等、回滚、补偿),增加了系统复杂度。
- 可用性风险:协调者故障或中间件故障可能影响整个业务。
- 建议:尽量通过业务设计避免分布式事务(如合理划分微服务边界),或使用最终一致性方案。
25. Seata 的 GlobalLock 是什么?
📝 通俗解释
就像**“VIP通道”。
有时候我只是想简单修改一下表里的数据,不想开启一个巨大的分布式事务(太重了)。
但我又怕我改的时候,正好有个分布式事务也在改,导致冲突。
所以我用@GlobalLock:“我就改一下,不搞大事务,但我会先看一眼有没有人在搞大事务,有我就等等,没有我就改。”**
这样既轻量,又安全。
- 作用:用于在非 Seata 全局事务中,访问被 Seata 全局事务管理的表时,防止脏读/脏写。
- 场景:一个老系统(不走 Seata)和一个新系统(走 Seata)操作同一张表。新系统加了 GlobalLock,老系统直接 update 可能会导致脏写。Seata 提供了
@GlobalLock注解,让本地事务在提交前也去尝试获取全局锁,获取不到则等待,从而实现隔离。
26. 什么是 LCN 模式?
📝 通俗解释
就像**“假装提交”**。
LCN 是 Lock, Confirm, Notify 的缩写。
- 阶段一:大家各自执行 SQL,但是执行完了不提交(Hold 住数据库连接)。
- 阶段二:队长说“都成功了”,大家再一起真的提交。
- 缺点:一直占着数据库连接不放,人多了数据库就崩了。
LCN (Lock, Confirm, Notify) 是一种不创建 Undo Log 的分布式事务模式(早期框架 TX-LCN)。
- 原理:代理数据库连接,事务提交时不真正提交,而是假提交(Hold 住连接)。等协调者通知所有服务都成功了,再真正提交。
- 缺点:连接占用时间长,性能差,容易导致数据库连接池耗尽。
27. 消息队列实现最终一致性时,如何处理消费失败?
- 重试:MQ 自身提供的重试机制。
- 死信队列:重试 N 次后依然失败,进入死信队列。
- 人工干预:监控死信队列,人工排查原因(如业务数据异常),修正后重新投递或手动补偿。
28. 为什么 Seata AT 模式需要主键?
- Seata AT 模式在生成 Undo Log 时,需要根据主键来记录数据的 Before Image 和 After Image。
- 在回滚时,也需要根据主键去还原数据。
- 全局锁也是基于 TableName + PK 构建的。
- 因此,使用 Seata AT 模式的表必须有主键。
29. 什么是“这种活儿得用第三方库(如 ASM, Javassist),现在 JDK 自带了”指的是什么(结合 Java 新特性)?
注:此题虽然偏 Java 新特性,但在分布式框架底层实现中常用
- 指的是 JDK 22 预览的 Class-File API。
- 分布式事务框架(如 Seata)通常需要通过字节码增强(代理)来拦截 JDBC 操作,以前依赖 ASM/Javassist,未来可以直接用 JDK 原生 API,更标准稳定。
30. 总结一下分布式事务的几种一致性模型。
📝 通俗解释
- 强一致性:“立刻”。我改了,你马上就能查到。绝不忽悠。(2PC)
- 弱一致性:“随缘”。我改了,你能不能查到,看运气。
- 最终一致性:“早晚”。我改了,你现在可能查不到,但过一会儿(几秒、几分钟)肯定能查到。(MQ, TCC)
- 强一致性:读操作一定能读取到最近一次写操作的数据(2PC, Paxos, Raft)。
- 弱一致性:数据更新后,不承诺立即能读到最新值,也不承诺多久能读到。
- 最终一致性:弱一致性的一种特例,保证在一定时间后,所有节点数据达成一致(DNS, Gossip, TCC, MQ)。
31. 什么是 XA 协议?
📝 通俗解释
就像**“USB接口标准”**。
X/Open 组织规定:数据库如果要支持分布式事务,必须提供xa_prepare,xa_commit,xa_rollback这些接口。
MySQL、Oracle 这些数据库大厂都按这个标准实现了接口,所以它们都能参与 XA 事务。
- 定义:X/Open 组织定义的分布式事务处理标准。
- 角色:AP(应用程序)、RM(资源管理器,如数据库)、TM(事务管理器)。
- 接口:
xa_start,xa_end,xa_prepare,xa_commit,xa_rollback。 - 特点:强一致性,阻塞协议,性能较差。
32. 什么是 GTS (Global Transaction Service)?
- 定义:阿里云提供的全局事务服务,Seata 的商业版。
- 特点:高性能、高可用、无侵入。支持 AT, TCC, Saga, XA 多种模式。
33. 分布式事务和本地事务的区别?
- 范围:本地事务局限于单个数据库实例;分布式事务跨越多个数据库或服务。
- ACID:本地事务严格遵守 ACID;分布式事务通常遵守 BASE,牺牲 C 换取 A。
- 性能:本地事务性能高;分布式事务性能低(网络开销、锁)。
34. 微服务架构下,如何处理跨库关联查询?
- 字段冗余:在从表冗余主表字段,避免关联。
- 数据同步:将多个库的数据同步到 ES 或宽表(HBase/Doris)进行查询。
- 应用层组装:分别查询不同服务,在代码中进行数据拼装(字段少时)。
35. 为什么说 TCC 是业务层面的两阶段提交?
📝 通俗解释
- 2PC:是数据库在干活。数据库帮你锁行、帮你提交、帮你回滚。你代码里看不到。
- TCC:是你在干活。Try, Confirm, Cancel 都是你写的 Java 方法。你想锁 Redis 也行,锁 MongoDB 也行,发个邮件也行。
所以 TCC 更灵活,但也更累。
- 2PC:是资源层(数据库)的提交,对业务无侵入。
- TCC:是业务层(代码)的提交,Try, Confirm, Cancel 都是业务逻辑代码。
- 灵活性:TCC 可以跨数据库、跨服务、跨非关系型存储(如 Redis),灵活性比 2PC 高。
总结:分布式事务没有“银弹”,只有最适合业务场景的方案。在电商等高并发场景下,“柔性事务 + 最终一致性”(如 MQ 事务消息、TCC)是主流选择。
