Seata AT模式:面试与项目答辩全解析

目录

[Seata AT 模式:面试与项目答辩版](#Seata AT 模式:面试与项目答辩版)

[一、30 秒回答:Seata AT 模式是什么](#一、30 秒回答:Seata AT 模式是什么)

[二、TC、TM、RM 分别是什么](#二、TC、TM、RM 分别是什么)

[1. TC:Transaction Coordinator](#1. TC:Transaction Coordinator)

[2. TM:Transaction Manager](#2. TM:Transaction Manager)

[3. RM:Resource Manager](#3. RM:Resource Manager)

[三、XID 的作用](#三、XID 的作用)

[四、XID 怎么跨服务传播](#四、XID 怎么跨服务传播)

[五、AT 模式第一阶段发生了什么](#五、AT 模式第一阶段发生了什么)

[1. 查询前镜像](#1. 查询前镜像)

[2. 执行业务 SQL](#2. 执行业务 SQL)

[3. 查询后镜像](#3. 查询后镜像)

[4. 写入 undo_log](#4. 写入 undo_log)

[5. 注册分支事务和全局锁](#5. 注册分支事务和全局锁)

[6. 本地事务提交](#6. 本地事务提交)

六、第二阶段正常提交

七、第二阶段异常回滚

八、为什么已经提交的本地事务还能回滚

九、为什么还需要后镜像

[十、全局锁和 MySQL 行锁有什么区别](#十、全局锁和 MySQL 行锁有什么区别)

[MySQL 行锁](#MySQL 行锁)

[Seata 全局锁](#Seata 全局锁)

[十一、AT 模式的一致性级别](#十一、AT 模式的一致性级别)

[十二、AT 模式的优点](#十二、AT 模式的优点)

[1. 业务侵入低](#1. 业务侵入低)

[2. 性能比 XA 更好](#2. 性能比 XA 更好)

[3. 自动补偿](#3. 自动补偿)

[4. 适合常见关系型数据库场景](#4. 适合常见关系型数据库场景)

[十三、AT 模式的缺点](#十三、AT 模式的缺点)

[1. 依赖数据库](#1. 依赖数据库)

[2. SQL 受限制](#2. SQL 受限制)

[3. 存在中间状态](#3. 存在中间状态)

[4. 热点数据容易产生全局锁冲突](#4. 热点数据容易产生全局锁冲突)

[5. 回滚依赖 undo_log](#5. 回滚依赖 undo_log)

十四、生产环境最重要的风险

[1. 不要在全局事务里做耗时操作](#1. 不要在全局事务里做耗时操作)

[2. 异常不能被吞掉](#2. 异常不能被吞掉)

[3. 必须设置合理超时时间](#3. 必须设置合理超时时间)

[4. 注意幂等性](#4. 注意幂等性)

[5. Seata Server 不能单点](#5. Seata Server 不能单点)

[6. 监控 undo_log](#6. 监控 undo_log)

[7. 版本尽量统一](#7. 版本尽量统一)

十五、项目答辩标准话术

十六、你现在应该真正记住的主线


Seata AT 模式:面试与项目答辩版

你现在这个项目已经完整跑通了:

复制代码
order-service
    ↓
storage-service
    ↓
account-service

账户扣款失败后,订单和库存成功回滚;日志中也出现了:

复制代码
Branch Rollbacking
branch rollback success
PhaseTwo_Rollbacked
rollback status: Rollbacked

说明 Seata 全局事务链路已经生效。


一、30 秒回答:Seata AT 模式是什么

面试时可以这样说:

Seata AT 是一种对业务代码侵入较低的分布式事务方案。业务入口通过 @GlobalTransactional 开启全局事务,Seata Server 作为 TC 管理全局事务,参与服务中的 RM 通过数据源代理拦截 SQL,记录数据修改前后的镜像并写入 undo_log

第一阶段各分支本地事务直接提交;如果全局事务成功,第二阶段异步删除 undo_log;如果失败,则根据 undo_log 中的前镜像执行补偿回滚。

核心关键词:

复制代码
低侵入
数据源代理
前镜像、后镜像
undo_log
全局锁
本地事务一阶段提交
二阶段提交或补偿回滚

二、TC、TM、RM 分别是什么

Seata 中有三个核心角色。

1. TC:Transaction Coordinator

事务协调者,也就是你启动的:

复制代码
Seata Server
端口:8091
控制台:7091

职责:

复制代码
创建全局事务
生成 XID
维护全局事务状态
维护分支事务状态
维护全局锁
通知各分支提交或回滚

TC 对应你 seata 数据库中的:

复制代码
global_table
branch_table
lock_table
distributed_lock

2. TM:Transaction Manager

全局事务管理器。

在你的项目中主要是:

复制代码
order-service

因为它的方法上加了:

复制代码
@GlobalTransactional(
        name = "create-order",
        rollbackFor = Exception.class
)

职责:

复制代码
向 TC 开启全局事务
获取 XID
业务成功时请求全局提交
业务失败时请求全局回滚

3. RM:Resource Manager

资源管理器,负责管理具体数据库资源。

你的三个服务都是 RM:

复制代码
order-service
storage-service
account-service

因为它们都操作 seata_test 数据库。

RM 的职责:

复制代码
拦截业务 SQL
生成前镜像和后镜像
保存 undo_log
向 TC 注册分支事务
申请全局锁
接收 TC 的提交或回滚命令

注意:

复制代码
order-service = TM + RM
storage-service = RM
account-service = RM

三、XID 的作用

XID 是一笔全局事务的唯一编号。

例如:

复制代码
10.159.39.99:8091:1189755580561252383

你打印 XID 后,三个服务得到的是同一个值:

复制代码
order-service XID   = 同一个值
storage-service XID = 同一个值
account-service XID = 同一个值

这说明三个服务属于同一笔分布式事务。

关系是:

复制代码
一个 XID
├── order-service 分支事务
├── storage-service 分支事务
└── account-service 分支事务

每个分支还有自己的:

复制代码
branch_id

所以:

复制代码
XID:全局事务编号
branch_id:某个本地分支事务编号

四、XID 怎么跨服务传播

完整过程如下:

复制代码
1. 请求进入 order-service

2. @GlobalTransactional 被 Seata AOP 拦截

3. order-service 中的 TM 请求 TC 创建全局事务

4. TC 生成并返回 XID

5. XID 绑定到 order-service 当前线程

6. order-service 通过 Feign 调用 storage-service

7. Seata 的 Feign 拦截器把 XID 放入 HTTP 请求头

8. storage-service 的 Seata 拦截器读取 XID

9. XID 绑定到 storage-service 当前线程

10. storage-service 的数据库操作注册为分支事务

11. 调用 account-service 时重复同样过程

12. 三个服务最终归属于同一个 XID

RootContext.getXID() 读取的就是当前线程绑定的 XID。


五、AT 模式第一阶段发生了什么

以库存扣减为例:

复制代码
UPDATE storage_tbl
SET count = count - 10
WHERE commodity_code = '2002';

Seata 数据源代理会完成以下工作。

1. 查询前镜像

执行 SQL 前先查询:

复制代码
商品编号:2002
原库存:100

这就是:

复制代码
Before Image

2. 执行业务 SQL

执行后:

复制代码
商品编号:2002
新库存:90

3. 查询后镜像

Seata 再查询一次,得到:

复制代码
After Image

即:

复制代码
修改前:100
修改后:90

4. 写入 undo_log

Seata 将:

复制代码
XID
branch_id
前镜像
后镜像
SQL 类型
表名
主键信息

序列化后写入业务库中的:

复制代码
undo_log

5. 注册分支事务和全局锁

RM 向 TC 注册分支事务:

复制代码
XID
branch_id
resourceId
lockKey

例如库存行的锁键可能类似:

复制代码
storage_tbl:主键值

TC 将全局锁信息放入:

复制代码
lock_table

6. 本地事务提交

业务 SQL 和 undo_log 在同一个本地事务中提交。

这是 AT 模式非常关键的一点:

第一阶段结束后,本地数据库事务已经提交。

因此 AT 模式不会像 XA 一样长时间占用数据库本地事务和本地行锁。


六、第二阶段正常提交

如果三个服务都成功:

复制代码
订单插入成功
库存扣减成功
账户扣款成功

order-service 方法正常结束,TM 请求 TC 提交全局事务。

第二阶段主要执行:

复制代码
释放全局锁
异步删除 undo_log
清理 global_table
清理 branch_table

它不需要再次执行订单、库存、账户的业务 SQL,因为这些 SQL 在第一阶段已经提交。

所以 AT 模式的提交阶段通常比较快。


七、第二阶段异常回滚

你这次测试中,账户服务执行:

复制代码
UPDATE account_tbl
SET money = money - 30000
WHERE user_id = '1002';

触发了数据库约束:

复制代码
Check constraint 'money_chk_2' is violated

说明扣款会使余额小于允许范围,因此 MySQL 拒绝更新。

之后流程是:

复制代码
account-service 抛出异常
        ↓
Feign 返回 HTTP 500
        ↓
异常传回 order-service
        ↓
@GlobalTransactional 拦截器感知异常
        ↓
TM 请求 TC 回滚
        ↓
TC 通知已经成功的分支执行回滚
        ↓
订单分支恢复
库存分支恢复
        ↓
删除 undo_log
释放全局锁

你日志中的:

复制代码
branch rollback success
PhaseTwo_Rollbacked
rollback status: Rollbacked

就是二阶段回滚成功。


八、为什么已经提交的本地事务还能回滚

这是最常见的面试问题。

答案是:

Seata AT 的"回滚"不是数据库原生事务的 ROLLBACK,而是基于 undo_log 执行反向补偿 SQL。

例如第一阶段执行:

复制代码
UPDATE storage_tbl
SET count = 90
WHERE id = 1;

undo_log 中保存前镜像:

复制代码
count = 100

回滚时,Seata 根据前镜像生成类似的补偿操作:

复制代码
UPDATE storage_tbl
SET count = 100
WHERE id = 1;

因此它能够恢复已经提交的数据。


九、为什么还需要后镜像

只记录前镜像似乎就能恢复,但 Seata 还需要后镜像,用于检测脏写。

例如:

复制代码
修改前库存:100
事务修改后:90

但在 Seata 回滚之前,其他程序又把库存改成:

复制代码
85

此时:

复制代码
当前数据 85
≠ 后镜像 90

说明业务数据已经被其他操作修改。

如果 Seata直接恢复到 100,就可能覆盖其他事务的合法修改。

因此回滚时会比较:

复制代码
当前数据
与
undo_log 中的后镜像

如果不一致,说明发生脏写冲突,Seata通常不会直接暴力覆盖,而是进入重试、异常处理或需要人工介入。


十、全局锁和 MySQL 行锁有什么区别

MySQL 行锁

作用范围:

复制代码
当前数据库实例中的本地事务

生命周期:

复制代码
本地事务开始
到
本地事务提交或回滚

第一阶段本地事务提交后,MySQL 行锁就释放。


Seata 全局锁

作用范围:

复制代码
所有加入 Seata 全局事务的业务服务

生命周期:

复制代码
分支注册
到
全局事务最终提交或回滚

即使第一阶段本地事务已经提交,Seata 全局锁仍然可以继续存在。

作用是防止另一个全局事务同时修改相同业务行。


十一、AT 模式的一致性级别

AT 模式不是严格意义上的数据库强隔离事务。

因为第一阶段本地事务已经提交,其他普通 SQL 查询可能看到中间结果。

例如:

复制代码
库存已经从 100 扣到 90
但账户扣款尚未完成

此时普通查询可能看到库存为 90。

所以 AT 更准确地说是:

复制代码
最终一致性
+
全局写隔离

它对全局事务之间的写冲突有全局锁保护,但普通读操作不一定具备严格的全局读隔离。

需要较强读隔离时,可以使用特定的加锁读取,例如:

复制代码
SELECT ... FOR UPDATE

但这样会增加锁冲突和性能成本。


十二、AT 模式的优点

1. 业务侵入低

业务入口只需要:

复制代码
@GlobalTransactional

下游普通数据库操作可以基本保持原样。

不需要手写:

复制代码
Try
Confirm
Cancel

2. 性能比 XA 更好

第一阶段本地事务直接提交,不长时间占用数据库本地锁。


3. 自动补偿

通过 undo_log 自动完成反向恢复,不需要业务开发者为每个操作手写补偿方法。


4. 适合常见关系型数据库场景

特别适合:

复制代码
订单
库存
账户
优惠券
积分

这类多个服务共同修改关系型数据库的业务。


十三、AT 模式的缺点

1. 依赖数据库

AT 主要围绕关系型数据库和 SQL 数据源代理工作。

如果事务中包含:

复制代码
Redis
Elasticsearch
消息队列
文件系统
外部支付接口
第三方 HTTP 接口

这些资源不能简单通过 AT 的 undo_log 自动恢复。


2. SQL 受限制

复杂 SQL、存储过程、部分批处理 SQL、数据库特有语法,可能无法被 Seata正确解析和生成镜像。


3. 存在中间状态

第一阶段本地事务已经提交,因此其他普通查询可能看到全局事务尚未完成的中间数据。


4. 热点数据容易产生全局锁冲突

例如大量请求同时扣减同一个商品库存:

复制代码
storage_tbl:商品1001

多个事务都会竞争相同的全局锁,可能出现:

复制代码
LockConflictException
获取全局锁失败
重试
超时

5. 回滚依赖 undo_log

如果:

复制代码
undo_log 被误删
表结构发生变化
回滚信息损坏
数据库连接中断

都可能导致自动回滚失败。


十四、生产环境最重要的风险

1. 不要在全局事务里做耗时操作

错误示例:

复制代码
@GlobalTransactional
public void createOrder() {
    insertOrder();

    callThirdPartyPayment();

    sendEmail();

    uploadFile();

    Thread.sleep(30_000);

    deductStock();
}

事务越长:

复制代码
全局锁持有越久
冲突概率越高
超时概率越高
undo_log 保留越久
TC 压力越大

全局事务中只放必要的数据库操作。


2. 异常不能被吞掉

错误:

复制代码
try {
    accountApi.deduct(...);
} catch (Exception e) {
    log.error("扣款失败", e);
}

return orderId;

虽然扣款失败,但方法最终正常返回,TM 可能请求全局提交。

正确:

复制代码
try {
    accountApi.deduct(...);
} catch (Exception e) {
    throw new RuntimeException("扣款失败", e);
}

3. 必须设置合理超时时间

如果事务执行时间超过 TC 配置的超时时间,可能触发超时回滚。

不能盲目把超时改得特别大,否则会让全局锁长期占用。


4. 注意幂等性

TC、RM、网络通信可能存在重试。

业务接口应考虑:

复制代码
重复下单
重复扣款
重复扣库存

通常通过:

复制代码
业务唯一号
唯一索引
状态机
幂等表
请求令牌

避免重复执行。


5. Seata Server 不能单点

你现在本地只有一个 TC:

复制代码
127.0.0.1:8091

生产环境如果 TC 宕机:

复制代码
无法开启新全局事务
无法提交或回滚
分支注册失败

生产环境通常需要:

复制代码
多节点 Seata Server
注册到 Nacos
数据库持久化
负载均衡
健康检查
监控告警

6. 监控 undo_log

重点监控:

复制代码
undo_log 数量持续增长
大量 rollback 失败
大量全局锁冲突
长时间未完成事务
TC 数据库慢 SQL

如果 undo_log 长期不清理,通常说明:

复制代码
全局事务没有正常结束
TC 通信异常
二阶段任务异常
回滚失败

7. 版本尽量统一

你当前日志中可以看到:

复制代码
Seata Server:2.2.0
客户端 seata-all:2.0.0

客户端目前已经能与服务端通信并完成回滚,说明当前组合可以运行;但生产环境仍建议尽量使用经过兼容性验证的版本组合,减少协议、配置项和序列化兼容风险。


十五、项目答辩标准话术

面试官问:

你的项目为什么要使用 Seata?

可以回答:

项目的下单流程涉及订单、库存和账户三个服务。普通的 @Transactional 只能保证单个服务、单个数据源内的本地事务,无法覆盖 Feign 远程调用。

因此我在订单服务的业务编排方法上使用 @GlobalTransactional,由订单服务作为 TM 开启全局事务,库存、账户以及订单服务的数据源作为 RM 注册分支事务,Seata Server 作为 TC 统一协调提交或回滚。


面试官问:

你怎么证明 Seata 真正生效了?

可以回答:

我做了正常提交和异常回滚两组测试。异常测试中故意让账户扣款违反金额非负约束,账户服务返回 500,异常通过 Feign 传回订单服务。订单服务日志出现 will be rollbackBranch RollbackedPhaseTwo_Rollbackedrollback status: Rollbacked。最终数据库中订单没有新增、库存恢复、余额不变,说明三个分支事务完成了全局回滚。


面试官问:

AT 模式为什么能回滚已经提交的数据?

回答:

AT 第一阶段会通过数据源代理记录业务数据修改前后的镜像,并将这些镜像保存到 undo_log。本地事务在第一阶段直接提交。全局事务失败后,RM 根据后镜像检查数据是否被其他事务修改,再根据前镜像生成补偿 SQL,恢复业务数据。


面试官问:

AT 和 XA 有什么区别?

回答:

XA 第一阶段通常会保持数据库资源和锁,等待事务协调者最终决定,因此强一致性更强,但资源占用和性能成本较高。AT 第一阶段本地事务直接提交,通过全局锁和 undo_log 实现最终一致性,性能更好、侵入更低,但会存在短暂中间状态,也依赖 SQL 解析和补偿机制。


十六、你现在应该真正记住的主线

复制代码
@GlobalTransactional
        ↓
TM 向 TC 开启全局事务
        ↓
TC 返回 XID
        ↓
XID 通过 Feign 传播
        ↓
各 RM 注册分支事务
        ↓
数据源代理记录前后镜像
        ↓
写业务数据和 undo_log
        ↓
第一阶段本地提交
        ↓
成功:删除 undo_log、释放全局锁
失败:读取 undo_log、执行补偿回滚

这条链路能够完整讲出来,Seata AT 模式的面试问题基本就能应对。

相关推荐
欢醉4 天前
k8s调度容器失败The node had condition: [DiskPressure]
kubernetes·springcloud
爱学习的小可爱卢5 天前
SpringCloud——微服务排错实战:从Gateway到Feign全链路解析
运维·springcloud
全栈Blue6 天前
Spring Cloud 微服务核心组件学习笔记
springcloud
欢醉7 天前
一次P0 故障:微服务线程数莫名暴涨 10 倍,线程问题踩坑
springboot·线程·springcloud
振宇i11 天前
springcloudAlibaba2025.0.0.0组件版本对应关系
springcloud·mybatis-plus·依赖关系
清心歌23 天前
Seata AT 模式简单学习及总结
分布式·seata
南部余额1 个月前
Seata分布式事务解决方案
seata·at·xa·saga·tcc
地瓜伯伯1 个月前
从MESI缓存一致性协议讲透synchronized的底层
java·spring boot·spring·spring cloud·微服务·springcloud
星辰_mya1 个月前
openfeign之在回首
java·架构·dubbo·springcloud·openfeign