深入理解 Seata:分布式事务的最佳实践

在微服务架构盛行的今天,分布式事务管理成为了一个必须面对的挑战。传统的单机事务模型在分布式环境下失效,如何保证多个服务间的数据一致性成为了开发者的痛点。Apache Seata 作为一款开源的分布式事务解决方案,为我们提供了优雅的解决方案。本文将深入介绍 Seata 的核心原理、使用方法和最佳实践。

一、分布式事务的挑战

在微服务架构中,一个业务操作可能涉及多个服务的调用,例如:

  1. 用户下单 → 创建订单(订单服务)

  2. 扣减库存(库存服务)

  3. 扣减账户余额(账户服务)

如果在这个过程中任何一个步骤失败,如何保证所有操作的一致性?传统的单机事务(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),适用于大多数常规业务场景,对业务代码侵入性极低。

核心原理

  1. 业务数据和回滚日志记录在同一个本地事务中,确保本地操作的原子性;

  2. 提交阶段:直接提交业务数据,回滚日志保留用于异常回滚;

  3. 回滚阶段:根据回滚日志自动执行补偿操作,恢复数据至事务前状态。

优势:使用简单,对业务无侵入,无需修改原有业务代码,性能优异。

3.2 TCC 模式(Try-Confirm-Cancel)

适用场景:非关系型数据库(如 MongoDB、Redis)、特殊业务场景(如自定义资源管控),需要灵活控制事务流程。

核心原理

  1. Try:资源检查和预留,确保业务操作所需资源可用,并锁定资源;

  2. Confirm:确认执行业务操作,释放锁定的资源,完成最终数据修改;

  3. 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 配置中心,便于集群部署和动态配置,步骤如下:

  1. 创建 config.txt 文件,配置核心参数(最简配置):
# 事务组映射配置,my_test_tx_group 为事务组名称,default 为集群名称
service.vgroupMapping.my_test_tx_group=default
# 存储模式配置,file 表示使用文件存储(开发环境推荐),生产环境推荐使用 db 模式
store.mode=file
# 文件存储目录,用于存储事务日志等数据
store.file.dir=file_store
  1. 下载 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';

表的作用

  1. 存储回滚数据:记录事务执行前的数据状态,当事务需要回滚时,Seata 会根据这些数据恢复到事务前的状态。

  2. 确保原子性:与业务操作在同一个本地事务中提交,确保回滚日志的写入与业务操作的原子性。

  3. 支持分布式事务回滚:当分布式事务中的任何一个分支事务失败时,TC 会指令所有分支事务执行回滚,通过 undo_log 表中的数据实现自动补偿。

  4. 事务追踪:通过 xid 和 branch_id 关联全局事务和分支事务,便于事务状态的追踪和管理。

  5. 数据一致性保障:确保在分布式环境下,即使部分服务失败,也能通过回滚机制保证整个系统的数据一致性。

多服务共用数据库的区分

当多个微服务共用同一个数据库时,undo_log 表会存储所有微服务的回滚日志。Seata 通过以下机制区分不同微服务的回滚日志:

  1. 全局事务 XID:每个分布式事务都有一个全局唯一的 XID,所有参与该事务的微服务分支事务都会关联到同一个 XID。

  2. 分支事务 Branch ID:每个微服务的分支事务都有一个唯一的 Branch ID,由 Seata Server 分配。Branch ID 与 XID 组合形成唯一标识,确保不同微服务的回滚日志不会混淆。

  3. 上下文信息 Contextcontext 字段可以存储额外的上下文信息,例如微服务名称、业务操作类型等,进一步帮助区分不同微服务的回滚日志。

添加依赖

在 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 操作也是幂等的,多次执行不会产生副作用,确保事务最终回滚。

实现重试机制的建议:

  1. 幂等设计:确保 Confirm 和 Cancel 方法是幂等的,即多次执行产生相同的结果。

  2. 重试策略:不建议设置具体重试次数,建议使用超时时间控制重试,而不是次数。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
  1. 业务日志:在 Confirm 和 Cancel 方法中添加详细日志,便于排查重试失败的原因。

  2. 超时处理:设置合理的全局事务超时时间,作为重试的最终兜底,避免重试无限期进行。

  3. 人工补偿:当系统自动重试失败后,需要建立人工补偿机制:

    • 事务状态监控:定期扫描超时未完成的事务,识别需要人工干预的事务
    • 补偿流程:建立标准化的人工补偿流程,包括事务状态分析、手动执行 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 四种事务模式,可灵活适配不同业务场景,解决微服务架构下的数据一致性难题。

通过本文的介绍,你应该已经掌握了:

  1. Seata 的核心架构(TC、TM、RM)和工作原理;

  2. 四种事务模式的使用场景和实现方式;

  3. Seata 环境搭建(Server 部署、注册/配置中心配置)和代码实现;

  4. 生产环境集群部署和高可用配置;

  5. 常见问题的排查和解决方案。

在实际项目中,应根据业务场景选择合适的事务模式,结合消息队列、缓存、数据库主从等技术,构建可靠、高性能的分布式系统。同时,关注 Seata 开源项目的更新,及时升级版本,享受更完善的功能和更稳定的性能。

Seata 作为一个活跃的开源项目,正在不断完善和发展,为微服务架构下的分布式事务管理提供更加成熟的解决方案。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容