什么是Seata?谈谈你对Seata的理解
一、 标准面试回答模版(建议背诵)
面试官: 什么是 Seata?谈谈你对 Seata 的理解。
Fox版标准回答: “Seata 是阿里开源的一款高性能分布式事务解决方案。它的核心目标是在微服务架构下,提供简单易用的事务服务。 它主要提供了 AT、TCC、Saga 和 XA 四种模式,其中最常用、也是 Seata 最大的创新点是 AT 模式。
我对 Seata AT 模式的理解,核心概括为‘一个优化,两个阶段,三处防线’:
- 一个优化(核心竞争力): 相比于传统的 XA(2PC),Seata AT 模式最大的改进是一阶段直接提交本地事务。它不长时间占用数据库连接资源,极大地提升了高并发场景下的性能。
- 两个阶段(工作机制):
一阶段: 执行业务 SQL,同时记录 Undo Log(回滚日志)(包含前镜像和后镜像),然后提交本地事务。
二阶段:
如果顺利提交:异步删除 Undo Log(效率高)。
如果需要回滚:根据 Undo Log 中的‘前镜像’生成反向 SQL,把数据还原。
- 三处防线(最大的坑): 因为一阶段释放了锁,会带来脏写(Dirty Write)的问题。Seata 引入了 TC 侧的全局锁(Global Lock) 来保证隔离性。
- 写隔离: 提交本地事务前,必须先拿全局锁,拿不到就重试。
- 读隔离: 默认是读未提交(脏读),如果需要读已提交,必须在 Select 语句上加
@GlobalLock或FOR UPDATE来申请检查全局锁。”
二、 原理与代码层面的体现
1. 场景一:Undo Log 是怎么生成的?(原理层面)
场景: 此时有一张表 account,id=1 的余额 money=100。 业务代码:update account set money = money - 10 where id = 1。
Seata 代理执行流程:
- 解析 SQL: 识别出是 Update 操作。
- 查询前镜像(Before Image): 执行
select * from account where id = 1,记录 money=100。 - 执行业务 SQL: money 变为 90。
- 查询后镜像(After Image): 执行
select * from account where id = 1,记录 money=90。 - 构建 Undo Log: 将前后镜像封装成 JSON,存入
undo_log表。
{
"beforeImage": { "id": 1, "money": 100 },
"afterImage": { "id": 1, "money": 90 }
}- 提交: 业务数据和 Undo Log 在同一个本地事务中提交。
2. 场景二:脏写问题与全局锁(避坑演示)
问题: 如果没有全局锁,两个分布式事务同时改同一行数据,会发生什么?
- T1 事务: 把 money 从 100 改成 90,本地提交了。
- T2 事务: 紧接着进来,把 money 从 90 改成 80,也本地提交了。
- T1 回滚: T1 发现异常要回滚,它拿着前镜像(100)去还原,直接把 money 改回 100。
- 结果:T2 的更新(80)丢失了! 这就是脏写。
Seata 的解法:
// Seata 的代理数据源在执行 Commit 之前,会执行类似如下逻辑:
while (true) {
// 尝试向 TC(Seata服务端)申请 id=1 这行记录的全局锁
boolean lock = tc.acquireGlobalLock(xid, "account", pk=1);
if (lock) {
// 拿到全局锁,才能提交本地事务
doLocalCommit();
break;
} else {
// 拿不到,抛异常回滚,或者重试
throw new LockConflictException();
}
}三、 Fox的深度解析
如果面试官问:“如果有一个线程没走 Seata 的代理,直接去改数据库,会有什么后果?” 或者 “AT 模式和 TCC 模式怎么选?”
Fox版解析:
1. 关于“野路子”线程(全局锁失效): “这是一个非常严重的生产事故隐患。 Seata 的 AT 模式强依赖于全局锁。如果你的项目中,有一个定时任务或者一个老接口,没有加 @GlobalTransactional,也没有走 Seata 的 DataSourceProxy,直接去更新数据库。 后果: 这个线程在修改数据时,完全不会去检查 TC 的全局锁。它会直接修改数据(例如把 90 改成 80)。 此时,如果有一个正经的 Seata 事务要回滚(原值 100 -> 90,想回滚回 100),Seata 会校验当前数据库的值(80)和后镜像(90)是否一致。 结果: 发现不一致(80 != 90),说明数据被‘脏写’了,Seata 会直接抛出无法回滚的异常,需要人工介入修数据。 解决: 涉及全局事务的表,必须禁止裸写,或者给‘野路子’加上 @GlobalLock 注解强制检查锁。”
2. 关于 AT vs TCC 的权衡: “面试官,我认为没有最好的模式,只有最适合的场景:
- AT 模式: 它是开发友好型。代码零侵入,像写本地事务一样写分布式事务。适合业务逻辑不复杂、非核心高并发的链路。
- TCC 模式: 它是性能极致型。它不依赖数据库的行锁,而是把资源预留(Try)、确认(Confirm)、取消(Cancel)都在业务层实现。适合核心支付交易链路,或者并发量极高的场景。但是,写 TCC 要防空回滚、防悬挂,开发成本极高。”