Seata 分布式事务核心原理详解(TC/TM/RM、AT/XA/TCC/Saga、锁机制)
前言在微服务架构下业务会拆分到多个独立服务跨服务操作就会遇到分布式事务问题保证多个微服务的数据一致性。Seata 是目前主流的分布式事务框架本文重点拆解 Seata 的三大核心组件 TC/TM/RMAT模式完整执行时序、锁模型补充 TCC 模式以及 AT/XA/TCC/Saga 的适用场景对比同时梳理开发中高频踩坑点。Seata AT模式是业务开发最常用方案本文案例均基于Seata。一、Seata 三大核心组件TC、TM、RMSeata 架构由三个角色组成TC事务协调者、TM事务管理器、RM资源管理器。1. TCTransaction Coordinator事务协调者Seata ServerTC是独立部署的服务端进程需要单独下载 seata-server 包启动。职责存储全局事务、分支事务状态接收TM/RM注册与状态上报驱动所有分支事务执行二阶段提交/回滚是全局事务的总指挥中心。存储模式file 文件存储TC重启事务数据丢失仅用于Demo测试db数据库存储将全局事务、分支事务、锁信息存入global_table/branch_table/lock_table测试环境常用raft集群模式生产推荐TC多节点集群leader宕机自动选主解决TC单点故障⚠️单点故障问题TC宕机后新的全局事务无法创建已经开启但未完成的全局事务二阶段提交/回滚无法执行分布式事务卡住。补充已经完成一阶段提交的本地数据不会自动回滚等TC服务恢复后TC会定时扫描未完成事务继续执行二阶段流程。2. TMTransaction Manager事务管理器客户端组件TM不是独立进程是嵌入业务微服务的客户端组件引入seata-spring-boot-starter就自带该能力。职责开启全局事务GlobalTransactional标记全局事务边界业务执行完毕后通知TC全局事务提交或回滚。特点一次全局事务中只有一个TM也就是发起分布式事务的服务。3. RMResource Manager资源管理器客户端组件RM同样是客户端组件嵌入所有参与分布式事务的微服务。发起事务的服务可以同时是TMRM。职责管理本地数据库资源向TC注册分支事务上报分支执行状态接收TC下发二阶段指令执行本地分支提交或回滚AT模式依靠undo_log实现补偿回滚。一句话通俗类比TC 中央调度指挥部RM 各个业务节点订单、库存TM 发起本次跨多节点操作的请求入口。为什么不能让TM兼任TC3个核心原因1.TM所在业务服务随时可能宕机最核心订单服务作为TM如果同时充当TC。一阶段全部执行成功准备执行二阶段时订单服务宕机。事务状态保存在业务服务内库存、账户分支事务一阶段已经提交无人触发二阶段undo_log和锁无法清理造成数据不一致。独立TC场景事务状态持久在TC即使TM业务服务宕机TC仍然存活定时扫描未完成事务等下游服务在线后继续下发二阶段指令保证事务闭环。核心原则分布式事务状态必须保存在独立第三方不能放在参与事务的业务节点上。业务节点会重启、下线、扩容。2.TM只是事务参与者之一无法拥有全部分支视图一次事务链路订单(TM)→库存(RM)→账户(RM)。TM只清楚自己本地分支情况无法实时感知其他分支执行状态。网络分区场景下TM收集信息不全无法判断全局提交/回滚。TC作为公共中心所有RM上报状态TC持有全部分支事务完整视图统一仲裁全局结果。3.业务服务频繁上下线不适合承担状态持久与调度工作订单、库存这类业务微服务会频繁发布重启、弹性扩缩容不适合长期保存事务状态也不适合长时间重试调度。TC专门负责状态持久、定时扫描、重试调度独立部署高可用集群实现职责解耦。和经典2PC对照Seata TC就是原生2PC中的协调者TM/RM属于参与者侧组件。如果TM兼任TC就是参与者充当协调者分布式系统中单点风险极高。TM的存在意义不能直接删掉TM只留TCRMTC只会被动接收指令保存事务状态下发指令。TC不会主动感知业务什么时候开启事务。TM负责事务入口工作业务代码走到GlobalTransactional方法TM调用TC创建全局事务拿到XIDXID放入调用链路传递给下游所有微服务业务执行完成TM向TC上报全局事务投票结果提交/回滚业务抛异常通知TC全局回滚。如果没有TM没有组件申请全局事务、生成XID、传递XID下游RM不知道当前属于哪个全局事务无法注册分支。RM仅在收到XID后才能注册分支、执行二阶段指令。类比TC是裁判RM是球员TM是教练。教练告诉裁判比赛开始比赛结束上报结果只有裁判球员比赛无法启动。重要提醒TM不是独立服务。引入seata starter的微服务自带TMRM。发起事务的服务激活TM能力下游被调用服务仅使用RM能力。二、注解区分Transactional VS GlobalTransactional高频踩坑注解归属作用范围TransactionalSpring原生仅管控当前JVM本地数据库事务感知不到远程微服务无法实现分布式事务GlobalTransactionalSeata提供开启Seata全局分布式事务底层封装TM逻辑生成XID串联所有RM分支事务SpringBoot中 EnableTransactionManagement 误区SpringBoot自动装配DataSourceTransactionManagerAutoConfiguration只要引入数据库相关依赖SpringBoot自动注册事务管理器自动开启AOP事务代理。SpringBoot项目EnableTransactionManagement可省略属于冗余注解加不加不影响Transactional本地事务生效SSM原生Spring项目必须手动添加EnableTransactionManagement开启事务AOP。对Seata的GlobalTransactional来说启动类加不加EnableTransactionManagement不影响全局事务。GlobalTransactional会创建独立Seata事务切面不依赖Spring原生事务AOP但是分支内部仍然依赖Spring本地事务管理器。✅工程标准写法AT模式业务链路A服务 → B服务 → C服务A请求入口分布式事务发起方添加GlobalTransactional同时具备TMRM能力BA远程调用的中间服务仅RM只需要TransactionalCB远程调用的下游服务仅RM只需要Transactional场景1前端下单请求链路 A→B→C// A服务TMRMServicepublic class OrderService {// 全局事务入口只用GlobalTransactional不要叠加TransactionalGlobalTransactionalpublic void createOrder(){// 本地创建订单RM分支orderMapper.insert();// 远程调用BbFeign.deduct();}}// B服务RMServicepublic class AccountService {// 分支方法保留Spring原生TransactionalTransactionalpublic void deduct(){// 扣余额远程调用CcFeign.reduceStock();}}// C服务RMServicepublic class StorageService {Transactionalpublic void reduceStock(){// 扣库存}}场景2后台管理接口直接访问B服务链路 B→C此时B变成链路最外层入口B上添加GlobalTransactionalB作为TM下游C依旧只保留Transactional。这是一套独立全局事务和场景1A发起的事务互不干扰。强制编码规范避坑全局入口方法不要同时写Transactional GlobalTransactional切面优先级混乱虽然偶尔能跑禁止混用。入口只用GlobalTransactional下游RM分支必须保留Transactional。AT模式一阶段业务SQL和undo_log写入必须在同一个本地事务保证原子性不加会导致undo_log写入失败回滚失效仅链路最外层入口添加GlobalTransactional下游微服务不能添加GlobalTransactional。下游添加会新建独立全局事务不是同一个事务一个调用链路只能有一处GlobalTransactional多处会创建多个独立全局事务引发异常。三、AT模式完整执行时序修正先TM创建全局事务再执行业务分支AT模式整体分为两大阶段一阶段准备、二阶段提交/回滚TC驱动整体前置流程调用A服务GlobalTransactional方法TM向TC申请创建全局事务生成全局唯一XIDXID放入请求头随远程调用传递到下游B、CB、C拿到XID后激活RM各个服务RM执行本地分支事务下面一阶段时序每个分支独立执行下面步骤单分支一阶段完整时序全部在同一个本地事务内未commitRM解析SQL查询数据前镜像执行业务SQLupdate/insert/delete查询数据后镜像插入undo_log回滚记录RM调用TC注册分支事务申请全局锁拿锁失败直接回滚本地事务业务SQL、undo_log全部撤销拿到全局锁成功执行本地事务commitcommit本地事务后MySQL原生行锁释放RM上报TC分支一阶段执行成功。核心设计目的先生成undo_log再注册分支规避空回滚问题。如果反过来先注册分支拿锁再执行业务SQL。SQL执行报错TC已经记录分支但RM没有undo_log后续全局回滚时找不到回滚数据触发空回滚异常。同时lockKey需要执行SQL拿到主键构造表名主键传给TC申请锁未执行SQL前无法获取主键。二阶段TC统一调度TC收集所有分支一阶段结果做全局仲裁✅ 所有分支一阶段成功TC下发提交指令给全部RMRM收到指令删除undo_log二阶段完成。❌ 任意分支失败TC下发回滚指令给全部RMRM收到回滚指令后先校验当前数据库真实数据如果数据库数据没有被其他事务修改直接使用undo_log前镜像执行回滚恢复数据完成后删除undo_log如果数据库数据已经被其他业务修改出现脏写冲突不能直接覆盖数据Seata会抛出异常终止自动回滚流程交由业务设置对应的人工处理/告警逻辑防止覆盖第三方更新的数据造成数据错乱。⚠️重点面试考点AT二阶段回滚不是无脑覆盖会先比对当前库数据与undo_log后镜像。一旦检测到数据被外部修改自动回滚不再执行避免强行覆盖别人已提交的数据。这种场景属于数据冲突需要业务监控告警、人工介入处理。四、并发场景XA vs Seata AT锁阻塞差异XA传统2PC一阶段执行SQL加MySQL行锁事务不commit。行锁和数据库连接一直占用直到二阶段TC下发commit/rollback才释放。阻塞位置数据库内核层面阻塞后果长事务长时间占用数据库连接连接池容易耗尽数据库压力大锁持有时间整个分布式事务生命周期。Seata AT全局锁阻塞T2执行update、生成undo_log之后在RM调用TC申请全局锁这一步被拦截。T2业务SQL在本地事务内部执行成功MySQL行锁短暂持有但还没有commit。拿到锁失败直接rollback本次修改不会持久到数据库。阻塞位置业务代码和TC之间应用层逻辑判断阻塞特点默认快速失败不阻塞等待锁重试需要手动配置参数开启配置参数seata.client.lock.retryTimes、seata.client.lock.retryInterval⚠️风险开启长时间锁重试T2会持续持有MySQL行锁占用数据库连接丧失AT高性能优势类似XA长锁问题。对比项XA MySQL行锁阻塞Seata AT TC全局逻辑锁阻塞阻塞位置MySQL数据库内TC应用层阻塞时是否执行update✅已执行update数据库锁等待✅事务内执行updatecommit前拦截是否长期占用数据库连接是默认不会快速失败回滚代价需要回滚已执行SQL拿锁失败直接回滚代价小五、AT模式隔离级别脏读、脏写AT模式默认读未提交Read Uncommitted假设T1一阶段完成本地事务commit库存更新为98TC全局锁还持有等待二阶段。1.普通select查询无GlobalTransactional不受Seata管控直接读取数据库已提交数据可以读到库存98。如果后续T1二阶段回滚就出现脏读。2.Seata全局事务T2默认隔离级别T2普通select同样读到脏数据如果T2尝试update这一行申请全局锁会被拦截最终回滚AT默认一定防止脏写。3.手动开启Seata读已提交 GlobalTransactional(isolation Isolation.READ_COMMITTED)T2查询时RM主动访问TC校验全局锁如果该行存在全局锁RM读取undo_log前镜像返回修改前旧数据避免脏读。代价查询增加一次TC远程调用读取undo_log性能下降。总结AT默认允许脏读禁止脏写读已提交是应用层实现非数据库原生隔离。六、TCC模式新增TCC 介绍TCCTry-Confirm-Cancel是Seata支持的业务补偿型分布式事务无数据库undo_log完全由业务代码编写三段接口属于侵入业务代码的方案。TCC同样基于TC/TM/RM架构TC负责全局事务状态保存与二阶段调度。三个阶段Try预留资源一阶段检查资源冻结/预留业务资源不直接扣减真实数据。例扣余额Try校验余额冻结余额不扣实际账户余额Confirm确认提交二阶段成功分支确认执行业务使用Try阶段预留资源正式提交。例Confirm把冻结的余额真正扣掉Cancel回滚补偿二阶段失败分支释放Try阶段预留资源恢复数据。例Cancel解冻余额恢复冻结额度一句话概括Try做资源预留Confirm确认提交Cancel撤销释放。TCC完整流程TM收到GlobalTransactional向TC创建全局事务拿到XIDXID传递下游所有参与分支执行Try预留资源向TC注册分支TC收集所有Try结果全部Try成功TC下发Confirm指令所有分支执行Confirm完成业务提交任意Try失败TC下发Cancel指令所有分支执行Cancel释放预留资源。TCC优缺点✅优点不依赖数据库undo_log不绑定数据库可以用于非数据库资源调用第三方支付、外部API无AT的脏读问题隔离性由业务自行控制没有全局锁高并发场景性能可控。❌缺点业务侵入极强每一个业务操作必须手动编写Try/Confirm/Cancel三套接口开发工作量巨大需要处理幂等、空回滚、悬挂三大经典TCC异常问题开发门槛高空回滚Cancel先于Try执行悬挂Try晚于Cancel执行幂等Confirm/Cancel重复调用不能重复执行业务业务逻辑复杂维护成本高。适用场景跨第三方服务、非数据库资源、高性能核心链路AT无法满足的场景。七、Saga事务模式长事务场景方案AT、XA不适合长事务人工审批、履约流程。AT虽然释放MySQL行锁但TC全局锁会一直持有到二阶段结束如果等待人工审批几天全局锁占用多天其他分布式事务无法修改该行业务卡死。Saga核心思想将一个大全局事务拆分成一串独立本地子事务。每一步子事务执行完立刻提交本地事务释放数据库锁后续步骤失败执行配套反向补偿接口。全程不持有长锁。案例请假审批流程创建请假单本地事务提交锁释放→等待主管人工审批等待几天无任何锁→审批通过执行人事登记审批拒绝执行补偿接口修改请假单状态为作废。Saga两种实现模式编排式Choreography服务之间互相发MQ消息无中心协调器缺点链路复杂维护困难。编排式Orchestration存在中心Saga协调器驱动各个子服务不一定依赖MQ。Saga优缺点✅优点无长锁适配人工审批、外部回调这类长事务场景❌缺点每个正向业务接口必须手写配套补偿接口开发成本高无隔离性子事务提交后数据立刻对外可见中间态业务数据持久落库补偿接口执行时可能失败需要人工对账兜底。AT/XA追求尽量对外不可见中间状态Saga天生阶段性提交中间数据对外可见仅保证最终一致性。业务开发需要识别中间状态不能把流程未完成的数据当成有效数据。八、XID传递重要前置条件Seata依靠XID串联整个分布式事务链路。XID需要放在请求头跨微服务传输。Feignseata starter自动装配拦截器自动传递XID开箱即用RestTemplate必须手动配置拦截器否则XID无法传递下游RM识别不到全局事务分支不注册异常无法回滚。XID丢失后果下游服务只是独立本地事务出现数据不一致。九、模式横向对比总结模式原理锁特点适用场景业务侵入ATundo_log自动补偿一阶段释放MySQL行锁TC全局锁到二阶段结束微服务短事务数据库场景最常用低几乎不用写额外业务代码TCCTry/Confirm/Cancel三段业务接口业务层资源预留无框架全局锁对接第三方API、非数据库资源、高性能链路极高需要手写3套接口处理空回滚/悬挂/幂等Saga分步本地事务反向补偿无长锁长事务人工审批、履约流程中编写补偿接口XA数据库原生2PC长持有数据库行锁强一致性并发低总结TC是独立部署服务全局事务状态存储与二阶段调度中心TM/RM是客户端组件嵌入业务微服务AT模式执行顺序TM创建全局事务、生成XID → 各RM执行一阶段镜像undo_log注册分支→ TC收集结果驱动二阶段提交/回滚二阶段回滚会校验当前数据库数据数据未修改直接回滚数据已被修改抛出冲突异常等待业务告警人工处理AT适合短事务高性能场景XA适合强一致性场景锁占用时间长TCC适合对接外部系统侵入业务Saga专门用于长事务、人工审批场景编码规范链路入口加GlobalTransactional下游分支保留Transactional保证XID跨服务传递禁止链路多处开启全局事务。