微服务架构下分布式事务解决方案:Seata AT模式与Saga模式实战对比
引言:微服务架构中的分布式事务挑战
在现代软件架构中,微服务已成为构建复杂系统的核心范式。通过将大型单体应用拆分为多个独立部署、自治运行的服务模块,微服务架构显著提升了系统的可维护性、可扩展性和灵活性。然而,这种“分而治之”的设计理念也带来了新的技术难题——分布式事务。
传统单体应用中,事务管理依赖于本地数据库的ACID特性(原子性、一致性、隔离性、持久性),所有操作都在同一个数据库实例内完成,由数据库自身保证事务的一致性。但在微服务架构中,每个服务通常拥有自己的私有数据库,跨服务的数据操作需要跨越多个独立的数据库或消息队列,这使得传统的本地事务机制失效。
例如,在一个电商系统中,用户下单涉及以下关键操作:
- 订单服务创建订单记录
- 库存服务扣减商品库存
- 财务服务扣除用户余额
- 通知服务发送短信提醒
这些操作分布在不同的服务中,分别连接各自的数据库。如果其中任何一个环节失败(如库存不足或余额不足),就必须确保其他已成功执行的操作全部回滚,否则将导致数据不一致。
这就是典型的分布式事务问题:如何在跨服务、跨数据库的场景下,保证多个操作要么全部成功,要么全部失败,从而维持业务逻辑的一致性。
目前主流的分布式事务解决方案包括两阶段提交(2PC)、TCC(Try-Confirm-Cancel)、Saga模式以及基于XA协议的方案。但这些方案各有优劣,尤其在性能、可用性和实现复杂度方面存在明显差异。
在此背景下,Seata(Simple Extensible Autonomous Transaction Architecture)作为一款开源的分布式事务框架,提供了多种模式来应对不同场景下的事务需求。其中,AT模式(Automatic Transaction Mode)和Saga模式是两种最常用的实现方式。它们分别代表了“自动补偿”与“事件驱动补偿”两种思想路径。
本文将从原理剖析、代码实践、性能调优到适用场景等多个维度,对 Seata 的 AT 模式与 Saga 模式进行系统性比较,帮助开发者根据实际业务需求选择最优方案。
Seata AT模式详解:自动化的全局事务管理
原理概述
Seata 的 AT 模式(Auto Transaction Mode)是一种基于代理数据源和全局锁机制的分布式事务解决方案,其核心思想是:无需修改业务代码即可实现分布式事务的自动管理。
AT 模式的设计目标是在保持业务逻辑透明的前提下,通过中间件层自动完成事务的协调与控制。它适用于大多数读写频繁、对性能要求较高的场景,特别适合那些希望“零侵入”地接入分布式事务能力的团队。
核心组件介绍
-
TC(Transaction Coordinator)
全局事务协调器,负责管理全局事务的生命周期,包括注册分支事务、提交/回滚全局事务等。 -
TM(Transaction Manager)
事务管理器,位于客户端应用中,用于发起和控制全局事务,调用 TC 接口进行事务开启、提交、回滚。 -
RM(Resource Manager)
资源管理器,运行在每个微服务中,负责管理本地资源(如数据库连接),并注册为分支事务,向 TC 报告事务状态。 -
Data Source Proxy(数据源代理)
AT 模式的关键所在。Seata 会将原始的数据源替换为一个代理对象(DataSourceProxy),拦截 SQL 执行,并记录前后镜像(before/after image),用于后续的回滚操作。
工作流程解析
以下是 AT 模式下一次完整分布式事务的执行流程:
-
事务开始
TM 向 TC 发起begin请求,获取一个全局事务 ID(XID)。 -
SQL 执行与快照生成
RM 在执行 SQL 前,通过DataSourceProxy拦截请求,读取当前数据行的状态,生成“前镜像”(before image)。执行完 SQL 后,再生成“后镜像”(after image)。 -
分支事务注册
RM 将本次本地事务的信息(包括 XID、数据变更记录、前/后镜像)上报给 TC,注册为一个分支事务。 -
全局提交或回滚
- 若所有分支事务均成功,则 TM 发起
commit请求,TC 通知所有 RM 提交事务。 - 若任一分支失败,则 TM 发起
rollback请求,TC 通知各 RM 执行回滚。
- 若所有分支事务均成功,则 TM 发起
-
回滚机制
RM 收到回滚指令后,使用“前镜像”恢复数据,即把表中对应记录还原成事务开始时的状态。
✅ 关键优势:无需手动编写回滚逻辑,Seata 自动根据前后镜像生成反向 SQL。
实战配置示例
下面以 Spring Boot + MySQL + Seata AT 模式为例,演示完整配置过程。
1. 环境准备
- Java 8+
- MySQL 5.7+
- Nacos 2.x(作为注册中心和配置中心)
- Seata Server 1.5+
2. 添加依赖
<!-- pom.xml -->
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- Seata AT 模式客户端 -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.5.2</version>
</dependency>
<!-- Nacos 注册与配置 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2021.0.5.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2021.0.5.0</version>
</dependency>
</dependencies>
3. 配置文件设置
application.yml
server:
port: 8081
spring:
application:
name: order-service
datasource:
url: jdbc:mysql://localhost:3306/order_db?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
server-addr: localhost:8848
file-extension: yaml
# Seata 配置
seata:
enabled: true
service:
vgroup-mapping:
my_test_tx_group: default
client:
rm:
report-retry-count: 5
report-success-enable: false
tm:
commit-retry-count: 5
rollback-retry-count: 5
undo:
log-store-type: db
log-table: undo_log
registry:
type: nacos
nacos:
application: seata-server
server-addr: localhost:8848
group: SEATA_GROUP
namespace: public
⚠️ 注意:
vgroup-mapping中的my_test_tx_group是自定义事务组名,需与 Seata Server 中配置一致。
bootstrap.yml(优先级更高)
spring:
main:
allow-bean-definition-overriding: true
seata:
enabled: true
tx-service-group: my_test_tx_group
4. 数据库初始化
在 order_db 数据库中创建 undo_log 表(用于存储回滚日志):
CREATE TABLE `undo_log` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`branch_id` BIGINT(20) NOT NULL,
`xid` VARCHAR(100) NOT NULL,
`context` VARCHAR(128) NOT NULL,
`rollback_info` LONGTEXT NOT NULL,
`log_status` INT(11) NOT NULL,
`log_created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`log_modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
5. 业务代码实现
假设我们有一个订单服务,包含以下实体类:
// Order.java
@Data
@TableName("t_order")
public class Order {
@TableId(type = IdType.AUTO)
private Long id;
private String userId;
private String commodityCode;
private Integer count;
private BigDecimal amount;
}
Mapper 接口:
@Mapper
public interface OrderMapper extends BaseMapper<Order> {}
Service 层:
@Service
@Transactional(rollbackFor = Exception.class)
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StockService stockService;
// 下单操作:跨服务调用
public void createOrder(String userId, String commodityCode, Integer count) {
// 1. 创建订单
Order order = new Order();
order.setUserId(userId);
order.setCommodityCode(commodityCode);
order.setCount(count);
order.setAmount(new BigDecimal(count * 10));
orderMapper.insert(order);
// 2. 调用库存服务扣减库存
stockService.deductStock(commodityCode, count);
}
}
注意:此处虽然加了 @Transactional 注解,但不能直接用于分布式事务,因为它是本地事务。真正起作用的是 Seata 的 @GlobalTransactional 注解。
修正后的 Service:
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StockService stockService;
@GlobalTransactional(name = "create-order-tx", timeoutMills = 30000, rollbackFor = Exception.class)
public void createOrder(String userId, String commodityCode, Integer count) {
// 1. 创建订单
Order order = new Order();
order.setUserId(userId);
order.setCommodityCode(commodityCode);
order.setCount(count);
order.setAmount(new BigDecimal(count * 10));
orderMapper.insert(order);
// 2. 调用库存服务扣减库存
stockService.deductStock(commodityCode, count);
}
}
✅
@GlobalTransactional是 Seata 提供的注解,标记该方法为全局事务入口。
6. 完整事务流程验证
当调用 createOrder 方法时,Seata 会:
- 开启全局事务(生成 XID)
- 本地数据库操作被拦截,记录前后镜像
- 远程调用
stockService.deductStock()时,Seata 自动注入 XID 到上下文 - 如果远程服务调用失败,Seata 将触发回滚,使用
undo_log中的前镜像恢复数据
Seata Saga模式详解:事件驱动的长事务处理
原理概述
与 AT 模式强调“自动回滚”不同,Saga 模式采用“补偿事务”的思想,适用于长时间运行、不可中断的业务流程,比如金融支付、审批流、物流调度等。
Saga 模式的核心理念是:不等待所有步骤完成才做决策,而是每一步都立即提交本地事务,一旦某步失败,则执行一系列预定义的补偿操作来回滚前面的成功步骤。
它并不依赖于全局锁或统一的事务协调器,而是通过事件驱动机制(Event Sourcing)来实现最终一致性。
两种 Saga 模式变体
-
Choreography(编排型)
各个服务之间通过发布/订阅事件通信,没有中央控制器。每个服务监听特定事件,决定是否执行下一步或补偿动作。 -
Orchestration(编排型)
存在一个中心化的协调器(Orchestrator),负责按顺序调用各个服务,并管理失败时的补偿逻辑。
Seata 默认支持 Orchestration 模式,即通过一个专门的 Saga 事务协调器来控制整个流程。
工作流程解析
-
启动 Saga 事务
TM 发起start请求,生成一个 Saga 事务 ID。 -
依次执行业务步骤
协调器调用第一个服务的try方法,成功则继续下一个;若失败,则跳转至补偿逻辑。 -
补偿机制触发
若第 N 步失败,协调器调用前 N-1 步的cancel方法,逐级回滚。 -
最终状态
所有补偿完成后,事务结束,系统达到一致状态。
🔄 与 AT 模式对比:Saga 不依赖“前镜像”,而是由开发者显式编写“补偿逻辑”。
实战配置示例
1. 项目结构说明
我们仍使用上述订单系统,但现在模拟一个“支付+发货”流程,该流程持续时间较长,适合 Saga 模式。
2. 添加依赖(同 AT 模式,略)
3. 配置文件更新
在 application.yml 中添加 Saga 相关配置:
seata:
enabled: true
service:
vgroup-mapping:
my_saga_tx_group: default
client:
saga:
enable: true
mode: orchestration
# 可选:指定 Saga 协调器地址
coordinator:
server-addr: localhost:8091
⚠️
mode: orchestration表示使用编排型 Saga。
4. 编写 Saga 事务协调器(Orchestrator)
创建一个 SagaCoordinator 类,负责调度流程:
@Component
public class SagaCoordinator {
@Autowired
private OrderService orderService;
@Autowired
private PaymentService paymentService;
@Autowired
private DeliveryService deliveryService;
/**
* 执行完整的支付发货 Saga 流程
*/
public boolean executePaymentAndDelivery(String orderId, String userId, String address) {
try {
// Step 1: 创建订单
orderService.createOrder(orderId, userId, "COMMODITY_001", 1);
// Step 2: 支付
if (!paymentService.pay(orderId, new BigDecimal(10))) {
throw new RuntimeException("支付失败");
}
// Step 3: 发货
if (!deliveryService.ship(orderId, address)) {
throw new RuntimeException("发货失败");
}
return true; // 成功
} catch (Exception e) {
// 触发补偿
compensate(orderId);
return false;
}
}
/**
* 补偿逻辑:按逆序撤销已执行的操作
*/
private void compensate(String orderId) {
System.out.println("开始补偿流程...");
// 1. 取消发货
deliveryService.cancelShip(orderId);
// 2. 退款
paymentService.refund(orderId);
// 3. 删除订单(可选)
orderService.deleteOrder(orderId);
}
}
5. 各服务实现补偿方法
OrderService.java
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
public void createOrder(String orderId, String userId, String commodityCode, Integer count) {
Order order = new Order();
order.setId(orderId);
order.setUserId(userId);
order.setCommodityCode(commodityCode);
order.setCount(count);
order.setAmount(new BigDecimal(count * 10));
orderMapper.insert(order);
}
public void deleteOrder(String orderId) {
orderMapper.deleteById(orderId);
}
}
PaymentService.java
@Service
public class PaymentService {
public boolean pay(String orderId, BigDecimal amount) {
// 模拟支付接口调用
System.out.println("正在支付订单:" + orderId + ",金额:" + amount);
// 模拟网络延迟或失败
if (Math.random() > 0.8) {
throw new RuntimeException("支付超时");
}
return true;
}
public boolean refund(String orderId) {
System.out.println("正在退款订单:" + orderId);
return true;
}
}
DeliveryService.java
@Service
public class DeliveryService {
public boolean ship(String orderId, String address) {
System.out.println("正在发货订单:" + orderId + ",收货地址:" + address);
if (Math.random() > 0.7) {
throw new RuntimeException("物流公司异常");
}
return true;
}
public boolean cancelShip(String orderId) {
System.out.println("取消发货订单:" + orderId);
return true;
}
}
6. 调用测试
@RestController
@RequestMapping("/api/saga")
public class SagaController {
@Autowired
private SagaCoordinator sagaCoordinator;
@GetMapping("/execute")
public String execute() {
boolean result = sagaCoordinator.executePaymentAndDelivery("ORD001", "U001", "北京市朝阳区");
return result ? "Saga 执行成功" : "Saga 执行失败,已触发补偿";
}
}
访问 /api/saga/execute,若中途某步失败,系统将自动执行补偿逻辑。
AT模式 vs Saga模式:全面对比分析
| 维度 | AT模式 | Saga模式 |
|---|---|---|
| 事务模型 | 基于两阶段提交(2PC)的自动回滚 | 事件驱动的补偿机制 |
| 是否需要修改业务代码 | 否(仅需加注解) | 是(必须编写补偿逻辑) |
| 回滚方式 | 自动根据前后镜像生成反向 SQL | 手动实现 cancel 方法 |
| 性能表现 | 较高(短事务优化好) | 低(长事务开销大) |
| 适用场景 | 短时、高频、强一致性要求 | 长时、异步、最终一致性容忍 |
| 数据库支持 | 支持主流关系型数据库(MySQL/Oracle等) | 无特殊限制,但需保证幂等性 |
| 容错能力 | 依赖 TC 可用性 | 更强(可脱离 TC 独立运行) |
| 实现复杂度 | 低(自动化程度高) | 高(需设计补偿链) |
| 监控与追踪 | 易于通过 Seata Dashboard 查看事务状态 | 需自行集成事件日志跟踪 |
选择建议
| 场景 | 推荐模式 |
|---|---|
| 电商下单、账户转账、库存扣减 | ✅ AT模式 |
| 订单审批流程、多阶段支付、物流调度 | ✅ Saga模式 |
| 事务跨度小于 1s,且需强一致性 | ✅ AT模式 |
| 事务可能持续数分钟甚至数小时 | ✅ Saga模式 |
| 团队缺乏分布式事务经验 | ✅ AT模式 |
| 有成熟事件总线(如 Kafka)支撑 | ✅ Saga模式(Choreography) |
性能调优与最佳实践
AT模式调优策略
-
合理设置超时时间
seata: client: tm: timeout-mills: 30000 -
启用批量提交(Batch Commit)
seata: client: rm: batch-commit-limit: 500 -
优化 Undo Log 表性能
- 添加索引:
(xid, branch_id) - 使用分区表或归档策略处理历史数据
- 添加索引:
-
避免大事务
- 单个事务不要涉及过多表或大量数据
- 分拆为多个小事务
-
关闭不必要的日志输出
logging: level: io.seata: WARN
Saga模式调优策略
-
使用幂等性设计
- 所有
try和cancel方法必须具备幂等性 - 可引入 Redis 或数据库唯一键防止重复执行
- 所有
-
引入重试机制
@Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 1000)) public boolean pay(...) { ... } -
异步执行补偿逻辑
- 将补偿任务放入消息队列(如 RabbitMQ/Kafka),避免阻塞主线程
-
增加事务状态记录表
CREATE TABLE saga_transaction ( id BIGINT PRIMARY KEY, status ENUM('STARTED', 'SUCCESS', 'FAILED', 'COMPENSATING') DEFAULT 'STARTED', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); -
可视化事务流程
- 使用 ELK 或 Prometheus + Grafana 监控 Saga 执行进度
结论与展望
在微服务架构日益普及的今天,分布式事务已成为绕不开的技术挑战。Seata 作为国内领先的分布式事务解决方案,其 AT 模式与 Saga 模式分别代表了“自动化”与“事件驱动”两大方向。
- AT 模式适合大多数常规业务场景,特别是对性能敏感、要求强一致性的系统。它的零侵入性和高自动化程度使其成为入门首选。
- Saga 模式则更适合复杂流程、长时运行的业务,如金融交易、供应链协同等。尽管实现成本较高,但其灵活性和容错能力更强。
未来,随着云原生架构的发展,混合模式(Hybrid Pattern)将成为趋势:结合 AT 的高效性与 Saga 的弹性,利用事件溯源(Event Sourcing)+ CQRS 架构,打造更健壮的分布式系统。
对于开发者而言,理解这两种模式的本质差异,掌握其配置与调优技巧,是构建稳定可靠微服务系统的关键一步。
🔗 参考资料
- Seata 官方文档:https://seata.io/
- Apache ShardingSphere 文档
- 《微服务架构设计模式》(Martin Fowler)
- 《分布式系统:概念与设计》(G. Coulouris)
本文来自极简博客,作者:黑暗征服者,转载请注明原文链接:微服务架构下分布式事务解决方案:Seata AT模式与Saga模式实战对比
微信扫一扫,打赏作者吧~