分布式事务Seata_分布式 事务

分布式事务Seata_分布式 事务

编程文章jaq1232025-10-23 3:59:457A+A-

Seata 是一款开源的分布式事务解决方案,提供了 AT(自动补偿型)、TCC(手动补偿型)、Saga(长事务模式) 等模式,能有效地解决微服务架构下的数据一致性问题。下面我用 Java 代码实例(主要基于 Spring Cloud Alibaba)带你快速上手 Seata 的两种核心模式,并聊聊它们的特点。

先了解 Seata 的几种模式

Seata 主要支持三种分布式事务模式,它们各有适用场景:

模式

原理

优点

缺点

适用场景

AT

两阶段提交,自动生成反向 SQL 回滚

对业务代码几乎零侵入

依赖数据库支持,全局锁

大部分业务场景,快速集成

TCC

两阶段提交,需要手动实现 Try/Confirm/Cancel 方法

性能好,无全局锁

代码侵入性强,实现复杂

对性能、一致性要求高的业务

Saga

长事务模型,通过状态机和事件补偿实现

适用于长时间的分布式事务

需保证补偿逻辑必然成功

业务流程长、无需强一致性的场景

环境准备

在使用以下代码前,请确保你的项目中已引入 Seata 依赖。以 Maven 为例:

<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.4.2</version> <!-- 请使用最新稳定版本 -->
</dependency>

并在 application.yml 中配置 Seata:

spring:
  cloud:
    alibaba:
      seata:
        tx-service-group: my_test_tx_group # 事务组名称,需与Seata Server配置匹配
seata:
  enabled: true
  application-id: your-application-name
  tx-service-group: my_test_tx_group
  registry:
    type: nacos # 以Nacos为例
    nacos:
      server-addr: localhost:8848
  config:
    type: nacos

代码示例

1. AT 模式(自动补偿)

AT 模式是 Seata 最常用的模式,通过代理数据源生成反向 SQL 实现自动回滚。

只需在全局事务的入口方法上添加 @GlobalTransactional 注解:

import io.seata.spring.annotation.GlobalTransactional;

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;
    
    @Autowired
    private InventoryService inventoryService;

    // 开启全局事务
    @GlobalTransactional(name = "createOrder", rollbackFor = Exception.class)
    public void createOrder(Order order) {
        // 1. 本地事务:创建订单
        orderMapper.insert(order);
        
        // 2. 远程调用:扣减库存(Feign调用)
        inventoryService.deduct(order.getProductId(), order.getCount());
        
        // 3. 模拟异常,触发全局回滚
        // int i = 1 / 0; 
    }
}

说明

  • Seata 会自动在分支事务注册时记录快照信息(前置镜像 + 后置镜像),存入 undo_log 表。
  • 若发生异常,Seata 服务器通知所有分支事务根据 undo_log 自动回滚
  • 需确保每个分支事务的业务库中都有 undo_log(建表 SQL 在 Seata 官方文档中可找到)。

2. TCC 模式(手动补偿)

TCC 模式不依赖数据库事务,而是要求开发者手动实现 Try、Confirm、Cancel 三个方法,更适合对性能要求高或数据库不支持自动补偿的场景。

a. 定义 TCC 接口

import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;

@LocalTCC
public interface InventoryTccService {

    @TwoPhaseBusinessAction(name = "inventoryTccService", commitMethod = "commit", rollbackMethod = "cancel")
    boolean tryDeduct(@BusinessActionContextParameter(paramName = "productId") String productId,
                      @BusinessActionContextParameter(paramName = "count") Integer count);

    boolean commit(BusinessActionContext context);
    
    boolean cancel(BusinessActionContext context);
}

b. 实现 TCC 接口

@Service
public class InventoryTccServiceImpl implements InventoryTccService {

    @Autowired
    private InventoryMapper inventoryMapper;

    @Override
    public boolean tryDeduct(String productId, Integer count) {
        // Try 阶段:尝试冻结库存(业务检查并预留资源)
        int availableStock = inventoryMapper.getAvailableStock(productId);
        if (availableStock < count) {
            throw new RuntimeException("库存不足");
        }
        inventoryMapper.freezeStock(productId, count); // 冻结库存
        return true;
    }

    @Override
    public boolean commit(BusinessActionContext context) {
        // Confirm 阶段:真正扣减冻结的库存
        String productId = (String) context.getActionContext("productId");
        Integer count = (Integer) context.getActionContext("count");
        inventoryMapper.reduceFreezedStock(productId, count); // 扣减已冻结库存
        return true;
    }

    @Override
    public boolean cancel(BusinessActionContext context) {
        // Cancel 阶段:释放冻结的库存
        String productId = (String) context.getActionContext("productId");
        Integer count = (Integer) context.getActionContext("count");
        inventoryMapper.returnFreezedStock(productId, count); // 释放冻结库存
        return true;
    }
}

c. 在业务中调用 TCC

@Service
public class OrderService {

    @Autowired
    private InventoryTccService inventoryTccService;

    @GlobalTransactional(name = "createOrderTcc", rollbackFor = Exception.class)
    public void createOrderTcc(Order order) {
        // 调用 TCC 的 Try 方法
        boolean tryResult = inventoryTccService.tryDeduct(order.getProductId(), order.getCount());
        if (!tryResult) {
            throw new RuntimeException("TCC Try 阶段失败");
        }
        // ... 其他业务逻辑(如创建订单)
    }
}

说明

  • Try:尝试执行业务,完成资源检查和预留(如冻结库存)。
  • Confirm:确认执行业务,真正使用预留的资源(如扣减冻结的库存)。要求幂等。
  • Cancel:取消执行,释放预留的资源(如解冻库存)。要求幂等。
  • TCC 模式无全局锁,性能较好,但需开发者手动实现补偿逻辑。

实践注意事项

  1. 全局事务 ID(XID)传递
    在微服务调用中,需确保 Seata 的 XID 通过请求头在不同服务间传递。通常可通过配置 InterceptorFilter 实现。例如,使用 RestTemplate 时可添加拦截器:
@Bean
public RestTemplate restTemplate() {
    RestTemplate restTemplate = new RestTemplate();
    restTemplate.setInterceptors(Collections.singletonList(new SeataRestTemplateInterceptor()));
    return restTemplate;
}

使用 Feign 时,Seata 已提供支持,一般无需额外配置。

  1. 异常处理
    默认情况下,Seata 只在抛出 RuntimeException 或 Error 时回滚。若需其他异常也触发回滚,可使用 @GlobalTransactional(rollbackFor = Exception.class)。
  2. 超时控制
    Seata 全局事务有超时时间,默认 60 秒。超时后会自动触发回滚。
  3. AT 模式下的脏回滚与手动干预
    在极少数情况下(如长时间网络分区、Seata Server 异常),AT 模式可能无法自动处理回滚(脏数据回滚不成功),此时可能需要人工干预
  4. 手动控制事务回滚与挂起
    在某些特殊场景(如调用第三方 API 并根据返回码判断是否需要回滚,或在大事务中需暂时脱离事务上下文执行一些非事务操作),你可能需要手动回滚临时挂起分布式事务。
    • 手动回滚
if (需要回滚的条件) {
    if (RootContext.inGlobalTransaction()) {
        GlobalTransactionContext.reload(RootContext.getXID()).rollback();
    }
}
    • 临时挂起(解绑并重新绑定 XID)
@GlobalTransactional
public void someMethod() {
    String xid = RootContext.getXID(); // 获取当前 XID
    RootContext.unbind(); // 解绑 XID,后续操作不在此全局事务内
    
    // 执行一些不希望被分布式事务管理的操作(如记录日志)
    logService.saveLog();
    
    RootContext.bind(xid); // 重新绑定 XID,后续操作重新加入全局事务
    // ... 其他业务逻辑
}

挂起事务时需谨慎评估数据一致性。

  1. 数据库支持
    AT 模式需要数据库支持(如 MySQL、PostgreSQL、Oracle 等),并且需要在业务数据库中创建 undo_log 表。
  2. 幂等性与空回滚
    在 TCC 模式中,务必保证 Confirm 和 Cancel 方法的幂等性,并处理好空回滚(Try 未执行,Cancel 却被调用)和防悬挂(Cancel 比 Try 先执行)问题。

如何选择模式?

  • 绝大多数场景:优先使用 AT 模式,对代码侵入低,能快速上线。
  • 高性能需求或数据库不支持(如某些 NoSQL、Redis操作):考虑 TCC 模式,但需承担实现复杂度。
  • 业务流程长、非核心业务或最终一致性即可:可考虑 Saga 模式(本文未详细展开,其通过状态机定义流程和补偿逻辑)。
点击这里复制本文地址 以上内容由jaq123整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

苍茫编程网 © All Rights Reserved.  蜀ICP备2024111239号-21