Seata分布式事务:跨服务数据一致性保障
Seata分布式事务跨服务数据一致性保障下单扣了库存余额也扣了结果订单创建失败——钱没了货也没了用户炸了。分布式事务就是来处理这种要么全成功、要么全回滚的跨服务烂摊子的。一、分布式事务是怎么产生的单体应用时代订单、库存、支付全在一个数据库里一个Transactional搞定一切。微服务化后下单流程跨3个服务、3个数据库 order-service product-service account-service ┌──────────┐ ┌──────────────┐ ┌──────────────┐ │ 创建订单 │ ──→ │ 扣减库存 │ ──→│ 扣减余额 │ │ (DB-A) │ │ (DB-B) │ │ (DB-C) │ └──────────┘ └──────────────┘ └──────────────┘ ↓ ✅ ↓ ✅ ↓ ❌ 失败 结果订单创建了库存扣了余额没扣——数据不一致传统事务Transactional管不了跨 JVM、跨数据库的操作这就是分布式事务要解决的问题。二、理论基础CAP 和 BASE2.1 CAP 理论CAP 是分布式系统的铁律一致性Consistency、可用性Availability、分区容错Partition Tolerance三者最多同时满足两个。选择说明举例CP保证一致性分区容错牺牲可用性ZooKeeper、etcdAP保证可用性分区容错牺牲一致性Eureka、CassandraCA理想状态但网络分区无法避免单机系统网络分区Partition在生产环境几乎无法避免——交换机故障、网线松了、机房断网都是分区。所以分布式系统实际上是在 CP 和 AP 之间做选择。2.2 BASE 理论既然 CAP 注定无法三全就退而求其次——BASE 理论是 CAP 在工程上的妥协BABasically Available基本可用允许响应时间变慢或部分功能降级SSoft State软状态允许系统存在中间状态数据不一致的窗口期EEventually Consistent最终一致性不要求实时一致但保证最终会一致Seata 的 AT 模式就是 BASE 理论的典型实践——它不保证实时一致性但通过补偿机制保证最终一致性。三、分布式事务方案概览方案原理一致性性能侵入性2PC两阶段提交协调者统一表决强一致低高TCCTry-Confirm-Cancel 三阶段最终一致中高需手动编码Saga长事务拆分正向调用补偿最终一致高中本地消息表本地事务 消息表保证投递最终一致高低Seata AT自动生成 undo_log 补偿最终一致中极低四、Seata 架构三剑客SeataSimple Extensible Autonomous Transaction Architecture是阿里开源的一款分布式事务中间件。┌─────────────────────────────────────────────────────┐ │ Seata 架构 │ │ │ │ ┌──────────┐ ①开启全局事务 ┌──────────────┐ │ │ │ TM │ ─────────────────→ │ TC │ │ │ │ 事务管理器 │ │ 事务协调器 │ │ │ │(order服务)│ ←────── ③提交/回滚 │ (Seata Server)│ │ │ └──────────┘ └──────────────┘ │ │ │ ↑ ↑ │ │ ②注册分支事务 ②注册 ↑ ↑ ② │ │ │ ┌──────┐ ┌──────┐│ │ ┌────▼────┐ │ RM │ │ RM ││ │ │ RM │ ←── ⑤一阶段提交 ──→ │(库存) │ │(账户) ││ │ │ (订单) │ └──────┘ └──────┘│ │ └─────────┘ │ └─────────────────────────────────────────────────────┘TCTransaction Coordinator事务协调器Seata Server独立部署管理全局事务状态决定提交还是回滚TMTransaction Manager事务管理器事务发起方负责开启、提交、回滚全局事务。通常在GlobalTransactional注解所在服务RMResource Manager资源管理器事务参与方管理分支事务上的资源数据库连接负责分支事务的提交和回滚五、AT 模式原理详解重点AT 模式是 Seata 最闪耀的功能——对业务代码零侵入你只需在方法上加一个GlobalTransactional剩下的自动搞定。5.1 一阶段拦截 SQL 记录 undo_log执行业务 SQLUPDATE 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 模式的核心自动挡。你写普通 SQLSeata 在背后偷偷生成反向补偿 SQL。六、Docker 部署 Seata TC Serverversion:3services:seata-server:image:seataio/seata-server:1.7.0container_name:seata-serverports:-7091:7091# Web 控制台-8091:8091# 事务协调端口environment:-SEATA_PORT8091-STORE_MODEdb# 存储模式db 或 file-SEATA_IP192.168.1.100volumes:-./seata/resources:/seata-server/resources启动后访问http://localhost:7091可以看到 Seata 控制台。在application.yml中配置 Seata 注册到 Nacosseata: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引入依赖dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-seata/artifactId/dependencyStep 2配置数据源代理seata:tx-service-group:my_tx_group# 事务分组名称service:vgroup-mapping:my_tx_group:default# 映射到 TC 集群data-source-proxy-mode:AT# AT 模式需要代理数据源Step 3undo_log 表每个参与分布式事务的数据库中都要建这张表CREATETABLEundo_log(idbigint(20)NOTNULLAUTO_INCREMENT,branch_idbigint(20)NOTNULLCOMMENT分支事务ID,xidvarchar(100)NOTNULLCOMMENT全局事务ID,contextvarchar(128)NOTNULL,rollback_infolongblobNOTNULLCOMMENTbefore/after image,log_statusint(11)NOTNULL,log_createddatetimeNOTNULL,log_modifieddatetimeNOTNULL,PRIMARYKEY(id),UNIQUEKEYux_undo_log(xid,branch_id))ENGINEInnoDBDEFAULTCHARSETutf8mb4;Step 4业务代码ServicepublicclassOrderService{// 远程调用库存服务AutowiredprivateStockFeignClientstockClient;// 远程调用账户服务AutowiredprivateAccountFeignClientaccountClient;AutowiredprivateOrderMapperorderMapper;GlobalTransactional// ← 就这么一个注解publicvoidcreateOrder(OrderDTOdto,LonguserId){// 1. 创建订单本地事务 → 自动注册为全局事务的分支OrderordernewOrder();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 模式阶段方法职责TrytryDeduct()预留资源冻结库存ConfirmconfirmDeduct()使用预留资源真正扣库存CancelcancelDeduct()释放预留资源解冻库存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。