Seata分布式事务:跨服务数据一致性保障
下单扣了库存,余额也扣了,结果订单创建失败——钱没了,货也没了,用户炸了。分布式事务就是来处理这种"要么全成功、要么全回滚"的跨服务烂摊子的。
一、分布式事务是怎么产生的
单体应用时代,订单、库存、支付全在一个数据库里,一个@Transactional搞定一切。微服务化后:
下单流程(跨3个服务、3个数据库) order-service product-service account-service ┌──────────┐ ┌──────────────┐ ┌──────────────┐ │ 创建订单 │ ──→ │ 扣减库存 │ ──→│ 扣减余额 │ │ (DB-A) │ │ (DB-B) │ │ (DB-C) │ └──────────┘ └──────────────┘ └──────────────┘ ↓ ✅ ↓ ✅ ↓ ❌ 失败! 结果:订单创建了,库存扣了,余额没扣——数据不一致!传统事务@Transactional管不了跨 JVM、跨数据库的操作,这就是分布式事务要解决的问题。
二、理论基础:CAP 和 BASE
2.1 CAP 理论
CAP 是分布式系统的铁律:一致性(Consistency)、可用性(Availability)、分区容错(Partition Tolerance)三者最多同时满足两个。
| 选择 | 说明 | 举例 |
|---|---|---|
| CP | 保证一致性+分区容错,牺牲可用性 | ZooKeeper、etcd |
| AP | 保证可用性+分区容错,牺牲一致性 | Eureka、Cassandra |
| CA | 理想状态,但网络分区无法避免 | 单机系统 |
网络分区(Partition)在生产环境几乎无法避免——交换机故障、网线松了、机房断网,都是分区。所以分布式系统实际上是在 CP 和 AP 之间做选择。
2.2 BASE 理论
既然 CAP 注定无法三全,就退而求其次——BASE 理论是 CAP 在工程上的妥协:
- BA(Basically Available)基本可用:允许响应时间变慢或部分功能降级
- S(Soft State)软状态:允许系统存在中间状态(数据不一致的窗口期)
- E(Eventually Consistent)最终一致性:不要求实时一致,但保证最终会一致
Seata 的 AT 模式就是 BASE 理论的典型实践——它不保证实时一致性,但通过补偿机制保证最终一致性。
三、分布式事务方案概览
| 方案 | 原理 | 一致性 | 性能 | 侵入性 |
|---|---|---|---|---|
| 2PC | 两阶段提交,协调者统一表决 | 强一致 | 低 | 高 |
| TCC | Try-Confirm-Cancel 三阶段 | 最终一致 | 中 | 高(需手动编码) |
| Saga | 长事务拆分,正向调用+补偿 | 最终一致 | 高 | 中 |
| 本地消息表 | 本地事务 + 消息表保证投递 | 最终一致 | 高 | 低 |
| Seata AT | 自动生成 undo_log 补偿 | 最终一致 | 中 | 极低 |
四、Seata 架构三剑客
Seata(Simple Extensible Autonomous Transaction Architecture)是阿里开源的一款分布式事务中间件。
┌─────────────────────────────────────────────────────┐ │ Seata 架构 │ │ │ │ ┌──────────┐ ①开启全局事务 ┌──────────────┐ │ │ │ TM │ ─────────────────→ │ TC │ │ │ │ 事务管理器 │ │ 事务协调器 │ │ │ │(order服务)│ ←────── ③提交/回滚 │ (Seata Server)│ │ │ └──────────┘ └──────────────┘ │ │ │ ↑ ↑ │ │ ②注册分支事务 ②注册 ↑ ↑ ② │ │ │ ┌──────┐ ┌──────┐│ │ ┌────▼────┐ │ RM │ │ RM ││ │ │ RM │ ←── ⑤一阶段提交 ──→ │(库存) │ │(账户) ││ │ │ (订单) │ └──────┘ └──────┘│ │ └─────────┘ │ └─────────────────────────────────────────────────────┘- TC(Transaction Coordinator)事务协调器:Seata Server,独立部署,管理全局事务状态,决定提交还是回滚
- TM(Transaction Manager)事务管理器:事务发起方,负责开启、提交、回滚全局事务。通常在
@GlobalTransactional注解所在服务 - RM(Resource Manager)资源管理器:事务参与方,管理分支事务上的资源(数据库连接),负责分支事务的提交和回滚
五、AT 模式原理详解(重点)
AT 模式是 Seata 最闪耀的功能——对业务代码零侵入,你只需在方法上加一个@GlobalTransactional,剩下的自动搞定。
5.1 一阶段:拦截 SQL + 记录 undo_log
执行业务 SQL:UPDATE product SET stock = stock - 1 WHERE id = 100 步骤: ① Seata 代理数据源,拦截到这条 UPDATE ② 先查快照:SELECT * FROM product WHERE id = 100 得到 before image: { id: 100, stock: 50 } ③ 执行原来的 UPDATE ④ 再查快照:SELECT * FROM product WHERE id = 100 得到 after image: { id: 100, stock: 49 } ⑤ 将 before/after image 写入 undo_log 表 ⑥ 向 TC 注册分支事务,告知"我一阶段搞定了"5.2 二阶段:提交 or 回滚
如果所有 RM 都成功了 → 二阶段提交:
TC 通知所有 RM:「全局事务成功,你们清理 undo_log 吧」 RM 收到提交指令 → 异步删除对应的 undo_log 记录 // 简单、轻量,不会有性能问题如果任何 RM 失败 → 二阶段回滚:
TC 通知所有 RM:「全局事务失败,都给我回滚!」 RM 收到回滚指令: ① 根据 undo_log 中的 after image 检查当前数据 如果数据被改了(脏写)→ 回滚失败,需要人工介入 如果数据没变 → 继续 ② 用 before image 做反向 SQL: UPDATE product SET stock = 50 WHERE id = 100 ③ 删除 undo_log 记录这就是 AT 模式的核心:自动挡。你写普通 SQL,Seata 在背后偷偷生成反向补偿 SQL。
六、Docker 部署 Seata TC Server
version:'3'services:seata-server:image:seataio/seata-server:1.7.0container_name:seata-serverports:-"7091:7091"# Web 控制台-"8091:8091"# 事务协调端口environment:-SEATA_PORT=8091-STORE_MODE=db# 存储模式:db 或 file-SEATA_IP=192.168.1.100volumes:-./seata/resources:/seata-server/resources启动后访问http://localhost:7091可以看到 Seata 控制台。
在application.yml中配置 Seata 注册到 Nacos:
seata:registry:type:nacosnacos:server-addr:127.0.0.1:8848namespace:""group:SEATA_GROUPapplication:seata-serverconfig:type:nacosnacos:server-addr:127.0.0.1:8848namespace:""group:SEATA_GROUP七、SpringBoot 整合 Seata AT 模式
Step 1:引入依赖
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId></dependency>Step 2:配置数据源代理
seata:tx-service-group:my_tx_group# 事务分组名称service:vgroup-mapping:my_tx_group:default# 映射到 TC 集群data-source-proxy-mode:AT# AT 模式需要代理数据源Step 3:undo_log 表
每个参与分布式事务的数据库中都要建这张表:
CREATETABLE`undo_log`(`id`bigint(20)NOTNULLAUTO_INCREMENT,`branch_id`bigint(20)NOTNULLCOMMENT'分支事务ID',`xid`varchar(100)NOTNULLCOMMENT'全局事务ID',`context`varchar(128)NOTNULL,`rollback_info`longblobNOTNULLCOMMENT'before/after image',`log_status`int(11)NOTNULL,`log_created`datetimeNOTNULL,`log_modified`datetimeNOTNULL,PRIMARYKEY(`id`),UNIQUEKEY`ux_undo_log`(`xid`,`branch_id`))ENGINE=InnoDBDEFAULTCHARSET=utf8mb4;Step 4:业务代码
@ServicepublicclassOrderService{// 远程调用库存服务@AutowiredprivateStockFeignClientstockClient;// 远程调用账户服务@AutowiredprivateAccountFeignClientaccountClient;@AutowiredprivateOrderMapperorderMapper;@GlobalTransactional// ← 就这么一个注解!publicvoidcreateOrder(OrderDTOdto,LonguserId){// 1. 创建订单(本地事务 → 自动注册为全局事务的分支)Orderorder=newOrder();order.setUserId(userId);order.setProductId(dto.getProductId());order.setAmount(dto.getAmount());orderMapper.insert(order);// 2. 扣减库存(Feign 远程调用 → 另一个分支事务)stockClient.deduct(dto.getProductId(),dto.getQuantity());// 3. 扣减余额(Feign 远程调用 → 又一个分支事务)accountClient.deduct(userId,dto.getTotalPrice());// 如果第3步抛出异常,Seata 会自动回滚 1 和 2 的操作}}就这么简单,Seata 自动完成分布式事务的提交和回滚。
八、TCC 模式简介
AT 模式虽好,但有些场景不适合——比如跨 Redis/MongoDB 操作,Seata 拦不住非关系型数据库的 SQL。这时用 TCC 模式:
| 阶段 | 方法 | 职责 |
|---|---|---|
| Try | tryDeduct() | 预留资源(冻结库存) |
| Confirm | confirmDeduct() | 使用预留资源(真正扣库存) |
| Cancel | cancelDeduct() | 释放预留资源(解冻库存) |
TCC 需要手写三阶段代码,比 AT 模式费劲,但更灵活,能处理异构数据源。
九、常见问题
脏写:二阶段回滚时,数据已被其他事务修改。Seata 用 after image 校验,发现数据变了就抛异常,此时需人工介入。
悬挂:Cancel 比 Try 先执行(Try 超时后 TC 直接调 Cancel,后来 Try 才到)。TCC 模式下需设计空回滚逻辑——Cancel 操作发现没有对应 Try 记录时直接返回成功。
空回滚:Try 阶段失败,TC 调用 Cancel,但该分支事务从未成功执行过任何操作。Cancel 必须容错,不能因为找不到记录就报错。
总结
Seata AT 模式让分布式事务的开发门槛降到最低——加一个@GlobalTransactional、建一张undo_log表,就能让跨服务的数据操作具有"要么全成功、要么全回滚"的能力。它基于 BASE 理论实现了最终一致性,对 90% 的业务场景够用。遇到复杂场景再考虑 TCC 或 Saga。