分布式事务:Seata
第六个终极难题:跨服务转账,A 扣了钱 B 没加上怎么办?单体里一个 @Transactional 全搞定。微服务里 A 银行扣钱和 B 银行加钱是两个服务、两个数据库,本地事务管不了跨库。这就是分布式事务,业界方案一堆,阿里开源的 Seata 是国内用得最多的。
论先懂 CAP,再谈 Seata
CAP:一致性(Consistency)、可用性(Availability)、分区容错(Partition tolerance)。分布式系统网络一定会断,所以 P 必须保,剩下 C 和 A 二选一。分布式事务就是在这个取舍下找平衡。
BASE:基本可用(Basically Available)、软状态(Soft State)、最终一致(Eventually Consistent)。大部分互联网业务不用强一致,接受"最后一致"就行——钱扣了,过 2 秒对方加上,用户能接受。
Seata 四种模式
| 模式 | 原理 | 适合场景 |
|---|---|---|
| AT | 自动生成回滚 SQL,对业务代码零侵入(加个注解就行)。 | Spring Cloud 业务服务,最常用。 |
| TCC | Try/Confirm/Cancel 三步,业务自己实现三个接口。 | 对性能和一致性要求高的资金类业务。 |
| Saga | 长事务,每步失败就反向执行补偿动作。 | 跨系统、流程长的业务(订票、审批)。 |
| XA | 数据库层面的两阶段提交,强一致但性能差。 | 传统企业、数据库支持 XA 的场景。 |
Seata 三个角色
Seata 架构就三个角色,记住缩写就行:
业务代码用 AT 模式,就加一个注解 @GlobalTransactional
AT 模式内部怎么工作(搞懂就不慌)
AT 模式是 Seata 最常用的,对业务零侵入。它的原理是"两阶段提交 + 自动补偿":
每个参与 Seata 的业务库,必须建一张 undo_log 表(Seata 官方脚本里有)
AT 模式靠"记录前后镜像、失败时反向 UPDATE"来回滚。回滚失败通常是:① 分支事务提交后,这条记录又被别的业务改了(脏写,镜像对不上);② 数据库隔离级别太高,回滚 SQL 拿不到锁;③ TC 服务挂了,全局事务状态丢了。生产环境一定要配 TC 集群 + 持久化到数据库,别用内存模式。
本章面试题
Q1. CAP 三选二,分布式注册中心为什么放弃 C?
参考答案
网络分区(P)无法避免必须保。注册发现场景下,分区时宁可各节点返回(可能略旧的)可用实例(A),也不能为了强一致整个注册中心停摆(牺牲 A)。所以 Nacos/Eureka 默认 AP;配置中心这类需要强一致的才切 CP。
Q2. Seata AT 模式为什么对业务无侵入?
参考答案
它拦截你的 SQL,自动在前后记录 before/after 镜像到 undo_log;阶段二全成功就删日志,失败就用 before 镜像反向生成 UPDATE 回滚。业务代码只写正常 SQL,加个 @GlobalTransactional 就行,不用手写 Try/Confirm/Cancel。
Q3. TCC 和 AT 怎么选?
参考答案
AT 无侵入、性能好、适合大多数互联网业务;TCC 要手写 Try/Confirm/Cancel 三个接口、开发成本高,但不受全局锁、更灵活,适合资金、库存扣减这种对并发和一致性要求极高的核心链路。
Q4. 本地消息表 / 最终一致了解吗?
参考答案
不用 Seata 的另一派:业务库建一张"消息表",本地事务里同时写业务数据和待发消息;后台任务扫表把消息发到 MQ,下游消费。靠"本地事务保证业务+消息同生共死"+MQ 重试实现最终一致。比 Seata 重业务,但不依赖 TC。