FEATURED · 精选文章

详解SpringCloud之分布式事务Seata

发布时间 / 2026/8/23 6:58:16
来源 / 创域科博编辑部
栏目 / 资讯中心
详解SpringCloud之分布式事务Seata 目录什么是分布式事务Seata 三大核心角色工作流程Seata 的四种模式一、AT 模式默认最常用自动事务AT 模式原理两阶段Seata AT 完整实战案例步骤 1undo_log 建表步骤 2Maven 依赖步骤 3配置文件步骤 4order‑service 代码TM RM4.1 RestTemplate 配置4.2 OrderService 业务代码步骤 5下游 account‑service、storage‑service 公共 Filter完整执行流程带 undo_log 变化演示阶段 1TM 开启全局事务生成 XID阶段 2order‑service 本地插入订单它同时也是 RM阶段 3RestTemplate 调用 account‑service 扣余额阶段 4RestTemplate 调用 storage‑service 扣库存分支 A全部正常分支 B异常重点故障演示如果没有写下游 SeataXidFilter 会发生什么关键注意点面试提问参考关键注意点对比 Feign 与 RestTemplate一句话总结二、TCC 模式三、SAGA 模式长事务适合长流程四、XA 模式Seata 架构部署SpringBoot 微服务依赖AT 模式常见坑面试高频面试经典问题场景 1同步 RPCorder‑service → account‑service → storage‑service场景 2MQ 异步方案 1Seata‑TCC手动编码方案 2Seata‑SAGA方案 3RocketMQ 事务消息半消息方案 4手动传递 XID强行 AT不推荐各个模式适合什么场景面试高频问题Q为什么异步 MQ 不能直接用 Seata ATQ那什么时候 AT 不能用QTCC/SAGA/ 事务消息和 AT 的区别一句话总结什么是分布式事务分布式事务一次业务操作跨多个数据库 / 多个微服务要保证所有数据库操作要么全部成功要么全部回滚。例子下单业务order‑service创建订单、account‑service扣余额、storage‑service扣库存。三个属于不同微服务、不同数据库。如果订单创建成功扣钱成功扣库存失败 → 数据不一致产生脏数据。本地事务MySQL 事务只能管同一个数据库管不了跨库跨服务就需要分布式事务框架Seata。Seata 三大核心角色角色全称作用TCTransaction Coordinator 事务协调者独立服务Seata‑Server。维护全局事务状态指挥所有分支提交 / 回滚。TMTransaction Manager 事务管理器业务应用端开启 / 结束全局事务向 TC 申请全局事务 ID (XID)。RMResource Manager 资源管理器业务应用端管理本地事务向 TC 上报分支事务状态执行分支提交 / 回滚。工作流程TM向 TC 申请一个全局事务 XID全局唯一事务 IDTM 开启全局事务执行业务各个微服务RM执行业务 SQL分支事务注册到 TCXID 会沿着 RPC 调用链路传递。所有分支执行完成全部成功TM 通知 TCTC 通知所有 RM 提交本地事务。任意分支失败TM 通知 TCTC 通知所有 RM 回滚本地事务。XID 全局事务 ID整个分布式事务的唯一编号。一次全局事务从头到尾只有同一个 XID所有参与这个分布式事务的微服务、分支事务全部都要带上这个 XID 上报给 TC。TM在事务入口拿到 XID当 A 服务调用 B 服务Feign/RPC 等远程调用的时候要把这个 XID 传给下游 B 服务B 服务拿到 XID 之后自己作为 RM 向 TC 注册分支事务。 这就是XID 沿着 调用链路传递。Seata 的四种模式一、AT 模式默认最常用自动事务全称Automatic Transaction无侵入业务几乎不用改代码基于undo_log 回滚日志。 前提数据库支持 ACIDMySQL。AT 模式原理两阶段第一阶段执行业务 SQL不提交记录 undo_logRM 执行业务 update/insert/delete记录 undo_log保存修改前镜像 (before image) 和 修改后镜像 (after image)本地事务提交释放数据库锁把分支事务状态上报 TC。⚠️第一阶段就提交本地事务数据库锁释放性能好。第二阶段根据 TC 指令✅提交直接删除对应 undo_log不用再操作数据库。❌回滚读取 undo_log 里 before_image把数据还原成修改前的值然后删除 undo_log。必须建表每个业务库都要执行undo_log表。建表 SQLMySQL 示例Seata 官方提供了标准建表脚本每个参与 AT 模式事务的业务库都要执行一次CREATE TABLE IF NOT EXISTS undo_log ( branch_id BIGINT NOT NULL COMMENT branch transaction id, xid VARCHAR(128) NOT NULL COMMENT global transaction id, context VARCHAR(128) NOT NULL COMMENT undo_log context,such as serialization, rollback_info LONGBLOB NOT NULL COMMENT rollback info, log_status INT(11) NOT NULL COMMENT 0:normal status,1:defense status, log_created DATETIME(6) NOT NULL COMMENT create datetime, log_modified DATETIME(6) NOT NULL COMMENT modify datetime, UNIQUE KEY ux_undo_log (xid, branch_id) ) ENGINE InnoDB AUTO_INCREMENT 1 DEFAULT CHARSET utf8mb4 COMMENT AT transaction mode undo table;容易混淆的点表建在哪里是否手动建undo_log每个业务库必须手动建global_table/branch_table/lock_table/distributed_lockSeata ServerTC端的数据库需手动建TC 启动时也不会自动建除非用了某些存储模式的初始化脚本小结AT 模式下每个业务库都必须有undo_log表否则分支事务注册时会报错。官方只提供 SQL 脚本不会自动执行需要你自己在业务库里跑一遍。如果用的是TCC / Saga / XA 模式则不需要undo_log表 —— 这张表是 AT 模式独有的。⚠️注意区分和MySQL InnoDB 的 undo logMySQL InnoDB 的 undo logSeata 的undo_log表是什么InnoDB 存储引擎内部的事务日志用户业务库里的一张普通业务表谁管理MySQL 引擎自己管理对用户透明Seata 客户端RM读写MySQL 只把它当普通表存在哪里系统表空间ibdata1或独立 undo 表空间undo_001、undo_002你的业务数据库里一张可见的表需不需要建不需要MySQL 初始化时自动创建需要因为它就是一张用户表用途支持事务回滚、MVCC快照读记录数据修改前的镜像供 Seata 分布式事务回滚用AT 模式问题脏写问题Seata 用全局锁解决。全局锁第一阶段更新数据时向 TC 登记行锁其他事务不能修改同一行避免覆盖。使用 AT 模式代码极其简单1. Feign调用的代码只要在入口方法上加GlobalTransactional其余微服务不需要额外注解Feign 自动传递 XID。GlobalTransactional // ✅开启全局事务TM标记 public void createOrder(){ // 1.本地保存订单 orderMapper.insert(order); // 2.远程调用扣余额account‑service RM accountFeign.deduct(userId,money); // 3.远程调用扣库存storage‑service RM storageFeign.deduct(goodsId,count); }2. 使用 RestTemplate 实现⚠️RestTemplate不会自动传递 XID需要手动处理请求头Feign 有 Seata 内置拦截器自动透传 X‑XIDRestTemplate 没有必须手动把 XID 放到 HTTP 请求头下游服务也要写过滤器从请求头取出 XID 绑定到RootContext。Seata AT 完整实战案例业务架构Nacos同时作为 Seata‑Server 注册中心 微服务注册中心Seata‑Server(TC)注册到 Nacos集群名default存储模式 db生产order‑serviceTM同时也是 RM本地插入订单RestTemplate 调用下游。数据库order_dbaccount‑serviceRM扣用户余额。数据库account_dbstorage‑serviceRM扣商品库存。数据库storage_db重点RestTemplate不会自动传递 XID需要发送端增加拦截器下游服务增加 Filter 解析绑定 XID。 全部业务库使用新版官方 undo_log 表。步骤 1undo_log 建表每个业务库执行 undo_log 建表order_db /account_db/storage_db 都执行CREATE TABLE IF NOT EXISTS undo_log ( branch_id BIGINT NOT NULL COMMENT branch transaction id, xid VARCHAR(128) NOT NULL COMMENT global transaction id, context VARCHAR(128) NOT NULL COMMENT undo_log context,such as serialization, rollback_info LONGBLOB NOT NULL COMMENT rollback info, log_status INT(11) NOT NULL COMMENT 0:normal status,1:defense status, log_created DATETIME(6) NOT NULL COMMENT create datetime(微秒), log_modified DATETIME(6) NOT NULL COMMENT modify datetime(微秒), UNIQUE KEY ux_undo_log (xid, branch_id) ) ENGINE InnoDB AUTO_INCREMENT 1 DEFAULT CHARSET utf8mb4 COMMENT AT transaction mode undo table; ALTER TABLE undo_log ADD INDEX ix_log_created (log_created);⚠️ Seata‑Server 自己的库不需要 undo_logundo_log 只在业务数据库。⚠️重要每个参与分布式事务的数据库都要执行建undo_log表 SQL。步骤 2Maven 依赖三个微服务都需要!-- seata starter -- dependency groupIdio.seata/groupId artifactIdseata-spring-boot-starter/artifactId version1.7.1/version /dependency !-- nacos注册发现 -- dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId /dependency步骤 3配置文件以 order‑service application.yml 举例account、storage 只改 application‑idspring: application: name: order-service cloud: nacos: discovery: server-addr: 127.0.0.1:8848 seata: application-id: order-service tx-service-group: my_tx_group service: vgroup-mapping: my_tx_group: default # 和seata-server集群名称对应 registry: type: nacos nacos: server-addr: 127.0.0.1:8848 namespace: public group: SEATA_GROUP步骤 4order‑service 代码TM RM4.1 RestTemplate 配置增加拦截器自动塞入 X‑XID 请求头import io.seata.core.context.RootContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.web.client.RestTemplate; import java.util.Collections; Configuration public class RestTemplateConfig { Bean public RestTemplate restTemplate() { RestTemplate restTemplate new RestTemplate(); ClientHttpRequestInterceptor interceptor (request, body, execution) - { String xid RootContext.getXID(); if(xid ! null){ // Seata固定请求头常量 X‑XID request.getHeaders().add(RootContext.KEY_XID, xid); } return execution.execute(request, body); }; restTemplate.setInterceptors(Collections.singletonList(interceptor)); return restTemplate; } }4.2 OrderService 业务代码import io.seata.spring.annotation.GlobalTransactional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; Service public class OrderService { Autowired private OrderMapper orderMapper; Autowired private RestTemplate restTemplate; // TM开启Seata全局事务 AT模式 GlobalTransactional public void createOrder(Long userId, Long goodsId, Integer count, Integer money) { // 1.本地事务插入订单当前order‑serviceRM Order order new Order(); order.setUserId(userId); order.setGoodsId(goodsId); order.setCount(count); order.setMoney(money); orderMapper.insert(order); // 2.RestTemplate远程调用 account‑service 扣余额 String accountUrl http://account-service/account/deduct?userId userId money money; restTemplate.getForObject(accountUrl, String.class); // 3.RestTemplate远程调用 storage‑service 扣库存 String storageUrl http://storage-service/storage/deduct?goodsId goodsId count count; restTemplate.getForObject(storageUrl, String.class); // 模拟异常如果抛出异常全局事务回滚 // int i 1 / 0; } }关键点GlobalTransactional标记当前服务为TM向 TC 申请全局 XIDXID 保存在RootContextThreadLocal。 RestTemplate 拦截器把 XID 放到 HTTP HeaderX‑XID发给 account‑service、storage‑service。步骤 5下游 account‑service、storage‑service 公共 Filter两个服务都要配置RestTemplate 调用过来下游必须配置 Filter从 http 请求头取出 X‑XID绑定到当前线程 RootContext否则下游没有 XID不会加入全局事务分布式事务失效import io.seata.core.context.RootContext; import jakarta.servlet.*; import jakarta.servlet.http.HttpServletRequest; import org.springframework.stereotype.Component; import java.io.IOException; Component public class SeataXidFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpReq (HttpServletRequest) request; String xid httpReq.getHeader(RootContext.KEY_XID); boolean bind false; if(xid ! null){ RootContext.bind(xid); bind true; } try{ chain.doFilter(request,response); }finally { // web线程池复用必须解绑防止旧XID污染后续请求 if(bind){ RootContext.unbind(); } } } }account‑service Controller 示例RestController RequestMapping(/account) public class AccountController { Autowired private AccountMapper accountMapper; GetMapping(/deduct) public String deduct(Long userId, Integer money){ // Filter已经把XID绑定当前线程当前服务作为RM accountMapper.deductBalance(userId,money); return 扣余额成功; } }storage‑service 的 Controller 逻辑完全一样。完整执行流程带 undo_log 变化演示初始数据order_dborder 表空account_dbuser_id1001balance1000storage_dbgoods_id2001count100调用入口orderService.createOrder(1001,2001,2,200)阶段 1TM 开启全局事务生成 XIDGlobalTransactional生效order‑service (TM) 向 TC 申请全局事务 ID。示例XID 127.0.0.1:8091:40001XID 存入 order‑service 当前线程RootContext。阶段 2order‑service 本地插入订单它同时也是 RM执行orderMapper.insert(order)Seata RM 代理数据源拦截 insert。查询 before_image插入前没有数据after_image 就是新插入订单的数据。同一个本地事务insert 订单 insert undo_logorder_db 的 undo_logbranch_id5001xid127.0.0.1:8091:40001log_status0提交本地 MySQL 事务order 表已经有这条订单释放锁。RM 向 TC 注册分支事务branch_id5001。阶段 3RestTemplate 调用 account‑service 扣余额RestTemplate 拦截器读取RootContext.getXID()放入 Http 请求头X‑XID:127.0.0.1:8091:40001。HTTP 请求发到 account‑service。SeataXidFilter拦截请求取出 header 中的 XID执行RootContext.bind(xid)绑定到 account 服务工作线程。执行accountMapper.deductBalance(1001,200)。RM 查询 before_imagebalance1000执行 updatebalance 变成 800同一个事务插入 account_db.undo_logbranch_id5002log_status0提交本地事务余额已经 800释放锁。account‑service RM 向 TC 注册分支 branch_id5002。返回 http 响应。阶段 4RestTemplate 调用 storage‑service 扣库存同样 HTTP 请求头携带同一个 XID。storage 服务 Filter 绑定 XID。扣库存storage_db 插入 undo_logbranch_id5003log_status0。提交本地事务向 TC 注册分支 branch_id5003。返回。此时三个数据库 undo_log 都有 log_status0 记录所有本地事务全部提交完成。TC 上记录全局 XID 以及 3 个 branch_id。分支 A全部正常没有异常注释掉 int i1/0createOrder 方法执行完毕TM 通知 TC全局事务提交。TC 向 3 个 RM 下发 commit 指令。每个 RM 收到 commit直接删除自己库中对应 xidbranch_id 的 undo_log 记录。全部 undo_log 清空。✅最终状态订单创建成功余额 800库存扣减成功。分支 B异常打开模拟异常int i 1 / 0;方法抛出异常代码抛出算术异常。GlobalTransactional捕获异常TM 通知 TC全局事务回滚。TC 向 3 个 RM 下发 rollback 指令。order‑service RM读取 order_db 的 undo_log拿到 before/after 快照执行补偿删除订单删除 undo_log。account‑service RM读取 account_db undo_log恢复 balance1000删除 undo_log。storage‑service RM读取 storage_db undo_log恢复库存删除 undo_log。✅最终订单消失、余额回到 1000、库存恢复全部回滚数据一致。重点故障演示如果没有写下游 SeataXidFilter 会发生什么下游请求拿不到X‑XIDRootContext.getXID() null。account、storage 执行 SQL不会注册分支事务到 TC不会写入 undo_log。抛出异常触发全局回滚order‑service 订单被回滚删除但是扣余额、扣库存已经提交数据不一致关键注意点RestTemplate 必须发送端拦截器、接收端 Filter 成对出现Feign 不需要seata 自动装配。Filter 中finally必须unbind()线程池不复用清理旧 XID 残留会污染后续普通请求。undo_log 每个业务库独立建表不能只建一张。Seata‑Server 生产必须 db 存储模式不能 file 模式否则重启丢失事务状态二阶段无法处理。GlobalTransactional必须加在业务入口方法内部方法调用不加 AOP 不会生效。面试提问参考Q上面例子一阶段各个 RM 就已经提交本地事务了为什么还能回滚 A依靠 undo_log 快照保存修改前数据二阶段做补偿 SQL属于补偿式分布式事务不需要持有数据库长锁。QXID 是怎么从 order‑service 传递到 account‑service ARestTemplate 拦截器把 XID 放入 HTTP 请求头 X‑XID下游 Filter 取出绑定到 RootContext。关键注意点为什么 Feign 不用写 FilterSeata starter 自动注册 Feign 拦截器 接收端的拦截器RestTemplate 没有内置接收处理下游必须手写 Filter 绑定 XID。finally 一定要执行RootContext.unbind()web 容器是线程池线程会复用。不 unbind旧 XID 残留在线程后面普通请求会错误加入旧全局事务产生严重 bug。测试手动在代码写int i1/0;制造异常。开启GlobalTransactional全部回滚订单插入、扣余额、扣库存全部撤销。如果把 Filter 注释掉下游拿不到 XID本地订单回滚但是扣余额扣库存提交成功数据不一致。对比 Feign 与 RestTemplate表格方式发送方接收方FeignSeata 自动拦截器添加 X‑XIDSeata 自动拦截器解析 X‑XID无需编码RestTemplate需要自己写拦截器添加 X‑XID自己写 Filter 解析绑定 XID一句话总结⚠️注意RestTemplate 只是普通 HTTP 客户端Seata 没有为它做适配需要我们自己在请求发送时把 XID 塞入请求头下游服务从请求头取出 XID 绑定到 RootContextRM 才能加入全局事务。二、TCC 模式手动编码T‑Try C‑Confirm C‑CancelTCCTry、Confirm、Cancel完全由业务代码自己写逻辑没有 undo_log 表。 适合不支持 AT 的场景比如非数据库资源调用外部接口、Redis。三段式Try预留资源。检查、冻结资源冻结余额、冻结库存不真正扣减。Confirm确认执行。全部 Try 成功真正执行业务真正扣钱扣库存。Cancel取消回滚。任意 Try 失败释放预留资源解冻。每个分支都要写 Try/Confirm/Cancel 三套业务代码侵入性高开发量大。 Seata-TCC 框架负责调度这三个方法业务实现逻辑。LocalTCC public interface AccountTCCService { TwoPhaseBusinessAction(name deduct,commitMethod confirm,rollbackMethod cancel) void tryDeduct(BusinessActionContextParameter(paramName userId) Long userId, BusinessActionContextParameter(paramName money)BigDecimal money); boolean confirm(BusinessActionContext ctx); boolean cancel(BusinessActionContext ctx); }坑幂等Confirm/Cancel 可能重复调用代码必须做幂等处理。三、SAGA 模式长事务适合长流程适合长业务流程比如订单履约、流程耗时很长。 把大事务拆成一串本地事务每个服务提供正向操作 补偿回滚操作。正常依次执行每个正向服务。某一步失败逆序执行前面每一步的补偿回滚。两种实现注解 Saga简单状态机 Saga复杂流程写 json 状态定义文件没有锁性能高没有隔离性中间数据对外部可见。适合业务可以接受中间状态暴露的长流程。四、XA 模式XA 是数据库标准分布式事务协议。一阶段各个 RM 数据库执行 SQLprepare不提交。二阶段TC 通知全部 commit /rollback。缺点一阶段会持有数据库锁直到二阶段完成锁时间长性能差。 优势数据库原生强隔离。一般很少线上使用。表格模式侵入性性能适用场景AT低仅注解高MySQL 微服务绝大多数业务首选TCC高手写三套代码高非数据库资源、特殊业务SAGA中高长流程业务允许中间状态可见XA低差强一致并发低场景企业开发 90% 场景使用 AT 模式。Seata 架构部署Seata‑ServerTC独立部署各个微服务引入seata‑spring‑boot‑starter作为 TM/RM。Seata Server 存储模式file内存重启丢失数据仅测试。db把事务日志存在数据库生产必须用 db 模式。注册中心Seata Server 注册到 Nacos微服务通过 Nacos 找到 TC。SpringBoot 微服务依赖dependency groupIdio.seata/groupId artifactIdseata-spring-boot-starter/artifactId version1.7.1/version /dependencyapplication.yml微服务端seata: application-id: order-service tx-service-group: my_tx_group #事务组 service: vgroup-mapping: my_tx_group: default #映射TC集群名 registry: type: nacos nacos: server-addr: 127.0.0.1:8848 namespace: public group: SEATA_GROUPSeata Server 也注册到 Nacos微服务不用写 TC 的 IP通过 nacos 发现 TC。AT 模式常见坑面试高频每个业务数据库必须创建 undo_log 表少一个库就回滚失效。GlobalTransactional只加在事务发起入口方法不能加在内部调用方法上AOP 失效。XID 依靠 Feign 传递如果是其他调用方式需要手动传递 XID。AT 模式不支持 DDL 语句只支持 DML update/insert/delete。全局锁高并发修改同一行会等待全局锁出现等待。Seata Server 生产环境必须配置 db 存储不能 file 模式否则重启丢失事务状态无法回滚。幂等二阶段请求可能重试虽然 AT 框架内部做了但业务也要注意。面试经典问题AT 模式一阶段为什么提交本地事务第一阶段提交本地事务释放数据库行锁提升并发性能依靠 undo_log 做回滚。AT 模式和 TCC 区别AT 框架自动生成 undo_log业务几乎无侵入TCC 全部补偿逻辑业务手写适合非数据库资源。如果 Seata Server 宕机已经提交的事务会怎么样Seata Server 重启后读取数据库事务日志继续完成未完成的二阶段提交 / 回滚。什么是空回滚什么是悬挂空回滚分支还没执行 try 业务收到回滚指令。RM 记录空回滚标记不执行业务。悬挂回滚先执行完再收到分支业务请求。RM 发现有空回滚标记拒绝执行业务。 Seata AT 已经内置处理空回滚、悬挂。注意⚠️异步 MQ 场景不能直接简单用 Seata‑AT但同步 RPC 场景AT 是首选。不是 AT 不能用分布式事务而是AT 强依赖 XID 在调用链路透传。先分清两种场景✅同步调用A 服务 Feign/RestTemplate 调用 BA 等待 B 返回Seata AT完全可以用生产最常用。⚠️异步 MQA 发消息到 MQB 消费消息两者没有同步调用链路不能直接用 AT。为什么 MQ 异步不能直接 AT AT 需要 XID 顺着请求链路传递。A 服务 TM 产生 XID发 MQ 消息之后A 的本地线程就结束了MQ 消费是另外一个独立线程、独立请求没有任何机制自动把 XID 带到消费者那边。 消费者拿到消息时线程里没有 XIDRM 无法注册分支事务AT 失效。场景 1同步 RPCorder‑service → account‑service → storage‑serviceGlobalTransactional public void createOrder(){ orderMapper.insert(order); // feign/restTemplate同步远程调用 accountFeign.deduct(); storageFeign.deduct(); }✅AT 模式完美支持这就是 AT 的标准使用场景。场景 2MQ 异步A 发消息B 异步消费示例order‑service 创建订单发送 MQ 消息storage‑service 监听 MQ 消息扣库存。order‑service(TM) → send MQ消息 → storage‑service异步消费消息扣库存❌直接写下面代码是错误AT 不生效GlobalTransactional public void createOrder(){ orderMapper.insert(order); mqProducer.send(goodsMessage); //发送消息之后线程结束 // 消息消费者在另一个线程没有XID扣库存是普通本地事务 }问题GlobalTransactional只作用在 order‑service 这个调用线程MQ 消费者是另一个独立线程没有 XID如果扣库存失败订单不会回滚数据不一致。那 MQ 异步场景有哪些解决方案方案 1Seata‑TCC手动编码生产者、消费者都实现 TCC 三段逻辑生产者 Try 阶段发送预备消息Confirm 才真正投递消息Cancel 删除消息。侵入大要手写 Try/Confirm/Cancel。方案 2Seata‑SAGA把整个业务编排成状态机每一步定义正向操作 补偿操作 消息消费失败执行前面业务的补偿回滚。适合长流程。方案 3RocketMQ 事务消息半消息RocketMQ 原生事务消息保证本地事务执行成功消息才对外可见。 适合本地数据库操作 发 MQ 消息。但是它只能保证「本地事务和发消息原子性」不能管理消费者内部的分布式事务。 也就是order 库插入订单成功消息一定发送成功但是 storage 消费消息扣库存失败RocketMQ 事务消息帮不了你需要业务重试 / 死信队列。方案 4手动传递 XID强行 AT不推荐可以手动把 XID 放到 MQ 消息体消费者拿到消息取出 XID手动RootContext.bind(xid)。⚠️大坑TM发起全局事务的那个服务的事务在发送完消息之后很可能已经结束提交 / 回滚了。 等消费者消费消息的时候全局事务早就结束了TC 上已经没有这个 XID 的事务状态RM 注册分支失败。 所以这种方式基本不可行不建议这么干。AT 模式的全局事务生命周期和TM 方法执行的生命周期绑定。GlobalTransactional修饰的方法执行完毕全局事务就结束。MQ 消费者是晚于 TM 方法执行的时间对不上。各个模式适合什么场景场景推荐方案说明微服务之间同步 Feign/RestTemplate 调用都是 MySQL 数据库✅Seata AT首选低侵入跨服务异步 MQ链路断开时间不同步❌不能直接 AT优先 TCC / SAGA / RocketMQ 事务消息资源不是数据库调用外部接口、Redis✅TCCAT 依赖 undo_log 数据库不适用业务流程很长耗时久不适合锁等待✅SAGA无锁允许中间状态对外可见面试高频问题Q为什么异步 MQ 不能直接用 Seata ATAT 的全局事务生命周期绑定 TM 方法执行TM 方法执行完成全局事务就结束。MQ 消费是异步晚执行消费的时候全局事务可能已经结束。没有自动传递 XID 的链路就算手动把 XID 塞进消息TC 上该全局事务已经结束无法注册新的分支事务。Q那什么时候 AT 不能用不是数据库资源Redis、第三方 http 接口异步 MQ 解耦场景有 DDL 语句数据库不支持 undo_log。QTCC/SAGA/ 事务消息和 AT 的区别AT框架自动生成 undo_log适合同步数据库微服务调用TCC业务手写预留、确认、回滚逻辑同步异步都可以SAGA正向 补偿长流程异步业务RocketMQ 事务消息解决【本地 DB 发消息】原子性管不到消费者内部。一句话总结Seata AT 不是不能做分布式事务它只适合「同步调用链路」的分布式事务一旦变成 MQ 异步调用链路断裂、时间错位AT 就不再适用这时才考虑 TCC、SAGA、事务消息。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻