微服务分布式事务解决方案梳理
核心问题:为什么需要分布式事务?
在单体应用中,我们通常使用本地事务(Local Transaction)和 ACID 特性(原子性、一致性、隔离性、持久性)来保证数据一致性。数据库本身就能处理这一切。
但在微服务架构中,一个业务操作通常会跨越多个服务,每个服务都有自己的独立数据库(这是微服务设计的一个核心原则——数据库按服务拆分)。这就意味着,你无法再用一个本地事务来涵盖整个操作。
例如“创建订单”流程:
1、订单服务:在 orders_db 中创建订单记录,状态为“待支付”。
2、库存服务:在 inventory_db 中扣减相应商品的库存。
3、用户服务:在 users_db 中给用户增加积分。
这三个步骤必须在逻辑上成为一个“事务”:要么全部成功,要么全部失败。如果订单创建成功但库存扣减失败,就会导致超卖。这就是典型的分布式事务问题。
理论基础:CAP 与 BASE 理论
在讨论解决方案前,必须先理解这两个理论,它们是分布式系统设计的基石。
1、CAP 定理
-
一致性 (Consistency):所有节点在同一时间看到的数据是一致的。
-
可用性 (Availability):每个请求都能得到响应(不保证是最新数据)。
-
分区容错性 (Partition Tolerance):系统在遇到网络分区(节点间无法通信)时仍然能继续工作。
-
CAP 定理指出,分布式系统无法同时满足这三个特性,最多只能满足其中两项。 由于网络分区无法避免,所以通常必须在 CP 或 AP之间做选择。
-
这对分布式事务意味着:强一致性(CP)会牺牲一定的可用性,而高可用性(AP)则通常要求我们放弃强一致性,转而追求最终一致性。
2、BASE 理论
-
它是 CAP 定理中 AP 方向的延伸,是对互联网大规模分布式系统的实践总结。
-
基本可用 (Basically Available):系统出现故障时,允许损失部分可用性(如响应时间变长、功能降级)。
-
软状态 (Soft State):允许系统中的数据存在中间状态,并且该中间状态不会影响系统整体可用性。
-
最终一致性 (Eventually Consistent):经过一段时间后,所有副本的数据会达到一致的状态。
-
BASE 理论是最终一致性事务方案的理论基础。
解决方案
分布式事务的解决方案主要分为两大类:强一致性 和 最终一致性。
一、强一致性方案
这类方案追求数据的实时一致性,通常较复杂,性能开销大,适用于金融、支付等对一致性要求极高的场景。
1、两阶段提交 (2PC - Two-Phase Commit)
-
角色:一个协调者 (Coordinator) 和多个参与者 (Participants)(即各个微服务)。
-
阶段一:准备阶段 (Prepare Phase)
-
协调者向所有参与者发送事务内容,询问是否可以提交。
-
参与者执行事务中的所有操作(但不提交),写入 undo/redo 日志,然后锁定资源。
-
参与者回复协调者:“可以提交” (Yes) 或“无法提交” (No)。
-
-
阶段二:提交/回滚阶段 (Commit/Rollback Phase)
-
如果所有参与者都回复 “Yes”:协调者向所有参与者发送 Commit 命令,参与者正式提交事务,释放锁。
-
如果任何参与者回复 “No” 或超时:协调者向所有参与者发送 Rollback 命令,参与者利用 undo 日志回滚事务,释放锁。
-
-
优点:强一致性,概念简单。
-
缺点:
-
同步阻塞:所有参与者在等待协调者指令时都处于阻塞状态,资源被锁定,性能差。
-
单点问题:协调者宕机会导致整个事务阻塞和数据不一致。
-
数据不一致:在第二阶段,如果部分参与者收到 Commit 后宕机,会导致数据不一致。
-
2、三阶段提交 (3PC)
- 2PC 的改进版,引入了超时机制和预提交阶段(CanCommit, PreCommit, DoCommit),减少了阻塞范围,但实现更复杂,且依然无法彻底解决数据不一致问题。实际应用较少。
二、最终一致性方案(推荐)
这是互联网公司更常用的模式,通过补偿机制和消息队列来实现数据的最终一致,保证系统的可用性和性能。
1、TCC (Try-Confirm-Cancel)
-
核心思想:将业务逻辑分为三个操作,由业务代码实现。
-
Try 阶段:资源检查与预留。调用所有服务的 Try 接口,完成业务检查(如检查库存是否充足),并预留关键资源(如冻结库存、冻结优惠券)。
-
Confirm 阶段:确认执行。如果所有服务的 Try 都成功,则进入 Confirm 阶段,调用所有服务的 Confirm 接口,正式提交事务。Confirm 操作需满足幂等性。
-
Cancel 阶段:取消补偿。如果任何服务的 Try 失败,则进入 Cancel 阶段,调用所有服务的 Cancel 接口,释放 Try 阶段预留的资源。Cancel 操作也需满足幂等性。
-
优点:性能比 2PC 高,数据最终一致。
-
缺点:对代码侵入性强,需要为每个业务逻辑设计 Try/Confirm/Cancel 三个接口。需要保证 Confirm 和 Cancel 的幂等性和空回滚(Try未执行,Cancel被调用)的处理。
2、Saga 事务
核心思想:将一个长事务拆分为多个本地短事务,每个短事务都有对应的补偿操作。
执行方式:
-
正向操作:S1 -> S2 -> S3 (例如:创建订单 -> 扣库存 -> 加积分)
-
补偿操作:C1 <- C2 <- C3 (例如:删除订单 -> 回滚库存 -> 扣减积分)
-
协调模式:
-
协同式 (Choreography):每个服务执行完后,产生一个事件来触发下一个服务。如果执行失败,则触发补偿事件。事件流散落在各个服务中,复杂度高。
-
编排式 (Orchestration):引入一个协调器 (Orchestrator),由它集中管理整个事务的执行顺序和补偿逻辑。逻辑更清晰,易于管理和监控。
-
-
优点:一阶段就提交本地事务,无锁,性能高。
-
缺点:不保证隔离性(可能出现“脏读”),补偿逻辑的实现有一定复杂度。
3、基于消息队列的最终一致性
-
核心思想:利用消息队列的可靠性投递和消费者幂等性来保证最终一致。
-
典型模式:本地消息表
1、 业务执行与消息发送在同一个本地事务中完成。- 例如:订单服务在 orders_db 中创建订单记录,同时在同一事务中向一张本地消息表插入一条“库存扣减”消息。
2、有一个后台任务轮询本地消息表,将消息发送到 MQ。
3、库存服务消费 MQ 中的消息,执行扣减库存操作。
4、 执行成功后,发送一条成功应答(或通过更新状态),订单服务收到后可将本地消息标记为“已发送”或删除。
-
如果步骤3失败? 消息会重投,直到成功。因此消费者必须实现幂等(判断是否已处理过该消息)。
-
优点:实现简单,与业务耦合度低,是常用的异步确保型方案。
-
缺点:消息可能会被重复消费,要求消费者幂等。
4、最大努力通知
- 适用于对一致性要求不高的场景,如支付结果通知。服务 A 执行完成后,最大努力地(反复重试)向服务 B 发送通知,直到对方成功返回。即使最终不成功,也提供查询接口,让 B 方主动来对账。这是最终一致性的一种柔性解决方案。
总结与选型建议
方案 | 一致性 | 性能 | 复杂度 | 适用场景 |
---|---|---|---|---|
2PC | 强一致 | 低(同步阻塞) | 中 | 传统银行、内部系统,追求强一致 |
TCC | 最终一致 | 中 | 高(代码侵入) | 金融交易、资金处理,高一致性要求 |
Saga | 最终一致 | 高(无锁) | 中 | 长事务、业务流程多的场景(如旅行订票) |
本地消息表 | 最终一致 | 高(异步) | 低 | 绝大多数业务场景,如扣库存、发积分 |
最大努力通知 | 最终一致 | 高 | 低 | 第三方回调、支付结果通知 |
现代微服务架构下的最佳实践:
1、默认首选最终一致性:在绝大多数业务场景下,最终一致性方案(尤其是基于消息队列的)在性能、可用性和复杂度之间取得了最佳平衡。
2、设计幂等性:无论选择哪种方案,服务接口的幂等性是必须考虑的,以防止重试或消息重复消费导致的数据错乱。
3、业务补偿而非技术回滚:接受“事情可能不会一步到位做完”的现实,通过设计补偿操作(取消订单、回滚库存)来纠正错误,而不是依赖数据库的回滚。
4、查询与操作分离:使用**CQRS(命令查询职责分离)**模式,允许读数据短暂滞后于写数据,更好地接受最终一致性模型。
5、必要时才用强一致性:仅在资金、交易等核心链路且无法接受延时一致的情况下,才考虑使用 TCC 或 Saga 等更重但一致性更强的方案。
以上是对微服务分布式事务一点理解、不足之处希望大牛们指点一二!