在微服务架构盛行的今天,分布式事务管理成为了一个必须面对的挑战。传统的单机事务模型在分布式环境下失效,如何保证多个服务间的数据一致性成为了开发者的痛点。Apache Seata 作为一款开源的分布式事务解决方案,为我们提供了优雅的解决方案。本文将深入介绍 Seata 的核心原理、使用方法和最佳实践。
一、分布式事务的挑战
在微服务架构中,一个业务操作可能涉及多个服务的调用,例如:
用户下单 → 创建订单(订单服务)
扣减库存(库存服务)
扣减账户余额(账户服务)
如果在这个过程中任何一个步骤失败,如何保证所有操作的一致性?传统的单机事务(ACID)无法解决这个问题,因为它们跨越了多个服务和数据库。
二、什么是 Seata
2.1 Seata 简介
Seata 是阿里巴巴开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。它支持多种事务模式,包括 AT、TCC、SAGA 和 XA,满足不同场景的需求。
2.2 Seata 核心组件
Seata 定义了三个核心组件,三者协同工作实现分布式事务的协调与管控:
TC (Transaction Coordinator):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚,是 Seata 分布式事务的核心中枢,需单独部署为 Seata Server。
TM (Transaction Manager):事务管理器,定义全局事务的范围,负责开启、提交或回滚全局事务,嵌入在业务应用中(如通过注解触发)。
RM (Resource Manager):资源管理器,管理分支事务的资源,与 TC 交互注册分支事务并汇报分支事务的状态,嵌入在各微服务中,管理本地数据库等资源。
2.3 全局事务 ID
Seata 使用全局唯一的 XID 来标识一个全局事务,XID 贯穿整个分布式事务的生命周期,用于关联全局事务与各个分支事务,确保事务追踪的一致性。
三、Seata 事务模式
Seata 提供四种核心事务模式,可根据业务场景灵活选择,覆盖绝大多数分布式事务需求。
重要说明:四种模式都需要使用 @GlobalTransactional 注解在发起方方法上开启全局事务,Seata 才能协调各分支事务的提交或回滚。
数据可见性总结:
- AT:本地已提交,数据库真实新数据;仅应用内全局事务查询隔离;外部普通事务可见中间脏数据(弱隔离、读未提交级别)
- TCC:本地提交冻结态,不改真实业务数据,外部可见冻结状态
- SAGA:本地提交真实数据,完全对外可见,无任何隔离
- XA:本地不提交,数据库未更新,全局未提交前所有外部都绝对看不见(强隔离)
模式差异总结:Seata 的 4 种模式(AT、TCC、SAGA、XA)整体流程完全一样,都是 TM → TC → RM 这套协作逻辑,区别只在 RM 怎么干活:
- AT 模式
提交:删除对应的 undo log
回滚:根据 undo log 进行镜像回滚 - TCC 模式
提交:执行 Confirm
回滚:执行 Cancel
整个流程围绕 Try - Confirm - Cancel 三个阶段完成。 - SAGA 模式
提交:按业务编排顺序执行正向服务
回滚:通过补偿服务反向执行,实现最终一致性 - XA 模式直接基于数据库原生 XA 协议,由 RM 驱动数据库完成 XA 分支的提交与回滚。
但上层角色不变:
- TM:依然是发起全局事务、管边界;
- TC:依然是协调、存状态、统一下发提交 / 回滚;
- RM:依然是注册分支、执行本地操作。
3.1 AT 模式(自动补偿)
适用场景:关系型数据库(如 MySQL、Oracle),适用于大多数常规业务场景,对业务代码侵入性极低。
核心原理:
业务数据和回滚日志记录在同一个本地事务中,确保本地操作的原子性;
提交阶段:直接提交业务数据,回滚日志保留用于异常回滚;
回滚阶段:根据回滚日志自动执行补偿操作,恢复数据至事务前状态。
优势:使用简单,对业务无侵入,无需修改原有业务代码,性能优异。
3.2 TCC 模式(Try-Confirm-Cancel)
适用场景:非关系型数据库(如 MongoDB、Redis)、特殊业务场景(如自定义资源管控),需要灵活控制事务流程。
核心原理:
Try:资源检查和预留,确保业务操作所需资源可用,并锁定资源;
Confirm:确认执行业务操作,释放锁定的资源,完成最终数据修改;
Cancel:取消执行,释放预留的资源,恢复数据至 Try 操作前状态。
核心要点:TCC 的核心就是「预留 / 冻结资源」,如果去掉这一步,它在机制上确实就和 Saga 基本一样了。
实现方式:
- 每个服务实现三段式:每个参与分布式事务的服务都需要实现 Try、Confirm、Cancel 三个方法
- 业务代码只调用 Try:应用程序只需要调用 Try 方法进行资源预留
-
Seata 自动调度:Seata 根据全局事务的执行状态,自动调度 Confirm 或 Cancel 方法
- 如果所有服务的 Try 都成功,Seata 会调用所有服务的 Confirm 方法
- 如果任何一个服务的 Try 失败,Seata 会调用所有服务的 Cancel 方法
- 重要:只有当 Try 方法抛出异常时才会触发回滚,返回 false 不会触发回滚
优势:灵活性高,可定制性强,支持非关系型数据库,适配复杂业务场景。
3.3 SAGA 模式
适用场景:长事务、业务流程复杂(如跨多个服务的链路操作)、容错要求高的场景。
核心原理:基于状态机定义服务调用流程,支持正向服务和补偿服务,通过状态流转驱动事务推进,若某一步失败则执行对应补偿服务回滚。
优势:适合长事务,容错能力强,可应对服务间长时间交互的场景。
3.4 XA 模式
适用场景:需要强一致性的场景(如金融、支付领域),依赖数据库原生 XA 协议。
核心原理:基于数据库原生 XA 协议,分为准备阶段(所有分支事务准备就绪并汇报状态)和提交阶段(TC 指令所有分支事务统一提交或回滚)。
⚠️ 生产环境警告:Seata XA 模式能用,但生产环境极其不推荐,基本没人用。
- 并发性能差:事务锁持有时间长,高并发下直接卡死;
- MySQL 主从切换有数据不一致风险:这个坑官方都没彻底解决。
优势:强一致性,可靠性高,完全遵循 ACID 特性,适配高一致性要求场景。
四、快速上手:环境搭建
Seata 环境搭建核心是部署 Seata Server(TC),并配置注册中心、配置中心,最终集成业务客户端,以下为基于 Nacos 注册/配置中心的最简搭建流程。
4.1 步骤 1:安装 Seata Server
下载并解压 Seata Server:推荐使用 1.7.1 稳定版本,下载地址可直接通过 GitHub 官方链接获取,执行以下命令完成下载和解压:
# 下载 Seata Server 1.7.1
wget https://github.com/seata/seata/releases/download/v1.7.1/seata-server-1.7.1.tar.gz
# 解压压缩包
tar -zxvf seata-server-1.7.1.tar.gz
# 进入解压后的目录
cd seata-server-1.7.1
4.2 步骤 2:配置注册中心
修改 Seata Server 目录下的 registry.conf 文件,配置 Nacos 作为注册中心,让业务客户端能够发现 TC 服务:
registry {
# 可选类型:file、nacos、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
nacos {
application = "seata-server" # Seata Server 在 Nacos 中的服务名
serverAddr = "127.0.0.1:8848" # Nacos 服务地址(本地部署默认地址)
group = "SEATA_GROUP" # 服务分组,默认 SEATA_GROUP
namespace = "" # Nacos 命名空间,默认为 public
cluster = "default" # 集群名称,默认 default
username = "nacos" # Nacos 登录用户名(默认 nacos)
password = "nacos" # Nacos 登录密码(默认 nacos)
}
}
4.3 步骤 3:配置配置中心
Seata 配置需推送至 Nacos 配置中心,便于集群部署和动态配置,步骤如下:
- 创建
config.txt文件,配置核心参数(最简配置):
# 事务组映射配置,my_test_tx_group 为事务组名称,default 为集群名称
service.vgroupMapping.my_test_tx_group=default
# 存储模式配置,file 表示使用文件存储(开发环境推荐),生产环境推荐使用 db 模式
store.mode=file
# 文件存储目录,用于存储事务日志等数据
store.file.dir=file_store
- 下载 Nacos 配置推送脚本
nacos-config.sh,并执行脚本将配置推送至 Nacos:
# 下载 nacos-config.sh 脚本(官方脚本地址)
wget https://github.com/seata/seata/blob/develop/script/config-center/nacos/nacos-config.sh
# 执行脚本,推送配置至 Nacos(需替换为实际 Nacos 地址和账号密码)
sh nacos-config.sh -h 127.0.0.1 -p 8848 -g SEATA_GROUP -u nacos -w nacos
说明:该脚本会读取 config.txt 中的配置,自动编码并推送至 Nacos 配置中心,推送成功后会提示“Init nacos config finished”。
4.4 步骤 4:启动 Seata Server
进入 Seata Server 目录,执行以下命令启动服务:
# 启动 Seata Server(默认端口 8091)
sh bin/seata-server.sh
启动成功后,可在 Nacos 控制台的“服务列表”中看到 seata-server 服务,说明 TC 部署成功。
五、实战示例:AT 模式
AT 模式是最常用的事务模式,对业务无侵入,以下以“下单-扣库存-扣余额”场景为例,实现 AT 模式分布式事务。
5.1 订单服务(TM 发起全局事务)
undo_log 表:
AT 模式需要在每个微服务的数据库中创建 undo_log 表,用于存储回滚日志。当事务需要回滚时,Seata 会根据这个表中的记录自动执行补偿操作。默认情况下,Seata 使用 undo_log 作为回滚日志表名。如果需要自定义表名,可以在配置文件中进行设置。
表结构及字段说明:
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT, -- 主键ID,自增
`branch_id` bigint(20) NOT NULL, -- 分支事务ID,由Seata Server分配给参与分布式事务的各个微服务的分支事务
`xid` varchar(100) NOT NULL, -- 全局事务ID,贯穿整个分布式事务生命周期
`context` varchar(128) NOT NULL, -- 上下文信息,存储额外的业务相关数据
`rollback_info` longblob NOT NULL, -- 回滚信息,存储事务执行前的数据镜像,用于回滚操作
`log_status` int(11) NOT NULL, -- 日志状态:0-正常,1-已删除
`log_created` datetime NOT NULL, -- 日志创建时间
`log_modified` datetime NOT NULL, -- 日志修改时间
PRIMARY KEY (`id`), -- 主键索引
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) -- 唯一索引,确保每个分支事务只有一条回滚日志
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='AT transaction mode undo table';
表的作用:
存储回滚数据:记录事务执行前的数据状态,当事务需要回滚时,Seata 会根据这些数据恢复到事务前的状态。
确保原子性:与业务操作在同一个本地事务中提交,确保回滚日志的写入与业务操作的原子性。
支持分布式事务回滚:当分布式事务中的任何一个分支事务失败时,TC 会指令所有分支事务执行回滚,通过 undo_log 表中的数据实现自动补偿。
事务追踪:通过 xid 和 branch_id 关联全局事务和分支事务,便于事务状态的追踪和管理。
数据一致性保障:确保在分布式环境下,即使部分服务失败,也能通过回滚机制保证整个系统的数据一致性。
多服务共用数据库的区分:
当多个微服务共用同一个数据库时,undo_log 表会存储所有微服务的回滚日志。Seata 通过以下机制区分不同微服务的回滚日志:
全局事务 XID:每个分布式事务都有一个全局唯一的 XID,所有参与该事务的微服务分支事务都会关联到同一个 XID。
分支事务 Branch ID:每个微服务的分支事务都有一个唯一的 Branch ID,由 Seata Server 分配。Branch ID 与 XID 组合形成唯一标识,确保不同微服务的回滚日志不会混淆。
上下文信息 Context:
context字段可以存储额外的上下文信息,例如微服务名称、业务操作类型等,进一步帮助区分不同微服务的回滚日志。
添加依赖
在 Spring Boot 项目中引入 Seata 客户端依赖(需与 Seata Server 版本匹配):
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
配置文件
修改 application.yaml 配置,关联 Seata 服务:
spring:
cloud:
seata:
tx-service-group: my_test_tx_group # 事务组名称,需与 config.txt 中配置一致
registry:
type: nacos # 注册中心类型
nacos:
server-addr: 127.0.0.1:8848 # Nacos 地址
group: SEATA_GROUP # 与 Seata Server 配置一致
application: seata-server # Seata Server 服务名
data-source-proxy:
undo:
log-table: undo_log # 自定义回滚日志表名
业务代码
通过 @GlobalTransactional 注解定义全局事务边界,发起全局事务:
重要说明:在 Seata AT 模式下,@Transactional 注解在全局事务环境下基本等于无效。即使方法上加了该注解,每执行一次数据库操作依然会立即提交本地事务,每条 SQL 都会产生独立的提交日志并立刻提交事务。
原因就是:
- Seata AT 模式下,本地事务默认自动提交,每条 SQL 立即执行,这是设计特性;
- @Transactional 没有失效,只是失去了本地事务提交控制权,被 Seata 代理接管;
- @GlobalTransactional 才是分布式事务的核心,控制全局提交 / 回滚;
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping("/create")
// 定义全局事务,name 为事务标识,rollbackFor 指定异常回滚条件
@GlobalTransactional(name = "create-order-at", rollbackFor = Exception.class)
public String createOrder(@RequestBody OrderDTO orderDTO) {
orderService.createOrder(orderDTO);
return "订单创建成功";
}
}
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private InventoryFeignClient inventoryFeignClient;
@Autowired
private AccountFeignClient accountFeignClient;
public void createOrder(OrderDTO orderDTO) {
// 1. 创建订单(本地分支事务)
Order order = new Order();
order.setOrderNo(UUID.randomUUID().toString());
order.setUserId(orderDTO.getUserId());
order.setProductId(orderDTO.getProductId());
order.setCount(orderDTO.getCount());
order.setAmount(orderDTO.getAmount());
order.setStatus(1); // 订单状态:1-待支付
orderMapper.insert(order);
// 2. 远程调用库存服务,扣减库存(远程分支事务)
InventoryDTO inventoryDTO = new InventoryDTO();
inventoryDTO.setProductId(orderDTO.getProductId());
inventoryDTO.setCount(orderDTO.getCount());
inventoryFeignClient.deduct(inventoryDTO);
// 3. 远程调用账户服务,扣减余额(远程分支事务)
AccountDTO accountDTO = new AccountDTO();
accountDTO.setUserId(orderDTO.getUserId());
accountDTO.setAmount(orderDTO.getAmount());
accountFeignClient.deduct(accountDTO);
}
}
5.2 库存服务(RM 参与分支事务)
库存服务无需额外配置全局事务注解,仅需实现本地扣减逻辑,Seata 客户端会自动将其注册为分支事务:
@RestController
@RequestMapping("/inventory")
public class InventoryController {
@Autowired
private InventoryService inventoryService;
@PostMapping("/deduct")
public String deduct(@RequestBody InventoryDTO inventoryDTO) {
inventoryService.deduct(inventoryDTO.getProductId(), inventoryDTO.getCount());
return "库存扣减成功";
}
}
@Service
public class InventoryService {
@Autowired
private InventoryMapper inventoryMapper;
public void deduct(Long productId, Integer count) {
// 检查库存是否充足
Inventory inventory = inventoryMapper.selectByProductId(productId);
if (inventory.getStock()< count) {
throw new RuntimeException("库存不足"); // 抛出异常,触发全局回滚
}
// 扣减库存
inventory.setStock(inventory.getStock() - count);
inventoryMapper.updateById(inventory);
}
}
5.3 账户服务(RM 参与分支事务)
与库存服务类似,仅实现本地扣减逻辑,异常时抛出异常触发回滚:
@RestController
@RequestMapping("/account")
public class AccountController {
@Autowired
private AccountService accountService;
@PostMapping("/deduct")
public String deduct(@RequestBody AccountDTO accountDTO) {
accountService.deduct(accountDTO.getUserId(), accountDTO.getAmount());
return "账户扣减成功";
}
}
@Service
public class AccountService {
@Autowired
private AccountMapper accountMapper;
public void deduct(Long userId, BigDecimal amount) {
// 检查余额是否充足
Account account = accountMapper.selectByUserId(userId);
if (account.getBalance().compareTo(amount) < 0) {
throw new RuntimeException("余额不足"); // 抛出异常,触发全局回滚
}
// 扣减余额
account.setBalance(account.getBalance().subtract(amount));
accountMapper.updateById(account);
}
}
说明:AT 模式下,Seata 会自动生成回滚日志,当某一分支事务失败(抛出异常),TC 会指令所有分支事务执行回滚,恢复数据一致性。
六、TCC 模式示例
TCC 模式需要手动实现 Try、Confirm、Cancel 三个方法,适用于非关系型数据库或自定义业务场景,以下以"下单-扣库存-扣余额"场景为例,实现 TCC 模式分布式事务。
重要说明:每个 Try 方法都必须加 @Transactional 注解,Confirm 和 Cancel 方法也最好都加。这不是为了分布式事务,而是为了确保本地数据库操作的原子性。
6.1 订单服务 TCC 实现
订单服务 TCC 接口
@LocalTCC
public interface OrderTccService {
// Try 阶段:创建订单,commitMethod 指定确认方法,rollbackMethod 指定回滚方法
// name 参数:业务动作名称,建议全局唯一,用于标识不同的 TCC 业务操作
// commitMethod:指定 Confirm 阶段的方法名
// rollbackMethod:指定 Cancel 阶段的方法名
@TwoPhaseBusinessAction(name = "tryCreateOrder", commitMethod = "commitCreateOrder", rollbackMethod = "cancelCreateOrder")
// @BusinessActionContextParameter:将参数传递到 BusinessActionContext 中,供 Confirm 和 Cancel 阶段使用
// paramName:参数在 BusinessActionContext 中的键名
boolean tryCreateOrder(@BusinessActionContextParameter(paramName = "order") Order order);
// Confirm 阶段:确认创建订单
boolean commitCreateOrder(BusinessActionContext context);
// Cancel 阶段:回滚订单创建
boolean cancelCreateOrder(BusinessActionContext context);
}
@Service
public class OrderTccServiceImpl implements OrderTccService {
@Autowired
private OrderMapper orderMapper;
@Override
@Transactional(rollbackFor = Exception.class)(rollbackFor = Exception.class)
public boolean tryCreateOrder(Order order) {
// Try 阶段:创建订单,状态设置为待确认
order.setStatus(0); // 0-待确认
boolean result = orderMapper.insert(order) > 0;
if (!result) {
throw new RuntimeException("创建订单失败"); // 抛出异常触发回滚
}
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean commitCreateOrder(BusinessActionContext context) {
// Confirm 阶段:更新订单状态为待支付
Order order = (Order) context.getActionContext("order");
order.setStatus(1); // 1-待支付
return orderMapper.updateById(order) > 0;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean cancelCreateOrder(BusinessActionContext context) {
// Cancel 阶段:删除订单
Order order = (Order) context.getActionContext("order");
return orderMapper.deleteById(order.getId()) > 0;
}
}
订单服务控制器
@RestController
@RequestMapping("/order")
public class OrderTccController {
@Autowired
private OrderTccService orderTccService;
@Autowired
private InventoryFeignClient inventoryFeignClient;
@Autowired
private AccountFeignClient accountFeignClient;
@PostMapping("/create-tcc")
@GlobalTransactional(name = "create-order-tcc", rollbackFor = Exception.class)
public String createOrderTcc(@RequestBody OrderDTO orderDTO) {
// 1. 创建订单(TCC Try 阶段)
Order order = new Order();
order.setOrderNo(UUID.randomUUID().toString());
order.setUserId(orderDTO.getUserId());
order.setProductId(orderDTO.getProductId());
order.setCount(orderDTO.getCount());
order.setAmount(orderDTO.getAmount());
order.setStatus(0); // 0-待确认
orderTccService.tryCreateOrder(order);
// 2. 远程调用库存服务,扣减库存(TCC Try 阶段)
InventoryDTO inventoryDTO = new InventoryDTO();
inventoryDTO.setProductId(orderDTO.getProductId());
inventoryDTO.setCount(orderDTO.getCount());
inventoryFeignClient.deductTcc(inventoryDTO);
// 3. 远程调用账户服务,扣减余额(TCC Try 阶段)
AccountDTO accountDTO = new AccountDTO();
accountDTO.setUserId(orderDTO.getUserId());
accountDTO.setAmount(orderDTO.getAmount());
accountFeignClient.deductTcc(accountDTO);
return "订单创建成功(TCC模式)";
}
}
6.2 库存服务 TCC 实现
@LocalTCC
public interface InventoryTccService {
// Try 阶段:检查并预留库存
@TwoPhaseBusinessAction(name = "tryDeductInventory", commitMethod = "commitDeductInventory", rollbackMethod = "cancelDeductInventory")
boolean tryDeductInventory(@BusinessActionContextParameter(paramName = "productId") Long productId,
@BusinessActionContextParameter(paramName = "count") Integer count);
// Confirm 阶段:确认扣减库存
boolean commitDeductInventory(BusinessActionContext context);
// Cancel 阶段:回滚库存预留
boolean cancelDeductInventory(BusinessActionContext context);
}
@Service
public class InventoryTccServiceImpl implements InventoryTccService {
@Autowired
private InventoryMapper inventoryMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public boolean tryDeductInventory(Long productId, Integer count) {
// 1. 检查库存是否充足
Inventory inventory = inventoryMapper.selectByProductId(productId);
if (inventory.getStock() < count) {
throw new RuntimeException("库存不足");
}
// 2. 预留库存:减少可用库存,增加预留库存
inventory.setStock(inventory.getStock() - count);
inventory.setReservedStock(inventory.getReservedStock() + count);
boolean result = inventoryMapper.updateById(inventory) > 0;
if (!result) {
throw new RuntimeException("扣减库存失败"); // 抛出异常触发回滚
}
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean commitDeductInventory(BusinessActionContext context) {
// Confirm 阶段:减少预留库存(实际扣减)
Long productId = Long.valueOf(context.getActionContext("productId").toString());
Integer count = Integer.valueOf(context.getActionContext("count").toString());
Inventory inventory = inventoryMapper.selectByProductId(productId);
inventory.setReservedStock(inventory.getReservedStock() - count);
return inventoryMapper.updateById(inventory) > 0;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean cancelDeductInventory(BusinessActionContext context) {
// Cancel 阶段:恢复可用库存,减少预留库存
Long productId = Long.valueOf(context.getActionContext("productId").toString());
Integer count = Integer.valueOf(context.getActionContext("count").toString());
Inventory inventory = inventoryMapper.selectByProductId(productId);
inventory.setStock(inventory.getStock() + count);
inventory.setReservedStock(inventory.getReservedStock() - count);
return inventoryMapper.updateById(inventory) > 0;
}
}
@RestController
@RequestMapping("/inventory")
public class InventoryTccController {
@Autowired
private InventoryTccService inventoryTccService;
@PostMapping("/deduct-tcc")
public String deductTcc(@RequestBody InventoryDTO inventoryDTO) {
inventoryTccService.tryDeductInventory(inventoryDTO.getProductId(), inventoryDTO.getCount());
return "库存扣减成功(TCC模式)";
}
}
6.3 账户服务 TCC 实现
@LocalTCC
public interface AccountTccService {
// Try 阶段:资源检查和预留,commitMethod 指定确认方法,rollbackMethod 指定回滚方法
@TwoPhaseBusinessAction(name = "tryDeductBalance", commitMethod = "commitDeductBalance", rollbackMethod = "cancelDeductBalance")
boolean tryDeductBalance(@BusinessActionContextParameter(paramName = "userId") Long userId,
@BusinessActionContextParameter(paramName = "amount") BigDecimal amount);
// Confirm 阶段:确认执行业务,无需额外操作(Try 阶段已完成核心逻辑)
boolean commitDeductBalance(BusinessActionContext context);
// Cancel 阶段:回滚操作,解冻预留的资源
boolean cancelDeductBalance(BusinessActionContext context);
}
@Service
public class AccountTccServiceImpl implements AccountTccService {
@Autowired
private AccountMapper accountMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public boolean tryDeductBalance(Long userId, BigDecimal amount) {
// 1. 检查余额是否充足
Account account = accountMapper.selectByUserId(userId);
if (account.getBalance().compareTo(amount) < 0) {
throw new RuntimeException("余额不足");
}
// 2. 冻结金额(预留资源):余额减少,冻结金额增加
account.setFreezeAmount(account.getFreezeAmount().add(amount));
account.setBalance(account.getBalance().subtract(amount));
boolean result = accountMapper.updateById(account) > 0;
if (!result) {
throw new RuntimeException("冻结账户失败"); // 抛出异常触发回滚
}
return true; // 返回 true 表示 Try 成功
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean commitDeductBalance(BusinessActionContext context) {
// 提交阶段:无需操作,冻结金额已经在 prepare 阶段扣除,确认后无需额外处理
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean cancelDeductBalance(BusinessActionContext context) {
// 回滚阶段:解冻金额,恢复余额
// 从上下文获取 Try 阶段传入的参数
Long userId = Long.valueOf(context.getActionContext("userId").toString());
BigDecimal amount = new BigDecimal(context.getActionContext("amount").toString());
Account account = accountMapper.selectByUserId(userId);
// 解冻:冻结金额减少,余额增加
account.setFreezeAmount(account.getFreezeAmount().subtract(amount));
account.setBalance(account.getBalance().add(amount));
return accountMapper.updateById(account) > 0; // 返回 true 表示回滚成功
}
}
@RestController
@RequestMapping("/account")
public class AccountTccController {
@Autowired
private AccountTccService accountTccService;
@PostMapping("/deduct-tcc")
public String deductTcc(@RequestBody AccountDTO accountDTO) {
accountTccService.tryDeductBalance(accountDTO.getUserId(), accountDTO.getAmount());
return "账户扣减成功(TCC模式)";
}
}
说明:TCC 模式下,TM 同样通过@GlobalTransactional 发起全局事务,TC 协调各分支的 Try、Confirm、Cancel 阶段执行,确保事务一致性。
TCC 模式的重试机制:
Try 阶段:通常不进行重试,因为 Try 操作可能包含业务检查和资源预留,如果重试可能导致重复预留资源(如重复冻结金额)。
Confirm 阶段:需要进行重试,直到成功为止。因为 Confirm 操作是幂等的,多次执行不会产生副作用,确保事务最终提交。
Cancel 阶段:需要进行重试,直到成功为止。因为 Cancel 操作也是幂等的,多次执行不会产生副作用,确保事务最终回滚。
实现重试机制的建议:
幂等设计:确保 Confirm 和 Cancel 方法是幂等的,即多次执行产生相同的结果。
重试策略:不建议设置具体重试次数,建议使用超时时间控制重试,而不是次数。Confirm/Cancel 应保持无限重试,只添加一个最长超时兜底即可:
# seata-server/conf/application.yml
# 全局事务超时时间配置
global:
transactionTimeoutMills: 60000 # 全局事务超时时间(毫秒),默认60秒
# 不设置具体重试次数,使用超时时间控制
# 服务器配置
server:
# 提交(Confirm)最长重试超时时间,毫秒
# 1小时 = 3600000ms
maxCommitRetryTimeout: 3600000
# 回滚(Cancel)最长重试超时时间
maxRollbackRetryTimeout: 3600000
# 恢复机制配置
recovery:
# 回滚中状态重试间隔,默认1s
rollbackingRetryPeriod: 1000
# 提交中状态重试间隔,默认1s
committingRetryPeriod: 1000
业务日志:在 Confirm 和 Cancel 方法中添加详细日志,便于排查重试失败的原因。
超时处理:设置合理的全局事务超时时间,作为重试的最终兜底,避免重试无限期进行。
-
人工补偿:当系统自动重试失败后,需要建立人工补偿机制:
- 事务状态监控:定期扫描超时未完成的事务,识别需要人工干预的事务
- 补偿流程:建立标准化的人工补偿流程,包括事务状态分析、手动执行 Confirm/Cancel 操作
- 操作记录:记录所有人工补偿操作,便于审计和追溯
- 告警机制:当出现大量需要人工补偿的事务时,及时触发告警,通知运维人员
TCC 模式的常见问题
1. 幂等问题
问题:由于网络重试或其他原因,Confirm 或 Cancel 操作可能被多次调用,如果不处理幂等性,可能导致业务逻辑错误(如重复扣钱、重复解冻)。
2. 空回滚问题
问题:当 Try 操作由于网络超时等原因未执行,但 TC 仍然会调用 Cancel 操作,导致 Cancel 操作处理一个不存在的分支事务(不该回滚时别乱回滚)。
3. 悬挂问题
问题:当 Try 操作由于网络延迟等原因后于 Cancel 操作执行,导致 Cancel 操作执行后,Try 操作才开始执行,造成数据不一致(别回滚完了又去冻结)。
TCC 模式的统一解决方案
为了同时解决幂等、空回滚和悬挂问题,建议使用 TCC 事务日志表来统一管理事务状态,所有操作都围绕这张表进行状态判断。
TCC 事务日志表结构:
CREATE TABLE `tcc_transaction_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT, -- 主键ID
`xid` varchar(100) NOT NULL, -- 全局事务ID
`branch_id` bigint(20) NOT NULL, -- 分支事务ID
`business_key` varchar(100) NOT NULL, -- 业务唯一标识(如订单号)
`status` int(11) NOT NULL, -- 事务状态:1-Try成功,2-Confirm成功,3-Cancel成功
`create_time` datetime NOT NULL, -- 创建时间
`update_time` datetime NOT NULL, -- 更新时间
PRIMARY KEY (`id`),
UNIQUE KEY `uk_xid_branch` (`xid`, `branch_id`), -- 确保每个分支事务只有一条记录
KEY `idx_business_key` (`business_key`) -- 业务键索引
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='TCC transaction log table';
基于上述 OrderTccServiceImpl 提供修改示例:
public enum TccStatusEnum {
/**
* Try 已执行
*/
TRY_SUCCESS(1),
/**
* Confirm 已执行
*/
CONFIRM_SUCCESS(2),
/**
* Cancel 已执行
*/
CANCEL_SUCCESS(3);
private final int code;
TccStatusEnum(int code) {
this.code = code;
}
public int getCode() {
return code;
}
}
@Service
public class OrderTccServiceImpl implements OrderTccService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private TccTransactionLogMapper logMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public boolean tryCreateOrder(@BusinessActionContextParameter(paramName = "order") Order order) {
String xid = RootContext.getXID();
long branchId = BranchContext.getBranchId();
// ======================== 防悬挂 ========================
TccTransactionLog log = logMapper.selectByXidAndBranchId(xid, branchId);
if (log != null) {
// 已经 Cancel(空回滚)→ 绝对禁止执行 Try
if (log.getStatus() == TccStatusEnum.CANCEL_SUCCESS.getCode()) {
System.out.println("防悬挂拦截:Cancel已执行,Try拒绝执行");
return false;
}
// 已经执行过 Try → 幂等,返回成功
if (log.getStatus() == TccStatusEnum.TRY_SUCCESS.getCode()) {
return true;
}
}
// Try 业务逻辑
order.setStatus(0);
boolean result = orderMapper.insert(order) > 0;
if (result) {
log = new TccTransactionLog();
log.setXid(xid);
log.setBranchId(branchId);
log.setBusinessKey(order.getOrderNo());
log.setStatus(TccStatusEnum.TRY_SUCCESS.getCode());
log.setCreateTime(new Date());
log.setUpdateTime(new Date());
logMapper.insert(log);
} else {
throw new RuntimeException("创建订单失败");
}
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean commitCreateOrder(BusinessActionContext context) {
String xid = context.getXid();
long branchId = context.getBranchId();
TccTransactionLog log = logMapper.selectByXidAndBranchId(xid, branchId);
if (log == null) {
System.out.println("Confirm:日志不存在");
return false;
}
// 幂等:已 Confirm
if (log.getStatus() == TccStatusEnum.CONFIRM_SUCCESS.getCode()) {
return true;
}
// 只有 Try 成功才能 Confirm
if (log.getStatus() != TccStatusEnum.TRY_SUCCESS.getCode()) {
System.out.println("Confirm:状态非法");
return false;
}
// 执行业务
Order order = (Order) context.getActionContext("order");
order.setStatus(1);
boolean update = orderMapper.updateById(order) > 0;
if (update) {
log.setStatus(TccStatusEnum.CONFIRM_SUCCESS.getCode());
log.setUpdateTime(new Date());
logMapper.updateById(log);
}
return update;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean cancelCreateOrder(BusinessActionContext context) {
String xid = context.getXid();
long branchId = context.getBranchId();
TccTransactionLog log = logMapper.selectByXidAndBranchId(xid, branchId);
// ======================== 空回滚 ========================
if (log == null) {
log = new TccTransactionLog();
log.setXid(xid);
log.setBranchId(branchId);
log.setStatus(TccStatusEnum.CANCEL_SUCCESS.getCode());
log.setCreateTime(new Date());
log.setUpdateTime(new Date());
logMapper.insert(log);
return true;
}
// 幂等:已 Cancel
if (log.getStatus() == TccStatusEnum.CANCEL_SUCCESS.getCode()) {
return true;
}
// 只有 Try 成功才能 Cancel
if (log.getStatus() != TccStatusEnum.TRY_SUCCESS.getCode()) {
System.out.println("Cancel:状态非法");
return false;
}
// 执行业务
Order order = (Order) context.getActionContext("order");
boolean delete = orderMapper.deleteById(order.getId()) > 0;
if (delete) {
log.setStatus(TccStatusEnum.CANCEL_SUCCESS.getCode());
log.setUpdateTime(new Date());
logMapper.updateById(log);
}
return delete;
}
}
执行流程:
- 所有 Try 成功 → 全局提交 → 执行所有 Confirm
- 任意 Try 失败 / 超时 → 全局回滚 → 执行所有 Cancel
- Confirm 和 Cancel 绝对互斥,永远不会同时执行
- Confirm / Cancel 失败,Seata 会自动重试,直到成功
七、SAGA 模式示例
SAGA 模式适用于长事务和复杂业务流程,通过状态机定义服务调用流程,支持正向服务和补偿服务。以下以“下单-扣库存-扣余额”场景为例,实现 SAGA 模式分布式事务。
7.1 SAGA 模式实现
1. 定义状态机配置
@Configuration
public class SagaStateMachineConfig {
@Bean
public StateMachineFactory<String, String, Object> stateMachineFactory() {
// 定义状态机构建器
StateMachineBuilder.Builder<String, String, Object> builder = StateMachineBuilder.builder();
// 定义状态和转换
builder.configureStates()
.withStates()
.initial("START")
.states(EnumSet.of("CREATE_ORDER", "DEDUCT_INVENTORY", "DEDUCT_ACCOUNT", "END"))
.end("END")
.end("ROLLBACK");
// 定义转换
builder.configureTransitions()
// 正向流程
.withExternal()
.source("START").target("CREATE_ORDER").event("CREATE_ORDER")
.and()
.withExternal()
.source("CREATE_ORDER").target("DEDUCT_INVENTORY").event("DEDUCT_INVENTORY")
.and()
.withExternal()
.source("DEDUCT_INVENTORY").target("DEDUCT_ACCOUNT").event("DEDUCT_ACCOUNT")
.and()
.withExternal()
.source("DEDUCT_ACCOUNT").target("END").event("SUCCESS")
// 回滚流程
.and()
.withExternal()
.source("CREATE_ORDER").target("ROLLBACK").event("ROLLBACK_CREATE_ORDER")
.and()
.withExternal()
.source("DEDUCT_INVENTORY").target("ROLLBACK").event("ROLLBACK_DEDUCT_INVENTORY")
.and()
.withExternal()
.source("DEDUCT_ACCOUNT").target("ROLLBACK").event("ROLLBACK_DEDUCT_ACCOUNT");
return builder.build();
}
}
2. 业务服务实现
@Service
public class SagaOrderService {
@Autowired
private StateMachineFactory<String, String, Object> stateMachineFactory;
@Autowired
private OrderMapper orderMapper;
@Autowired
private InventoryFeignClient inventoryFeignClient;
@Autowired
private AccountFeignClient accountFeignClient;
@GlobalTransactional
public void createOrderWithSaga(OrderDTO orderDTO) {
// 创建状态机实例
StateMachine<String, String, Object> stateMachine = stateMachineFactory.getStateMachine(UUID.randomUUID().toString());
try {
// 启动状态机
stateMachine.start();
// 1. 执行创建订单
Order order = new Order();
order.setOrderNo(UUID.randomUUID().toString());
order.setUserId(orderDTO.getUserId());
order.setProductId(orderDTO.getProductId());
order.setCount(orderDTO.getCount());
order.setAmount(orderDTO.getAmount());
order.setStatus(1); // 订单状态:1-待支付
orderMapper.insert(order);
stateMachine.sendEvent(MessageBuilder.withPayload("CREATE_ORDER").build());
// 2. 远程调用库存服务,扣减库存
InventoryDTO inventoryDTO = new InventoryDTO();
inventoryDTO.setProductId(orderDTO.getProductId());
inventoryDTO.setCount(orderDTO.getCount());
inventoryFeignClient.deductSaga(inventoryDTO);
stateMachine.sendEvent(MessageBuilder.withPayload("DEDUCT_INVENTORY").build());
// 3. 远程调用账户服务,扣减余额
AccountDTO accountDTO = new AccountDTO();
accountDTO.setUserId(orderDTO.getUserId());
accountDTO.setAmount(orderDTO.getAmount());
accountFeignClient.deductSaga(accountDTO);
stateMachine.sendEvent(MessageBuilder.withPayload("DEDUCT_ACCOUNT").build());
// 4. 完成事务
stateMachine.sendEvent(MessageBuilder.withPayload("SUCCESS").build());
} catch (Exception e) {
// 根据当前状态执行相应的回滚
String currentState = stateMachine.getState().getId();
switch (currentState) {
case "CREATE_ORDER":
// 回滚订单创建
stateMachine.sendEvent(MessageBuilder.withPayload("ROLLBACK_CREATE_ORDER").build());
break;
case "DEDUCT_INVENTORY":
// 远程调用库存服务,回滚库存扣减
InventoryDTO inventoryDTO = new InventoryDTO();
inventoryDTO.setProductId(orderDTO.getProductId());
inventoryDTO.setCount(orderDTO.getCount());
inventoryFeignClient.rollbackSaga(inventoryDTO);
stateMachine.sendEvent(MessageBuilder.withPayload("ROLLBACK_DEDUCT_INVENTORY").build());
break;
case "DEDUCT_ACCOUNT":
// 远程调用账户服务,回滚账户扣减
AccountDTO accountDTO = new AccountDTO();
accountDTO.setUserId(orderDTO.getUserId());
accountDTO.setAmount(orderDTO.getAmount());
accountFeignClient.rollbackSaga(accountDTO);
stateMachine.sendEvent(MessageBuilder.withPayload("ROLLBACK_DEDUCT_ACCOUNT").build());
break;
}
throw e;
} finally {
stateMachine.stop();
}
}
}
@RestController
@RequestMapping("/order")
public class SagaOrderController {
@Autowired
private SagaOrderService sagaOrderService;
@PostMapping("/create-saga")
public String createOrderSaga(@RequestBody OrderDTO orderDTO) {
sagaOrderService.createOrderWithSaga(orderDTO);
return "订单创建成功(SAGA模式)";
}
}
3. 库存服务 SAGA 实现
@RestController
@RequestMapping("/inventory")
public class InventorySagaController {
@Autowired
private InventoryService inventoryService;
@PostMapping("/deduct-saga")
public String deductSaga(@RequestBody InventoryDTO inventoryDTO) {
inventoryService.deduct(inventoryDTO.getProductId(), inventoryDTO.getCount());
return "库存扣减成功(SAGA模式)";
}
@PostMapping("/rollback-saga")
public String rollbackSaga(@RequestBody InventoryDTO inventoryDTO) {
// 回滚库存扣减,恢复库存
Inventory inventory = inventoryService.getByProductId(inventoryDTO.getProductId());
inventory.setStock(inventory.getStock() + inventoryDTO.getCount());
inventoryService.update(inventory);
return "库存回滚成功(SAGA模式)";
}
}
4. 账户服务 SAGA 实现
@RestController
@RequestMapping("/account")
public class AccountSagaController {
@Autowired
private AccountService accountService;
@PostMapping("/deduct-saga")
public String deductSaga(@RequestBody AccountDTO accountDTO) {
accountService.deduct(accountDTO.getUserId(), accountDTO.getAmount());
return "账户扣减成功(SAGA模式)";
}
@PostMapping("/rollback-saga")
public String rollbackSaga(@RequestBody AccountDTO accountDTO) {
// 回滚账户扣减,恢复余额
Account account = accountService.getByUserId(accountDTO.getUserId());
account.setBalance(account.getBalance().add(accountDTO.getAmount()));
accountService.update(account);
return "账户回滚成功(SAGA模式)";
}
}
八、XA 模式示例
XA 模式适用于需要强一致性的场景,依赖数据库原生 XA 协议,提供最强的数据一致性保证。以下以“下单-扣库存-扣余额”场景为例,实现 XA 模式分布式事务。
8.1 XA 模式实现
1. 配置文件开启 XA 模式
# application.yaml
spring:
cloud:
seata:
tx-service-group: my_test_tx_group
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
data-source-proxy:
mode: XA # 开启 XA 模式
2. 订单服务 XA 实现
@RestController
@RequestMapping("/order")
public class XAOrderController {
@Autowired
private XAOrderService xaOrderService;
@PostMapping("/create-xa")
// 定义全局事务,使用 XA 模式
@GlobalTransactional(name = "create-order-xa", rollbackFor = Exception.class)
public String createOrderXA(@RequestBody OrderDTO orderDTO) {
xaOrderService.createOrder(orderDTO);
return "订单创建成功(XA模式)";
}
}
@Service
public class XAOrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private InventoryFeignClient inventoryFeignClient;
@Autowired
private AccountFeignClient accountFeignClient;
public void createOrder(OrderDTO orderDTO) {
// 1. 创建订单(本地分支事务 - XA)
Order order = new Order();
order.setOrderNo(UUID.randomUUID().toString());
order.setUserId(orderDTO.getUserId());
order.setProductId(orderDTO.getProductId());
order.setCount(orderDTO.getCount());
order.setAmount(orderDTO.getAmount());
order.setStatus(1); // 订单状态:1-待支付
orderMapper.insert(order);
// 2. 远程调用库存服务,扣减库存(远程分支事务 - XA)
InventoryDTO inventoryDTO = new InventoryDTO();
inventoryDTO.setProductId(orderDTO.getProductId());
inventoryDTO.setCount(orderDTO.getCount());
inventoryFeignClient.deductXa(inventoryDTO);
// 3. 远程调用账户服务,扣减余额(远程分支事务 - XA)
AccountDTO accountDTO = new AccountDTO();
accountDTO.setUserId(orderDTO.getUserId());
accountDTO.setAmount(orderDTO.getAmount());
accountFeignClient.deductXa(accountDTO);
}
}
3. 库存服务 XA 实现
@RestController
@RequestMapping("/inventory")
public class XAInventoryController {
@Autowired
private XAInventoryService inventoryService;
@PostMapping("/deduct-xa")
public String deductXA(@RequestBody InventoryDTO inventoryDTO) {
inventoryService.deduct(inventoryDTO.getProductId(), inventoryDTO.getCount());
return "库存扣减成功(XA模式)";
}
}
@Service
public class XAInventoryService {
@Autowired
private InventoryMapper inventoryMapper;
public void deduct(Long productId, Integer count) {
// 检查库存是否充足
Inventory inventory = inventoryMapper.selectByProductId(productId);
if (inventory.getStock() < count) {
throw new RuntimeException("库存不足"); // 抛出异常,触发全局回滚
}
// 扣减库存
inventory.setStock(inventory.getStock() - count);
inventoryMapper.updateById(inventory);
}
}
4. 账户服务 XA 实现
@RestController
@RequestMapping("/account")
public class XAAccountController {
@Autowired
private XAAccountService accountService;
@PostMapping("/deduct-xa")
public String deductXA(@RequestBody AccountDTO accountDTO) {
accountService.deduct(accountDTO.getUserId(), accountDTO.getAmount());
return "账户扣减成功(XA模式)";
}
}
@Service
public class XAAccountService {
@Autowired
private AccountMapper accountMapper;
public void deduct(Long userId, BigDecimal amount) {
// 检查余额是否充足
Account account = accountMapper.selectByUserId(userId);
if (account.getBalance().compareTo(amount) < 0) {
throw new RuntimeException("余额不足"); // 抛出异常,触发全局回滚
}
// 扣减余额
account.setBalance(account.getBalance().subtract(amount));
accountMapper.updateById(account);
}
}
九、生产环境部署
开发环境的单机部署无法满足生产环境的高可用需求,生产环境需部署 Seata 集群、注册中心集群和数据库主从,确保服务稳定运行。
9.1 集群部署
生产环境推荐使用 DB 模式存储事务数据(替代单机的 file 模式),确保集群数据一致性,修改 file.conf 配置:
store {
mode = "db" # 存储模式:db(数据库),生产环境推荐
db {
datasource = "druid" # 数据源类型
dbType = "mysql" # 数据库类型
driverClassName = "com.mysql.jdbc.Driver" # 数据库驱动
url = "jdbc:mysql://127.0.0.1:3306/seata?useSSL=false" # 数据库地址(需提前创建 seata 数据库)
user = "root" # 数据库用户名
password = "123456" # 数据库密码
}
}
部署多个 Seata Server 节点,指定不同端口,组成集群:
# 节点 1:端口 8091
sh bin/seata-server.sh -h 127.0.0.1 -p 8091
# 节点 2:端口 8092
sh bin/seata-server.sh -h 127.0.0.1 -p 8092
# 节点 3:端口 8093
sh bin/seata-server.sh -h 127.0.0.1 -p 8093
所有节点配置相同的 Nacos 注册中心和 DB 存储,Nacos 会自动将多个 Seata Server 节点识别为一个集群,实现负载均衡和故障转移。
9.2 高可用性配置
Nacos 集群:部署多个 Nacos 节点,避免注册中心单点故障,确保 Seata Server 和业务客户端能够正常注册和发现。
Seata 集群:至少部署 2 个 Seata Server 节点,实现故障转移,某一节点宕机后,其他节点可继续提供服务。
数据库主从:配置 MySQL 主从复制,Seata 数据库的主库宕机后,从库可切换为新主库,确保事务数据不丢失。
十、最佳实践
在实际项目中,合理运用 Seata 的特性,结合业务场景优化设计,可在保证数据一致性的同时,提升系统性能和稳定性。
10.1 事务边界设计
最小化事务范围:只包含必要的业务操作,避免将无关操作纳入分布式事务,减少事务执行时间。
避免长事务:将大事务拆分为多个小事务,避免事务长时间占用资源,降低超时和锁冲突风险。
合理设置超时时间:根据业务执行时间,设置合适的
globalTransactionTimeout参数,避免事务卡住。
10.2 性能优化
优先使用 AT 模式:AT 模式对业务无侵入,性能优于 TCC、XA 模式,适合大多数常规业务场景。
合理设置资源锁定时间:避免长时间锁定数据库资源,减少锁冲突,提升并发能力。
使用异步处理:非核心操作(如日志记录、消息通知)使用消息队列异步处理,不纳入分布式事务范围。
10.3 监控和告警
集成 Prometheus:监控 Seata 服务的运行状态(如事务成功率、超时率、节点负载),实时掌握系统情况。
配置告警:设置事务失败率、超时、节点宕机等告警规则,及时发现并处理异常。
日志管理:集中管理事务日志(如全局事务 XID、分支事务状态、回滚日志),便于问题排查。
十一、常见问题与解决方案
在 Seata 使用过程中,常见问题主要集中在事务超时、网络异常和数据一致性,以下为具体解决方案。
11.1 事务超时
问题:事务执行时间过长,超过默认超时时间,导致全局事务回滚。
解决方案:
优化业务逻辑,减少事务执行时间(如拆分大事务、优化数据库查询);
合理设置
globalTransactionTimeout参数,延长超时时间(需结合业务实际);将大事务拆分为多个小事务,降低单个事务的执行时间。
11.2 网络异常
问题:服务间网络波动,导致分支事务状态无法正常上报,或 TC 指令无法正常下发,造成事务状态不一致。
解决方案:
配置服务调用重试机制(如 Feign 重试),应对短暂网络波动;
使用可靠的网络环境,避免网络中断、延迟过高;
实现业务幂等性设计,避免重试导致的数据重复操作。
11.3 数据一致性
问题:极端场景下(如 TC 宕机、数据库异常),出现最终数据不一致。
解决方案:
使用可靠的消息队列(如 RocketMQ、Kafka),实现最终一致性补偿;
实现对账机制,定期比对各服务的数据,发现不一致时手动修复;
开启 Seata 的日志恢复机制,TC 重启后可恢复未完成的事务。
十二、总结
Seata 为分布式事务提供了一套完整的解决方案,支持 AT、TCC、SAGA、XA 四种事务模式,可灵活适配不同业务场景,解决微服务架构下的数据一致性难题。
通过本文的介绍,你应该已经掌握了:
Seata 的核心架构(TC、TM、RM)和工作原理;
四种事务模式的使用场景和实现方式;
Seata 环境搭建(Server 部署、注册/配置中心配置)和代码实现;
生产环境集群部署和高可用配置;
常见问题的排查和解决方案。
在实际项目中,应根据业务场景选择合适的事务模式,结合消息队列、缓存、数据库主从等技术,构建可靠、高性能的分布式系统。同时,关注 Seata 开源项目的更新,及时升级版本,享受更完善的功能和更稳定的性能。
Seata 作为一个活跃的开源项目,正在不断完善和发展,为微服务架构下的分布式事务管理提供更加成熟的解决方案。