springboot 业务系统和流程系统两个微服务之间的事务如何保障
在 Spring Boot 微服务架构中,业务系统和流程系统之间的事务保障核心是解决分布式事务问题(跨服务数据一致性)。由于微服务独立部署、数据隔离,传统单机事务(ACID)无法直接生效,需采用分布式事务方案。以下是结合实际场景的落地方案,从简单到复杂、从弱一致性到强一致性逐步说明:
一、核心问题与设计原则
1. 核心矛盾
- 跨服务调用:业务系统(如订单服务)调用流程系统(如审批流程服务),两个服务操作各自数据库,无法用单一事务控制。
- 网络不可靠:服务调用可能超时、宕机,导致一方提交、一方未提交的 “数据不一致”。
2. 设计原则
- 优先选最终一致性:大多数业务场景(如订单创建 + 流程启动)无需实时强一致,最终一致即可降低系统复杂度。
- 避免长事务:微服务事务应 “短平快”,减少锁竞争和超时风险。
- 失败可补偿:设计 “回滚 / 补偿机制”,确保异常时数据能恢复或修正。
二、落地方案(按优先级排序)
方案 1:本地消息表 + 定时任务(最常用,最终一致性)
原理
基于 “可靠消息投递” 思想:业务系统执行本地事务时,同时写入 “消息表”(与业务表同库,利用本地事务保证原子性),再通过定时任务将消息投递到流程系统,流程系统执行完成后回调确认,未确认的消息重试。
流程拆解(以 “订单创建后启动审批流程” 为例)
| 步骤 | 业务系统(订单服务) | 流程系统(审批服务) |
|---|---|---|
| 1 | 开启本地事务:
|
- |
| 2 | 提交本地事务(订单和消息表原子提交,要么都成功) | - |
| 3 | 定时任务(如 Quartz、Spring Scheduler)扫描 “待发送” 消息,调用流程系统接口 | 接收消息,开启本地事务:
|
| 4 | 流程系统执行成功后,调用业务系统 “消息确认接口” | 提交本地事务 |
| 5 | 业务系统更新消息表状态为 “已确认” | - |
| 6 | 失败重试:定时任务对 “待发送” 且超时的消息重试(设置重试次数 / 间隔,避免死循环) | 幂等处理:接收重复消息时,通过订单 ID 判断流程是否已创建,避免重复执行 |
关键实现要点
- 消息表设计:
id(主键)、business_id(业务 ID,如订单 ID)、message_type(消息类型)、status(待发送 / 发送中 / 已确认 / 失败)、content(消息内容 JSON)、retry_count(重试次数)、create_time、update_time。 - 幂等性:流程系统必须通过
business_id做幂等校验(如查询订单对应的流程是否已存在),避免重复创建流程。 - 重试策略: exponential backoff(指数退避),如 10s、30s、1min、5min,重试 3-5 次后标记为 “失败”,人工介入。
优点
- 实现简单,无中间件依赖,稳定性高(依赖数据库本地事务)。
- 适合中小规模系统、非核心链路(如通知、日志同步)。
缺点
- 侵入业务代码(需手动操作消息表)。
- 一致性延迟:依赖定时任务间隔,存在短暂数据不一致。
方案 2:RocketMQ/RabbitMQ 事务消息(最终一致性,无侵入)
原理
利用中间件的 “事务消息” 特性,替代方案 1 的 “本地消息表”,将消息投递与本地事务绑定,无需手动维护消息表,降低代码侵入性。
核心流程(以 RocketMQ 为例)
- 业务系统发送 “半事务消息”:向 RocketMQ 发送一条 “未确认” 的消息(半消息),RocketMQ 收到后存储,但不投递到消费者(流程系统)。
- 业务系统执行本地事务:插入订单表(待审批),执行成功 / 失败。
- 业务系统提交 “消息确认”:
- 本地事务成功:向 RocketMQ 发送 “确认投递” 指令,RocketMQ 将消息标记为可消费,流程系统接收。
- 本地事务失败:向 RocketMQ 发送 “取消投递” 指令,RocketMQ 删除半消息。
- RocketMQ 回查机制:若业务系统未返回确认(如宕机),RocketMQ 会定时回查业务系统的事务状态,根据结果决定是否投递。
- 流程系统消费消息:接收消息后,创建流程实例,执行本地事务,完成后返回消费成功(RocketMQ 删除消息);失败则重试(需幂等处理)。
关键实现要点
- 依赖中间件支持:RocketMQ 原生支持事务消息,RabbitMQ 需通过 “死信队列 + 确认机制” 模拟。
- 事务回查接口:业务系统需提供回查接口(如
/transaction/check?businessId=xxx),RocketMQ 通过该接口确认事务状态。 - 幂等性:流程系统同样需通过
businessId校验,避免重复消费。
优点
- 无代码侵入(无需手动维护消息表),开发效率高。
- 一致性比方案 1 更及时(无需定时任务扫描,回查机制保障)。
缺点
- 依赖中间件(需部署 RocketMQ),增加系统复杂度。
- 适合核心链路(如订单创建、支付回调)。
方案 3:Saga 模式(长事务 / 复杂流程,最终一致性)
适用场景
当业务流程包含多个跨服务步骤(如 “订单创建→流程启动→库存扣减→支付确认”),且每个步骤都有 “正向操作” 和 “反向补偿操作” 时,适合用 Saga 模式。
原理
将分布式事务拆分为多个 “本地事务”(每个服务的操作),每个本地事务对应一个 “补偿事务”(回滚操作),通过事件驱动或编排模式执行,若某一步失败,触发前面所有步骤的补偿操作,恢复数据一致性。
两种实现方式
(1)编排式 Saga(推荐,适合流程固定场景)
由一个 “Saga 协调器”(如 Spring Cloud Stream、Seata Saga)统一调度所有服务的步骤,流程清晰,易于维护。
流程示例(订单 + 流程 + 库存):
- 协调器调用业务系统:执行 “创建订单”(本地事务成功)。
- 协调器调用流程系统:执行 “启动流程”(本地事务成功)。
- 协调器调用库存系统:执行 “扣减库存”(本地事务成功)。
- 若步骤 3 失败(如库存不足),协调器触发补偿:
- 调用流程系统:“撤销流程”(补偿事务)。
- 调用业务系统:“取消订单”(补偿事务)。
(2) choreography 式 Saga(无协调器,适合灵活场景)
每个服务完成本地事务后,发送事件通知下一个服务执行,失败时发送补偿事件,由其他服务监听并执行补偿。
流程示例:
- 业务系统创建订单→发送
OrderCreatedEvent。 - 流程系统监听
OrderCreatedEvent→启动流程→发送ProcessStartedEvent。 - 库存系统监听
ProcessStartedEvent→扣减库存→发送StockDeductedEvent。 - 若库存扣减失败→发送
StockDeductFailedEvent。 - 流程系统监听
StockDeductFailedEvent→撤销流程→发送ProcessCancelledEvent。 - 业务系统监听
ProcessCancelledEvent→取消订单。
关键实现要点
- 补偿事务设计:每个正向操作必须有对应的反向操作(如 “启动流程”→“撤销流程”,“创建订单”→“取消订单”),补偿操作需保证幂等性。
- 状态追踪:需记录 Saga 执行状态(如 “订单创建成功→流程启动中→库存扣减失败”),便于问题排查和手动干预。
- 技术选型:
- 编排式:Seata Saga(轻量级,支持 XML / 注解配置)、Camunda(流程引擎,适合复杂流程)。
- choreography 式:Spring Cloud Stream(事件驱动)、Kafka(消息队列)。
优点
- 支持长事务、复杂跨服务流程。
- 失败后通过补偿机制恢复一致性,不阻塞业务。
缺点
- 设计复杂(需维护正向 / 反向操作)。
- 仅支持最终一致性,不适合强一致场景。
方案 4:2PC/3PC(强一致性,不推荐微服务)
原理
- 2PC(两阶段提交):分为 “准备阶段” 和 “提交阶段”,由事务协调器统一控制所有服务:
- 准备阶段:协调器向业务系统、流程系统发送 “准备指令”,两服务执行本地事务(不提交),返回 “就绪” 或 “失败”。
- 提交阶段:若所有服务就绪,协调器发送 “提交指令”;若任一服务失败,发送 “回滚指令”。
- 3PC(三阶段提交):在 2PC 基础上增加 “预提交阶段”,降低阻塞风险,但复杂度更高。
为什么不推荐微服务?
- 阻塞问题:准备阶段所有服务持有数据库锁,若某服务宕机,其他服务会一直阻塞,导致系统可用性极低。
- 一致性风险:协调器宕机后,可能出现部分服务提交、部分回滚的情况。
- 微服务理念冲突:微服务强调 “去中心化”,2PC/3PC 需要强中心化协调,违背微服务设计原则。
适用场景
仅适合数据库层面紧密耦合的场景(如单体拆分为微服务过渡期),纯微服务架构中几乎不用。
三、技术选型建议
| 方案 | 一致性 | 复杂度 | 适用场景 | 技术栈 |
|---|---|---|---|---|
| 本地消息表 + 定时任务 | 最终一致(延迟低) | 低 | 中小系统、非核心链路 | Spring Boot + 数据库 + Quartz |
| RocketMQ 事务消息 | 最终一致(延迟极低) | 中 | 核心链路、无代码侵入需求 | Spring Boot + RocketMQ |
| Saga 模式 | 最终一致(支持复杂流程) | 高 | 长事务、多步骤跨服务流程 | Seata Saga / Camunda + Spring Cloud Stream |
| 2PC/3PC | 强一致 | 中高 | 数据库紧耦合场景(不推荐) | Spring Cloud Alibaba Seata(支持 2PC) |
四、关键保障措施(无论选哪种方案)
1. 幂等性设计(必须实现)
- 所有跨服务接口必须支持幂等,避免重复执行导致数据错误。
- 实现方式:通过
businessId(如订单 ID)作为唯一标识,查询操作是否已执行(如流程系统查询 “订单 ID=123 的流程是否存在”)。
2. 超时处理
- 服务调用设置超时时间(如 Feign 调用设置
ribbon.ReadTimeout=3000ms),避免无限阻塞。 - 超时后默认视为失败,触发重试或补偿机制。
3. 日志与监控
- 记录分布式事务关键日志(如消息发送 / 接收、事务状态、补偿执行结果),便于问题排查。
- 监控事务成功率、重试次数、失败率(如 Prometheus + Grafana),异常时告警(如钉钉、邮件)。
4. 手动干预机制
- 对于重试多次仍失败的事务(如流程启动失败),需提供手动处理入口(如后台管理系统的 “重试流程”“取消订单” 按钮),避免数据长期不一致。
五、代码示例(方案 2:RocketMQ 事务消息)
1. 依赖引入(Spring Boot + RocketMQ)
xml
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.2.3</version>
</dependency>
2. 业务系统配置(生产者)
yaml
rocketmq:
name-server: 127.0.0.1:9876
producer:
group: order-transaction-group # 事务生产者组
3. 业务系统事务消息发送
java
运行
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
@Service
public class OrderService {
@Resource
private OrderMapper orderMapper; // 订单DAO
@Resource
private RocketMQTemplate rocketMQTemplate;
// 发送事务消息+执行本地事务
public void createOrderAndStartProcess(OrderDTO orderDTO) {
// 1. 发送半事务消息(topic: process_topic,tag: start)
String businessId = orderDTO.getOrderId(); // 业务唯一ID
String messageContent = JSON.toJSONString(orderDTO); // 消息内容
rocketMQTemplate.sendMessageInTransaction(
"process_topic:start", // topic+tag
MessageBuilder.withPayload(messageContent)
.setHeader(RocketMQHeaders.TRANSACTION_ID, businessId)
.build(),
orderDTO // 传递给本地事务的参数
);
}
// 2. 本地事务执行逻辑(由RocketMQ回调)
@Transactional
public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
OrderDTO orderDTO = (OrderDTO) arg;
try {
// 执行本地事务:插入订单表
Order order = new Order();
order.setOrderId(orderDTO.getOrderId());
order.setStatus("PENDING_APPROVAL"); // 待审批
orderMapper.insert(order);
return RocketMQLocalTransactionState.COMMIT; // 本地事务成功,确认投递
} catch (Exception e) {
return RocketMQLocalTransactionState.ROLLBACK; // 本地事务失败,取消投递
}
}
// 3. 事务回查逻辑(RocketMQ回查时调用)
public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
String businessId = msg.getHeaders().get(RocketMQHeaders.TRANSACTION_ID).toString();
// 回查订单状态:是否已创建
Order order = orderMapper.selectById(businessId);
if (order != null) {
return RocketMQLocalTransactionState.COMMIT; // 订单已创建,确认投递
} else {
return RocketMQLocalTransactionState.ROLLBACK; // 订单未创建,取消投递
}
}
}
4. 流程系统消费消息(消费者)
java
运行
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
@Service
@RocketMQMessageListener(
topic = "process_topic",
selectorExpression = "start", // 只消费tag=start的消息
consumerGroup = "process-consumer-group"
)
public class ProcessConsumer implements RocketMQListener<String> {
@Resource
private ProcessMapper processMapper; // 流程DAO
@Override
@Transactional
public void onMessage(String message) {
OrderDTO orderDTO = JSON.parseObject(message, OrderDTO.class);
String businessId = orderDTO.getOrderId();
// 幂等校验:是否已创建流程
ProcessInstance process = processMapper.selectByBusinessId(businessId);
if (process != null) {
return; // 已创建,直接返回
}
// 执行本地事务:创建流程实例
ProcessInstance newProcess = new ProcessInstance();
newProcess.setBusinessId(businessId);
newProcess.setProcessType("ORDER_APPROVAL");
newProcess.setStatus("RUNNING"); // 流程运行中
processMapper.insert(newProcess);
}
}
总结
- 大多数场景优先选择RocketMQ 事务消息(无侵入、一致性及时)或本地消息表(简单稳定),满足最终一致性需求。
- 复杂跨服务流程(多步骤、需补偿)选择Saga 模式,通过正向 / 反向操作保障一致性。
- 强一致性需求极少(微服务架构不推荐),若必须,可考虑 Seata 的 2PC 模式,但需接受可用性降低的代价。
- 无论哪种方案,幂等性、超时处理、日志监控是必做的基础保障,否则会出现数据不一致或排查困难的问题。

浙公网安备 33010602011771号