分布式事务之Seata

概述

Seata有四种模式

**AT模式:**无侵入式的分布式事务解决方案,适合不希望对业务进行改造的场景,但由于需要添加全局事务锁,对影响高并发系统的性能。该模式主要关注多DB访问的数据一致性,也包括多服务下的多DB数据访问一致性问题。通过更新前快照回滚、更新后快照对比在二阶段提交时是否有人修改(为不受Seata代理的数据源做兜底),解决脏写。

优点:

  • 一阶段完成直接提交事务,释放数据库资源,性能比较好

  • 利用全局锁实现读写隔离

  • 没有代码侵入,框架自动完成回滚和提交

缺点:

  • 两阶段之间属于软状态,属于最终一致

  • 框架的快照功能会影响性能,但比XA模式要好很多

**TCC模式:**高性能的分布式事务解决方案,适用于对性能要求比较高的场景。该模式主要关注业务拆分,在按照业务横向扩展资源时,解决服务间调用的一致性问题。

优点:

  • 一阶段完成直接提交事务,释放数据库资源,性能好
  • 相比AT模型,无需生成快照,无需使用全局锁,性能最强
  • 不依赖数据库事务,而是依赖补偿操作,可以用于非事务型数据库
  • Redis这种也可以使用TCC模式

缺点:

  • 有代码侵入,需要人为编写try、Confirm和Cancel接口,太麻烦
  • 软状态,事务是最终一致
  • 需要考虑Confirm和Cancel的失败情况,做好幂等处理
  • 因为如果失败Seata会重试,所有要做好幂等

**Saga模式:**长事务的分布式事务解决方案,适用于业务流程长且需要保证事务最终一致性的业务系统。Saga 模式一阶段就会提交本地事务,无锁,长流程情况下可以保证性能,多用于渠道层、集成层业务系统,事务参与者可以是其它公司的服务也可以是遗留系统的服务,并且对于无法进行改造和提供 TCC 要求的接口,也可以使用 Saga 模式。

优点:

  • 一阶段提交本地数据库事务,无锁,高性能;

  • 参与者可以采用事务驱动异步执行,高吞吐;

  • 补偿服务即正向服务的"反向",易于理解,易于实现;

缺点:

  • Saga 模式由于一阶段已经提交本地数据库事务,且没有进行"预留"动作,所以不能保证隔离性。后续会讲到对于缺乏隔离性的应对措施。

XA模式: 强一致性。XA早期版本是一种规范 主流数据库对XA规范提供了支持,没有TM(事务管理者的概念)只有TC和RM。一阶段不提交事务,在第二阶段TM发起事务提交/回滚时才会让TC 检查分支状态做提交/回滚因此是强一致性的

优点:

  • 强一致性
  • 易于使用:因为主流数据库都支持且无代码侵入

缺点:

  • 第一阶段不提交,在等待过程中占用数据库锁,占用系统资源,性能差

Seata核心组件

  • TC(Transaction Coordinator):全局事务协调器,负责管理全局事务的状态。
  • TM(Transaction Manager):事务管理器,负责发起全局事务,并向TC注册事务。
  • RM(Resource Manager):资源管理器,负责管理资源的本地事务,并向TC汇报事务状态。

前置条件

  1. 引入Seata依赖:在项目中引入Seata的依赖。
  2. 配置Seata服务:配置Seata的TC服务地址。
  3. 定义全局事务 :使用@GlobalTransactional注解定义全局事务。

1. AT 模式(Auto Transaction)​

核心原理

​两阶段提交:

  1. 阶段一(Branch Commit):拦截业务 SQL,生成前置镜像(before image)和后置镜像(after image),保存到 UNDO_LOG 表。
    示例:执行 UPDATE product SET stock = stock - 10 WHERE id = 1 时,记录修改前的 stock=100 和修改后的 stock=90。
  2. 阶段二(Global Commit/Rollback):全局事务提交时,删除 UNDO_LOG;回滚时,根据镜像数据生成反向SQL(如 UPDATE product SET stock = 100 WHERE id = 1)。

​全局锁机制:

在阶段一提交前,Seata 会获取记录的全局锁,防止其他事务修改同一数据,确保隔离性。

​应用场景

单服务多数据源:

例如订单服务同时操作 MySQL 和 PostgreSQL,需要保证两个库的事务一致性。

​简单跨服务调用:

服务 A 调用服务 B 的接口,两者均使用 AT 模式(如订单服务扣减库存服务)。

代码示例

java 复制代码
// 订单服务(使用 AT 模式)
@GlobalTransactional // 开启全局事务
public void saveOrder(OrderRequest request) {
    // 1. 本地事务:创建订单
    orderDao.insert(request.getOrder());
    
    // 2. 远程调用库存服务(Feign 接口)
    storageFeign.discount(request.getProductId(), request.getCount());
    
    // 3. 模拟异常触发回滚
    if (request.getForceFail()) {
        throw new RuntimeException("Force rollback");
    }
}

关键细节

  • UNDO_LOG 表结构:需在业务库中提前创建,包含 branch_id、xid、rollback_info 等字段。
  • 隔离性牺牲:AT 模式默认隔离级别为读未提交(Read Uncommitted),高并发场景可能脏读,需业务侧处理(如版本号校验)。
  • 性能优化:避免单行数据频繁更新,防止全局锁竞争。

​2. TCC 模式(Try-Confirm-Cancel)​

核心原理

三阶段控制:

​Try:预留资源(如冻结库存、预扣余额),完成业务检查。

​Confirm:确认操作,真正执行业务(如扣减冻结的库存)。

​Cancel:回滚操作,释放预留资源(如解冻库存)。

业务侵入性:需手动编写 Try/Confirm/Cancel 接口,处理幂等性、空回滚、悬挂等问题。

​应用场景

  • 资金交易:转账前预冻结账户金额,最终扣款或解冻。
  • 第三方服务集成:调用外部 API(如支付接口)需要明确的成功/失败确认。

​代码示例

java 复制代码
// TCC 接口定义(账户扣款)
public interface AccountTccService {
    @TwoPhaseBizAction(name = "deduct", commitMethod = "confirm", rollbackMethod = "cancel")
    boolean tryDiscount(@BizActionContextParameter(paramName = "userId") String userId,
                     @BizActionContextParameter(paramName = "amount") BigDecimal amount);
 
    boolean confirm(BizActionContext context);
    boolean cancel(BizActionContext context);
}
 
// Try 阶段实现(冻结资金)
@Override
public boolean tryDeduct(String userId, BigDecimal amount) {
    if (accountDao.getAvailableBalance(userId).compareTo(amount) < 0) {
        throw new RuntimeException("余额不足");
    }
    accountDao.freeze(userId, amount); // 冻结资金
    return true;
}
 
// Confirm 阶段(实际扣款)
@Override
public boolean confirm(BizActionContext context) {
    String userId = (String) context.getActionContext("userId");
    BigDecimal amount = (BigDecimal) context.getActionContext("amount");
    accountDao.discount(userId, amount);  // 扣减冻结金额
    accountDao.unfreeze(userId, amount); // 解冻
    return true;
}
 
// Cancel 阶段(解冻资金)
@Override
public boolean cancel(BizActionContext context) {
    String userId = (String) context.getActionContext("userId");
    BigDecimal amount = (BigDecimal) context.getActionContext("amount");
    accountDao.unfreeze(userId, amount);
    return true;
}

​关键细节

  • 幂等性处理:
    通过唯一事务 ID(xid)确保 Confirm/Cancel 只执行一次。
  • 空回滚问题:
    Try 未执行但收到 Cancel 请求时,需插入标记记录,避免误解冻。
  • 悬挂问题:
    Cancel 比 Try 先到达时,需通过状态判断拒绝后续 Try 操作。

​3. Saga 模式

​核心原理

  • 事件驱动流程:
    将分布式事务拆分为多个本地事务,每个事务提交后触发下一个事务。若某个事务失败,按反向顺序执行补偿操作。
  • 补偿机制:
    每个正向操作需定义对应的补偿方法(如 bookHotel() 对应 cancelHotel())。

​应用场景

  • 长流程业务:
    旅行预订(机票 → 酒店 → 租车)、电商订单(下单 → 支付 → 发货)。
  • 渠道层、集成层业务:
    在渠道层和集成层业务中,往往需要与外部系统进行交互。例如,银行系统与第三方支付平台的集成,或者企业系统与ERP系统的集成。这些场景中,事务的跨服务特性使得Saga模式成为理想的解决方案。
  • 跨公司服务集成:
    跨公司服务集成中,事务的分布式特性更加明显。例如,一个供应链系统可能涉及多个供应商、物流和零售商的协作。Saga模式通过补偿机制,可以有效地处理这些复杂场景中的事务问题。
  • 最终一致性场景:
    接受中间状态短暂不一致,但最终一致。Saga模式通过补偿操作保证最终一致性。如果某个Saga单元失败,系统会依次调用之前所有单元的补偿操作,回滚之前的操作。例如,如果支付服务扣款失败,系统会调用库存服务的补偿操作恢复库存。

​代码示例

java 复制代码
//Saga 流程编排(状态机或注解驱动)
@SagaService
public class OrderSagaService {
    @Autowired
    private InventoryService inventoryService;
    @Autowired
    private PaymentService paymentService;
 
    @SagaStart
    public void createOrder(Order order) {
        // 1. 扣减库存
        inventoryService.discount(order.getProductId(), order.getQuantity());
        
        // 2. 发起支付
        paymentService.pay(order.getUserId(), order.getAmount());
        
        // 3. 更新订单状态为成功
        order.setStatus(OrderStatus.SUCCESS);
        orderDao.update(order);
    }
 
    @Compensate
    public void compensateOrder(Order order) {
        // 反向操作:释放库存、退款、订单状态回滚
        inventoryService.restore(order.getProductId(), order.getQuantity());
        paymentService.refund(order.getUserId(), order.getAmount());
        order.setStatus(OrderStatus.FAILED);
        orderDao.update(order);
    }
}

关键细节

  • 状态机配置:
    可通过 JSON 或注解定义 Saga 流程,明确每个步骤的补偿方法。
  • 超时管理:
    设置 Saga 事务超时时间,避免流程长期悬挂。
  • 异步执行:
    适合结合消息队列(如 RocketMQ)实现异步 Saga。

​注:@Compensate

4. XA 模式

核心原理

  • 传统两阶段提交:
    Prepare 阶段:所有参与者(数据库)锁定资源,返回就绪状态。
    Commit/Rollback 阶段:协调者根据 Prepare 结果提交或回滚。
  • 强一致性:
    所有资源在 Prepare 阶段锁定,直到全局事务结束。

​应用场景

  • 金融核心系统:
    银行转账(必须保证双方账户同时成功或失败)。
  • 传统数据库集成:
    旧系统迁移,依赖数据库原生 XA 协议。

​代码示例

java 复制代码
// XA 数据源配置
@Bean
public DataSource dataSource() {
    MysqlXADataSource xaDataSource = new MysqlXADataSource();
    xaDataSource.setUrl("jdbc:mysql://localhost:3306/test");
    xaDataSource.setUser("root");
    xaDataSource.setPassword("***");
    
    return new AtomikosDataSourceBean(xaDataSource);
}
 
// 业务方法(依赖 JTA)
@Transactional // 使用 JTA 事务管理器
public void transfer(String fromId, String toId, BigDecimal amount) {
    jdbcTemplate.update("UPDATE account SET balance = balance - ? WHERE id = ?", amount, fromId);
    jdbcTemplate.update("UPDATE account SET balance = balance + ? WHERE id = ?", amount, toId);
}

关键细节

  • 性能瓶颈:
    全局锁持有时间长,高并发下吞吐量低。
  • 数据库支持:
    需数据库支持 XA 协议(如 MySQL InnoDB、Oracle)。
  • 调试复杂:
    XA 事务状态需通过数据库日志或 JTA 工具监控。

选型对比

​对比维度

维度 AT TCC Saga XA
​一致性 弱隔离(读未提交) 强隔离(预留资源) 最终一致性 强一致性
​性能 高(短事务) 中(两阶段控制) 高(异步流程) 低(长锁)
​侵入性 低(自动 UNDO_LOG) 高(手动 TCC 接口) 中(补偿方法) 低(数据库支持)
适用场景 简单跨服务/多数据源 资金交易、第三方集成 长流程业务 金融核心、传统系统
​容错能力 自动回滚 需处理空回滚、悬挂 需补偿逻辑完备 依赖数据库 XA 恢复

决策树

  • 是否需要强一致性?
    是 → XA 模式(金融场景)或 TCC 模式(业务可控)。
    否 → 进入下一步。
  • 是否为长流程业务?
    是 → Saga 模式(如电商订单)。
    否 → 进入下一步。
  • 是否希望低侵入?
    是 → AT 模式(简单跨服务调用)。
    否 → TCC 模式(精细化控制)。

​总结

  • AT 模式:快速解决 80% 的分布式事务问题,适合微服务新手。
  • TCC 模式:应对资金、库存等核心资源操作,牺牲开发效率换取高可靠性。
  • Saga 模式:长流程业务的终极方案,需接受最终一致性。
  • XA 模式:传统系统兼容选择,性能敏感场景慎用。

实际开发中,多种模式在同一个工程中不可混用、不能同时使用,但可组合使用多种模式(如 AT + Saga),并配合消息队列、幂等设计、监控告警,构建健壮的分布式事务体系。

相关推荐
皮皮林5512 小时前
SpringBoot 加载外部 Jar,实现功能按需扩展!
java·spring boot
rocksun2 小时前
认识Embabel:一个使用Java构建AI Agent的框架
java·人工智能
Java中文社群4 小时前
AI实战:一键生成数字人视频!
java·人工智能·后端
王中阳Go4 小时前
从超市收银到航空调度:贪心算法如何破解生活中的最优决策谜题?
java·后端·算法
shepherd1114 小时前
谈谈TransmittableThreadLocal实现原理和在日志收集记录系统上下文实战应用
java·后端·开源
维基框架4 小时前
Spring Boot 项目整合Spring Security 进行身份验证
java·架构
日月星辰Ace5 小时前
Java JVM 垃圾回收器(四):现代垃圾回收器 之 Shenandoah GC
java·jvm
天天摸鱼的java工程师6 小时前
商品详情页 QPS 达 10 万,如何设计缓存架构降低数据库压力?
java·后端·面试
天天摸鱼的java工程师6 小时前
设计一个分布式 ID 生成器,要求全局唯一、趋势递增、支持每秒 10 万次生成,如何实现?
java·后端·面试
阿杆6 小时前
一个看似普通的定时任务,如何优雅地毁掉整台服务器
java·后端·代码规范