Spring Cloud + OpenFeign 架构下,服务间调用导致的分布式事务一致性问题
通用、改动少
背景
- 单体应用下,Spring 的本地事务可以保证一次业务操作的数据一致性。
- 当系统拆分为多个服务(微服务架构)后,一个业务流程可能涉及多个服务,各自有自己的数据源和事务边界。这时本地事务已无法跨服务协作,出现事务一致性难题。
- OpenFeign 作为 HTTP 客户端,仅负责服务间 HTTP 调用,不具备事务能力。

常见的解决方案
分布式事务一般有三大主流解决方案:
A. 柔性事务(最终一致性)
1. 可靠消息+本地事务(推荐)
- 典型代表:
- 阿里巴巴 RocketMQ 事务消息、淘宝 TCC-Transaction,或者使用第三方组件(如 Seata)。
- 思路:
- 服务A完成本地事务后,通过消息中间件将事件通知给服务B,服务B异步消费消息并操作,然后回调确认。
- 追求最终一致性,即允许服务间在"短时间内"状态不一致,但最后会达成一致
- 典型场景:
- 订单创建→扣库存,订单服务本地提交事务→发送消息→库存服务消费扣减库存→最终两者一致
- 优点:业务系统只需在关键点集成下消息发送和消费,改动有限。
- 适用:绝大多数互联网场景。
- 具体的实现流程:
- 本地业务操作+发送消息 同步事务提交
- 订单服务执行"创建订单",同时写入一条"待发送消息"到本地消息表,两步在同一个本地事务中完成。
- 事务成功后,消息通过任务(或轮询机制)推送到消息队列(RabbitMQ/RocketMQ/Kafka)。
- 下游服务(如库存服务)监听消息,完成本地业务操作并记录消费成功(幂等、可重试)。
- 消费成功后发确认消息(可以回查、补偿,如有失败可人工或自动补回)
- 本地业务操作+发送消息 同步事务提交
核心思想:只要本地数据库和消息发送是一致的(在一个事务里),就保证跨服务的最终一致。
- 相关实现:
- RocketMQ 事务消息天然支持这种模式(发送半消息→确认之后再正式发出)。
- 使用RabbitMQ不能天然保证半事务,需要本地消息表+定时补偿投递机制 。
- mysql本地表 + 消息投递+retry 框架
- 也开源的 outbox pattern 组件:如 axon、dtm、spring-transation-mq 等
- Spring Cloud Stream 结合可靠消息表 + 事务模板(见阿里GTS,京东pop,滴滴dtm等实现)。
2. TCC(Try-Confirm-Cancel)
- 通过补偿机制、幂等接口来实现一致性
- 推荐开源实现:tcc-transaction
- 优点:强一致,资源持锁时间短,性能优异
- 缺点:侵入性较大,需要业务方实现Try/Confirm/Cancel三个接口
B. 强一致性(事务性)
1. 分布式事务中间件
- Seata
- Seata实现了三种主流模式:
- AT模式 (自动代理数据库操作,适合简单场景)
- TCC模式(适合复杂业务)
- SAGA模式(适合长事务/批量处理)。
- 注意事项
- Spring Cloud 已有较好的Seata集成:Spring Cloud Alibaba Seata
- Spring Boot/Spring Cloud下集成Seata后,只需要在业务方法上加@GlobalTransactional 即可跨服务实现事务一致,其实就是AT模式
- 改动较小,大体保持原业务不变,只需数据库框架(mybatis/jpa)保证被代理。
- Seata AT 模式原理(自动代理)
- 适用场景:仅限于关系型数据库,适合有独立数据表、无复杂跨库 join 的场景。
- 工作流程 :
- 全局事务TM :业务方法加
@GlobalTransactional
,自动开启分布式事务 - 分支事务RM :各微服务参与该全局事务,Seata通过数据源代理,自动拦截SQL,记录前镜像(before image)和后镜像(after image)。这些数据用于回滚操作。
- 协调者TC:全局事务协调者,维护事务的提交/回滚状态。
- 提交时 ,TC通知各分支事务commit,各自提交; 回滚时,TC通知rollback,各自利用before image反向生成回滚SQL。
- 全局事务TM :业务方法加
- 优势:无需侵入业务代码,适合99%业务场景;适合写已提交/读未提交隔离级别。
- 缺点:不适合复杂SQL、批处理、长耗时业务。
- Seata TCC模式
- 定义Try/Confirm/Cancel三个操作,由应用实现,Seata负责调用和回调。
- 代码实现复杂度高,但能做到强一致且性能尚可。
- Seata SAGA模式
- 业务解耦拆解为本地事务+补偿事务。
- 适合长耗时、弱一致性、多步骤业务
C. SAGA模式(长事务)
什么是SAGA
- 将全局大事务拆成一系列本地事务,每个本地事务完成后马上提交。
- 如果后续任一事务失败,走补偿逻辑回滚已完成的事务(非物理回滚,而是业务反向操作)。
- 适合"长事务"和业务流程编排型服务。
举个例子
- 步骤A(比如订单服务减余额):成功,变更已生效
- 步骤B(扣库存服务):如果失败,调用A的补偿(比如给用户余额+回去)
- 类似流程一直下去,任何环节失败就补偿先前步骤。
两种实现方式
-
协同式 (Choreography):
- 原理: 没有中央协调者。每个服务完成自己的本地事务后,发布一个事件 (Event),触发链中的下一个服务执行其本地事务。失败时,同样通过发布失败事件来触发补偿流程,由监听该事件的服务执行自己的补偿事务。
- 通信: 通常依赖事件总线 或消息队列 (如 RabbitMQ, Kafka)。
- 例子:
- 订单服务创建订单 (T1),发布
OrderCreated
事件。 - 库存服务监听
OrderCreated
,扣减库存 (T2),发布StockDecreased
事件。 - 支付服务监听
StockDecreased
,处理支付 (T3)。如果支付失败,发布PaymentFailed
事件。 - 库存服务监听
PaymentFailed
,执行补偿操作:增加库存 (C2),发布StockRestored
事件。 - 订单服务监听
StockRestored
或PaymentFailed
,执行补偿操作:取消订单 (C1)。
- 订单服务创建订单 (T1),发布
- 优点: 服务松耦合,简单直观(对于简单流程)。
- 缺点:
- 流程分散: 事务逻辑散布在各个服务中,难以跟踪和调试整个 Saga 的状态。
- 循环依赖风险: 服务间可能形成事件监听的循环依赖。
- 难以理解复杂流程: 当 Saga 涉及的服务和步骤增多时,事件链会变得非常复杂。
-
编排式 (Orchestration):
- 原理: 引入一个中央协调器 (Orchestrator) 或 Saga 执行协调器 (SEC)。该协调器负责调用各个服务的本地事务接口,并根据执行结果决定是调用下一个服务的本地事务,还是调用前面服务的补偿事务接口。协调器维护整个 Saga 的状态。
- 通信: 协调器直接向参与方服务发送命令式消息(如 RPC 调用,或通过 MQ 发送命令消息)。
- 例子:
- Saga 协调器调用订单服务的
createOrder
(T1)。 - 成功后,协调器调用库存服务的
decreaseStock
(T2)。 - 成功后,协调器调用支付服务的
processPayment
(T3)。 - 如果
processPayment
失败,协调器会依次调用库存服务的increaseStock
(C2) 和订单服务的cancelOrder
(C1)。
- Saga 协调器调用订单服务的
- 优点:
- 集中控制: 事务逻辑和状态管理集中在协调器,易于理解、监控和调试。
- 职责分离: 参与方服务只需关注自己的业务逻辑和补偿逻辑,不需知道整个 Saga 流程。
- 避免循环依赖: 协调器控制流程,不易产生服务间循环依赖。
- 缺点:
- 协调器复杂性: 协调器本身的设计和实现可能比较复杂,需要处理状态持久化、失败恢复等。
- 协调器单点风险: 协调器可能成为性能瓶颈或单点故障(需要考虑高可用)。
- 额外组件: 引入了额外的协调器组件。
注意点
- 必须为每个可能需要回滚的本地事务设计补偿逻辑
- 补偿事务必须是幂等的,因为可能被重复调用
- Saga 不保证隔离性,在 Saga 执行过程中,系统可能处于中间状态(部分事务已完成,部分未完成),只能保证最终会达到一致状态(要么全部完成,要么全部补偿)
- 对于简单、步骤较少 的 Saga,协同式可能更简单快捷
- 对于复杂、步骤多、需要清晰状态管理 的 Saga,编排式通常是更好的选择。可以使用 Seata Saga 模式实现、Spring Statemachine 或专门的 Saga 编排框架
如何选型
针对场景
- 极高一致性(资金流水、金融系统、券商):建议选 TCC 或 Seata(AT/TCC);最大化减少分布式环节,有条件时局部使用XA
- 高一致+业务较简单:Seata-AT
- 业务复杂、有补偿/逆操作能力:SAGA
- 简单易落地,允许短时不一致:可靠消息+本地事务(消息表+RabbitMQ补偿)
针对改动量
- 以Seata为例,可以做到对现有代码结构最小改动,主要是增加注解和配置,不必重写业务逻辑
- 如果服务间调用是同步,事务度要求很高,Seata AT模式最合适
- 如果可以异步补偿,可靠消息方案或SAGA也考虑。
收尾
微服务分布式事务不是银弹,无需一刀切,优先设计幂等+补偿机制,尽可能"弱化"事务。
确实有强一致场景时,推荐首选Seata,次之为可靠消息,TCC/SAGA视业务复杂性酌情采用。