Seata + AT 模式 复习记录

一、Seata 简介

Seata(Simple Extensible Autonomous Transaction Architecture) 是阿里开源的分布式事务解决方案,支持多种事务模式:

  • AT 模式(Auto Transaction):最常用,基于两阶段提交(2PC),但对业务无侵入。
  • TCC 模式:Try-Confirm-Cancel,需业务代码实现补偿逻辑。
  • Saga 模式:长事务编排,适用于超长流程。
  • XA 模式:传统强一致性协议,性能较差。

二、AT 模式的核心原理

1. 核心思想:自动补偿 + 两阶段提交(2PC)

AT 模式将分布式事务拆解为两个阶段:

第一阶段(Prepare Phase):
  • 所有参与的微服务在本地执行 SQL(如 UPDATE account SET balance = balance - 100 WHERE user_id = 1)。
  • Seata 的 DataSourceProxy 会拦截该 SQL,自动生成 Undo Log(回滚日志) 并存入本地数据库(与业务数据同库)。
  • 同时向 TC(Transaction Coordinator)注册分支事务,但不提交本地事务(保持连接打开)。
  • 若所有分支都成功,则进入第二阶段;否则直接回滚。
第二阶段(Commit / Rollback Phase):
  • 提交:TC 通知各分支提交,各服务删除 Undo Log。
  • 回滚 :TC 通知回滚,服务根据 Undo Log 反向生成 SQL (如 UPDATE account SET balance = 原值 WHERE user_id = 1 AND balance = 当前值)进行补偿。

✅ 关键点:第一阶段就完成业务数据修改 + 记录快照,第二阶段只需清理或回滚,因此性能优于传统 2PC。

三、核心组件

组件 作用
TM(Transaction Manager) 发起全局事务(@GlobalTransactional 注解所在服务)
RM(Resource Manager) 管理分支事务资源(每个参与服务)
TC(Transaction Coordinator) 协调器,维护全局事务状态,驱动分支提交/回滚

四、@GlobalTransactional 注解如何工作?

java 复制代码
@GlobalTransactional
public void transfer(String fromUser, String toUser, BigDecimal amount) {
    accountService.debit(fromUser, amount);   // 调用 A 服务
    accountService.credit(toUser, amount);    // 调用 B 服务
}

执行流程:

  1. TM 启动全局事务
    • 方法被 @GlobalTransactional 标记,Seata 拦截器(通过 AOP)向 TC 注册全局事务,获得 XID(全局事务 ID )。
  2. XID 透传
    • XID 通过 RPC(如 Feign、Dubbo)上下文传递给下游服务。
  3. RM 注册分支事务
    • 下游服务收到 XID,其 DataSourceProxy 拦截 SQL,生成 Undo Log,并向 TC 注册分支事务。
  4. 异常处理
    • 若任意服务抛出异常,TM 通知 TC 回滚所有分支。
    • TC 通知各 RM 执行 Undo Log 补偿。
  5. 成功提交
    • 所有服务成功,TM 通知 TC 提交,各 RM 删除 Undo Log。

五、具体 Spring Boot 项目示例

场景:用户转账(A → B)

服务划分:
  • order-service:发起转账(TM)
  • account-service:提供扣款/加款接口(RM)
数据库表(每个服务都有):
sql 复制代码
-- account 表
CREATE TABLE account (
  id BIGINT PRIMARY KEY,
  user_id VARCHAR(50),
  balance DECIMAL(18,2)
);

-- undo_log 表(Seata 要求)
CREATE TABLE undo_log (
  id BIGINT AUTO_INCREMENT,
  branch_id BIGINT NOT NULL,
  xid VARCHAR(100) NOT NULL,
  context VARCHAR(128) NOT NULL,
  rollback_info LONGBLOB NOT NULL,
  log_status INT NOT NULL,
  log_created DATETIME NOT NULL,
  log_modified DATETIME NOT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY ux_undo_log (xid, branch_id)
);
order-service 代码:
java 复制代码
@Service
public class OrderService {

    @Autowired
    private AccountFeignClient accountClient;

    @GlobalTransactional
    public void transfer(String from, String to, BigDecimal amount) {
        accountClient.debit(from, amount);  // 扣款
        accountClient.credit(to, amount);   // 加款
    }
}
account-service 接口:
java 复制代码
@RestController
public class AccountController {

    @PostMapping("/debit")
    public void debit(@RequestParam String userId, @RequestParam BigDecimal amount) {
        accountService.debit(userId, amount); // 执行 UPDATE
    }

    @PostMapping("/credit")
    public void credit(@RequestParam String userId, @RequestParam BigDecimal amount) {
        accountService.credit(userId, amount);
    }
}
数据源代理配置(关键!):
java 复制代码
@Configuration
public class DataSourceProxyConfig {

    @Bean
    @ConfigurationProperties("spring.datasource")
    public DataSource dataSource() {
        return new DruidDataSource();
    }

    @Bean
    public DataSourceProxy dataSourceProxy(DataSource dataSource) {
        return new DataSourceProxy(dataSource);
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSourceProxy);
        return factoryBean.getObject();
    }
}

⚠️ 必须使用 DataSourceProxy 包装原始数据源,否则无法生成 Undo Log!

六、异常与补偿机制详解

假设 credit(to, amount) 抛出异常:

  1. order-service 捕获异常,标记全局事务为 Rollbacking

  2. TM 通知 TC 回滚。

  3. TC 依次通知两个 RM(debit 和 credit 分支)执行回滚。

  4. RM 查找 undo_log 表中对应 xid 的记录。

  5. 解析 rollback_info(JSON 格式,包含 beforeImage 和 afterImage)。

  6. 构造反向 SQL:

    sql 复制代码
    -- 原 SQL: UPDATE account SET balance = 900 WHERE user_id = 'A'
    -- Undo:  UPDATE account SET balance = 1000 WHERE user_id = 'A' AND balance = 900
  7. 执行回滚 SQL,恢复数据。

  8. 删除 undo_log 记录。

✅ 补偿是 幂等 的(通过 AND balance = 当前值 防止并发干扰)。

七、AT 模式的优缺点

优点:

  • 对业务无侵入:只需加注解 + 配置数据源代理。
  • 开发简单:无需写 Try/Confirm/Cancel 逻辑。
  • 性能较好:一阶段即完成业务操作,二阶段异步清理。
  • 支持 ACID:最终保证一致性。

缺点:

  • 依赖数据库:必须支持本地事务(MySQL、Oracle 等),NoSQL 不支持。
  • 隔离性弱 :一阶段提交后,其他事务可读到"未全局提交"的数据(脏读风险)。
    • 可通过 SELECT FOR UPDATE + 全局锁缓解。
  • 回滚依赖 Undo Log 完整性:若 undo_log 被误删,无法回滚。
  • 不支持所有 SQL :如 DDL批量更新无主键 等可能无法生成正确 Undo。

八、为什么选择 AT 模式?

  • 业务简单,追求开发效率。
  • 数据库事务为主,无复杂补偿逻辑。
  • 能接受短暂脏读(可通过业务设计规避)。
  • 团队无 TCC/Saga 开发经验。

📌 适用场景:电商下单、账户转账、库存扣减等短事务。


九、高频面试题 & 回答要点

Q1:Seata AT 模式如何保证分布式事务一致性?

:基于两阶段提交。一阶段执行业务 SQL 并记录 Undo Log(before/after image),二阶段根据全局事务状态决定提交(删日志)或回滚(执行反向 SQL)。通过 XID 串联全局事务,TC 协调各 RM。

Q2:AT 模式和 TCC 模式有什么区别?

:AT 对业务无侵入,自动补偿;TCC 需手动实现 Try(预留资源)、Confirm(确认)、Cancel(释放),更灵活但开发成本高。AT 适合简单场景,TCC 适合高并发或非 DB 资源(如 Redis、MQ)。

Q3:AT 模式下如何解决脏读问题?

:默认存在脏读(一阶段已提交本地事务)。可通过在查询时加 @GlobalLockSELECT FOR UPDATE 获取全局锁,确保读取已全局提交的数据。

Q4:Undo Log 结构是怎样的?如何保证回滚幂等?

:Undo Log 包含 beforeImage(修改前)、afterImage(修改后)、SQL 类型等。回滚时构造 UPDATE ... WHERE pk = ? AND current_column = afterImage_value,若数据已被其他事务修改,则条件不匹配,回滚失败(需人工干预)。

Q5:Seata 的 TC 宕机了怎么办?

:TC 是单点(社区版),生产建议集群部署 + Raft 协议(Seata Server 支持)。若宕机,正在执行的事务会阻塞,恢复后可继续;新事务无法开启。

十、总结

Seata AT 模式是 平衡开发效率与一致性的优秀方案,特别适合以关系型数据库为核心的微服务架构。

💡 建议:在生产环境务必做好监控(如 undo_log 积压)、TC 高可用、以及异常回滚的人工兜底方案。

相关推荐
麦兜*1 小时前
SpringBoot Profile多环境配置详解,一套配置应对所有场景
java·数据库·spring boot
MetaverseMan1 小时前
rpc节点: synchronized (this) + 双检锁,在 race condition 的情况下分析
java·区块链
CTO Plus技术服务中1 小时前
强悍的Go语言开发面试题和答案
java·面试·职场和发展
黎雁·泠崖2 小时前
Java static入门:概述+静态变量特点与基础实战
java·开发语言
一条大祥脚2 小时前
26.1.21 根号分治 相向双指针
java·开发语言·redis
迦蓝叶2 小时前
JDBC元数据深度实战:企业级数据资源目录系统构建指南
java·jdbc·企业级·数据资源·数据血缘·数据元管理·构建指南
chilavert3182 小时前
技术演进中的开发沉思-327 JVM:内存区域与溢出异常(下)
java·jvm
冲刺逆向2 小时前
【js逆向案例六】创宇盾(加速乐)通杀模版
java·前端·javascript