ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Seata 分布式事务全解析:核心原理、四种模式与生产实践

Seata 分布式事务全解析:核心原理、四种模式与生产实践 1. 从一次“钱扣了订单没生成”说起做分布式系统的同学大概率都遇到过这种场面明明业务代码try-catch写得整整齐齐消息也发了数据库也更新了结果一核对A库的数据变了B库的数据没变对不上账。尤其在微服务架构里一个用户下单的动作可能同时触发订单服务、库存服务、积分服务这三个服务各连各的库事务边界天然被拆散了。这时候你就需要一套能把跨服务、跨库的操作“粘”成一个大事务的方案而 Seata 就是干这个的。Seata 的全称是 Simple Extensible Autonomous Transaction Architecture直译过来是“简单可扩展的自治事务架构”名字有点拗口但定位很清晰——它是一套开源的分布式事务中间件由阿里巴巴起步后来捐给了 Apache 基金会。很多人第一次接触 Seata 是被它的 AT 模式吸引业务代码几乎不用改加几个注解就能拿到分布式事务能力。但真正上手之后才会发现Seata 的价值远不止 AT 模式它把 TCC、SAGA、XA 也都收编了一套框架覆盖四种事务方案这在业界是很少见的。这篇内容我按自己实际使用的经验来拆先讲清楚 Seata 的整体设计思路和三个核心角色TC、TM、RM再逐个模式说透最后分享一些生产环境里的配置心得和坑。不管你是刚听说 Seata 想选型还是已经接入但在调优应该都能找到有用的部分。2. 核心角色拆解TC、TM、RM 到底各管什么2.1 三个角色的职责边界Seata 的架构里有一个很重要的设计原则把“协调事务”和“执行事务”分开。它抽象出了三个角色各司其职理解清楚这三个角色整个 Seata 的工作流程就懂了大半。TCTransaction Coordinator事务协调器独立的服务端。它负责维护全局事务的状态比如全局事务的开启、提交、回滚都是由它来拍板的。TC 在 Seata 里就是 server 端需要单独部署生产环境通常还要搭集群。TMTransaction Manager事务管理器嵌入在业务应用里。它的职责是向 TC 发起全局事务的开启、提交或回滚请求。说白了TM 是“发起方”它不直接执行具体的数据操作而是负责给整个事务“定调子”。RMResource Manager资源管理器同样嵌入在业务应用里。它管理的是每个分支事务的资源比如一个订单服务要更新订单表RM 就负责向 TC 注册这个分支事务并且上报分支事务的执行状态让它参与全局提交或回滚。打个比方TC 像是整场演出的总导演TM 是副导演负责跟总导演说“这场戏开始拍了”“这场戏过了”而 RM 是台上的演员每演完一幕都要跟副导演报备“我这段演完了”“我这段演砸了”。2.2 一次完整的全局事务是怎么跑通的理解了角色之后再看流程就清晰了。一次标准的两阶段提交在 Seata 里的执行过程是这样的TM 向 TC 发起begin请求TC 生成一个全局事务 IDXID并记录全局事务状态为 Begin。XID 通过 Dubbo、Spring Cloud 等框架的上下文传递机制传播到参与事务的各个微服务。各个微服务里的 RM 在执行本地事务时向 TC 注册分支事务报告分支事务可以提交。TM 执行完业务逻辑后向 TC 发起commit或rollback请求。TC 根据全局事务里所有分支事务的状态决定整体提交或回滚并驱动各个 RM 执行对应的动作。这里有个关键点你要注意Seata 做的不是“同时提交”而是“协调提交”。它先让所有分支事务做预提交都成功了再统一提交任何一个分支失败就让已经成功的分支回滚。这跟数据库的本地事务不是一个层级的概念它是跨服务、跨库的资源协调。2.3 TM 和 RM 为什么要分开放很多初学者会问TM 和 RM 都是嵌入应用里的为什么不合并成一个这就要说到职责分离的价值了。在 Seata 的设计里一个应用可以同时充当 TM 和 RM比如订单服务既是全局事务的发起方TM又负责自己的分支事务RM。但拆分出来之后你在代码层面就能很清晰地看到发起事务的入口在哪参与事务的资源在哪。排查问题的时候你只需要看是 TM 节点报的错还是 RM 节点报的错问题范围立刻就缩小了。另外这套模型也对应了 Seata 的可扩展性TC 是独立服务可以水平扩容RM 和 TM 作为客户端可以随着业务服务一起扩。不同的团队甚至可以用不同的语言实现 RM只要走 Seata 定义的协议就行。3. 四种事务模式怎么选AT、TCC、SAGA、XA3.1 AT 模式无侵入是最大的卖点但别忽略“全局锁”AT 模式是 Seata 最出圈的模式核心卖点是“对业务代码侵入性极低”。你只需要在业务方法上加上GlobalTransactionalSeata 就能帮你把方法里涉及到的所有数据源操作纳入全局事务。它背后做了一大堆事情其中最关键的是两件事生成 undo_log 和获取全局锁。AT 模式在执行业务 SQL 之前会先解析 SQL生成“镜像”数据——也就是操作前数据的快照before image和操作后数据的快照after image存在每张业务表对应的undo_log表里。如果全局事务需要回滚Seata 就用这些镜像数据反方向执行补偿 SQL把数据还原回去。全局锁是 AT 模式里最容易被忽略又最影响性能的东西。为了保证并发事务不互相覆盖Seata 在 TC 端维护了一把“全局锁”RM 执行本地事务前要先去 TC 申请锁拿不到锁就一直重试。这就带来一个实际的感受AT 模式的吞吐量上限往往不是数据库本身而是 TC 的锁竞争能力。我在压测环境里见过一个极端案例某个热点商品表并发下单量一上来大量事务卡在“获取全局锁”这个环节接口耗时直接从 50ms 飙到 3 秒。后来排查发现问题不在 Seata而是业务代码里一个大事务里执行了太多 SQL导致锁持有时间过长。所以用 AT 模式务必控制全局事务的粒度别把十几张表的更新全塞进一个事务里。3.2 TCC 模式适合对性能有要求、能接受改造的业务TCCTry-Confirm-Cancel是一种补偿型事务方案它的思路是每个参与事务的业务都要实现三个方法——Try预留资源、Confirm确认执行业务、Cancel取消执行补偿回滚。Seata 在这套机制之上提供了事务状态的管理和驱动帮你把各个服务的 Try 都调完再逐个调 Confirm任何一个分支失败就反向调所有已确认分支的 Cancel。TCC 比 AT 模式好在哪第一它不依赖数据库的 undo_log只要你业务上能实现补偿逻辑理论上任何资源都能纳入事务比如 Redis 里的库存扣减第二TCC 的锁粒度由业务自己控制不像 AT 模式那样有全局锁并发性能上限更高。但代价也很明显业务改造量大每一个参与方都必须写三个方法而且三个方法必须满足幂等性——因为 Confirm 和 Cancel 可能会被重复调用。我见过不少团队TCC 的 Try 写得贼利落Confirm 逻辑里却带了状态判断导致重复调用时直接出错。记住TCC 三个方法的幂等是硬要求不是可选优化项。3.3 SAGA 模式和 XA 模式各回各家各找各妈SAGA 模式的核心思想是“长事务拆短事务”把一个长链路拆成多个本地事务每个事务执行完就提交如果后续某个环节失败就反向执行之前每个事务的补偿操作。它特别适合业务流程很长、中间环节多的场景比如一个完整的订单履约链路创建订单 → 锁库存 → 扣优惠券 → 通知物流。这些环节如果串在一个 AT 大事务里事务拆装成本太高用 SAGA 就能把每个环节的失败补偿逻辑写清楚就行。XA 模式则是直接利用数据库对 XA 协议的原生支持Seata 在这里更像一个“会话管理器”负责协调所有分支事务的 prepare 和 commit/rollback。它的优点是强一致性最强缺点也明显数据库层面的事务隔离级别会被拉升并发性能损耗大而且很多数据库的 XA 支持并不完善。这四种模式不是互斥的实际生产环境完全可以根据业务特点混合使用。比如订单主链路用 AT积分服务用 TCC跨系统的对接链路用 SAGA同一个 Seata 集群可以同时承载多种模式。4. 部署与配置的实战要点4.1 服务端 TC 部署的三个配置坑Seata 的 server 端TC部署本身不难下载发行包解压就能启动但真正到了生产环境有几个配置项值得特别注意。第一是存储模式。Seata server 的事务状态默认可以存文件、DB 或 Redis。本地调试用 file 模式最省事但生产环境必须用 DB 模式否则 TC 一重启所有全局事务状态全丢正在执行的业务会直接卡死。DB 模式需要建一张global_table、一张branch_table和一张lock_table官方脚本里有别自己瞎猜字段。第二是事务日志的超时清理。TC 里有个配置叫sessionReloadTimeout以及定时清理会话的机制。如果全局事务一直不结束对应的 session 就会堆积在表里。我见过一个极端场景运维发现global_table涨到了几百万行原因是某个服务代码写死了不回滚每个事务就挂在“已提交”状态没人清。后来只能人工清理。所以接入 Seata 之后监控全局事务数量是日常运维的必修课。第三是网络超时。Seata 客户端和服务端之间的通信走的是自定义的协议默认超时时间可能需要调大。如果你的服务调用链比较长微服务之间经过多跳网络建议把 timeout 设到 5 秒以上否则高峰期很容易出现 RM 还没注册完TM 那边就已经判定超时了。4.2 客户端接入时的数据源代理逻辑Seata 客户端接入时要做的核心事情是让 Seata 代理你的数据源。AT 模式之所以能自动生成 undo_log、自动解析 SQL本质上是因为它用自己的DataSourceProxy包了一层你的原始数据源。在 Spring Boot 里你只需要把DataSource注册成DataSourceProxy的 Bean或者在配置里开启自动代理Seata 的 SQL 解析器就能在每次执行 SQL 时介入。这里有个隐藏问题如果你的项目里有多个数据源或者用了 ShardingSphere、MyBatis-Plus 这种自带数据源管理的组件代理的顺序和方式就特别容易出岔子。常见的表现是本地事务一切正常一加上 Seata 之后SQL 执行报“无法识别 xa”或者找不到 undo_log。这通常不是 Seata 的 bug而是代理链没接对、或者业务表没建 undo_log 表。注意AT 模式的每张业务表都必须建对应的undo_log表建表语句在 Seata 官方仓库的script/client/at/db目录下。这个表一定要和业务表放在同一个库里因为 AT 模式的回滚依赖本地事务保证 undo_log 和业务数据的原子性。4.3 事务分组与配置中心的联动Seata 客户端有一个概念叫“事务分组”每个微服务可以配置自己属于哪个分组然后通过配置中心获取该分组对应的 TC 地址。这套设计是为了做隔离和灰度不同的业务线可以走不同的 TC 集群互不影响。实际配置里最常用的是 Nacos 作为配置中心。你需要在 Nacos 里维护一个service.vgroupMapping.你的分组名的配置值为 TC 集群的名字再配置集群的地址列表。很多初次接入的同学会在这一步绕晕配置文件里写的明明是seata.tx-service-groupmy_test_tx_group结果启动直接报no available server大概率就是vgroupMapping没注册或者注册的值和 TC 服务端的registry.conf里的集群名对不上。我个人的习惯是给每个微服务起独立的事务分组名比如order-tx-group、inventory-tx-group然后在 Nacos 里统一维护这些分组的映射关系。这样排查问题的时候能从日志里一眼看出是哪个服务在跟哪个 TC 集群通信。5. 生产环境常见问题排查手册5.1 全局事务一直处于“Begin”不结束现象TC 的global_table里某条记录始终是Begin状态对应接口的事务一直没有提交或回滚。排查思路第一步看业务代码是否所有分支都正常执行完了第二步看 TM 是否发出 commit 请求如果 TM 进程卡死或者网络超时TC 永远等不到终态。这种情况我遇到过一次原因是业务代码里在线程池里提交了一个子任务子任务里也有数据库操作但子任务的上下文没有传递 XID导致 RM 注册失败而主线程还在等子任务返回整个事务就 hang 住了。记住一条铁律Seata 的全局事务分支必须是同一线程内执行的线程池里的异步操作默认不会自动传递事务上下文需要手动绑定。5.2 回滚失败undo_log 里全是“dirty data”现象全局事务回滚时部分分支回滚成功部分分支报dirty data回滚被跳过。原因AT 模式回滚时会校验 after image 和当前数据是否一致如果不一致说明这段时间里事务又被别的事务改过了为了防止覆盖他人数据Seata 拒绝回滚并把这条数据标记为脏数据。这个机制非常保守所以要注意AT 模式全局事务里操作的数据行一定别再被其他非 Seata 事务的服务直接修改否则回滚冲突是必然的。提示如果确实需要让 Seata 强制回滚脏数据可以手动把 undo_log 里的rollback_info清除把status改成 1但这是非常危险的操作一定要在全面确认数据安全的前提下做最好先备份。5.3 Seata 导致接口变慢如何定位瓶颈接入 Seata 之后接口变慢最优先怀疑三个位置RM 分支注册的耗时。每个分支事务都要向 TC 注册一次如果服务端负载高这一环会显著变慢。解决思路给 TC 扩容或者把 TC 部署到离业务服务更近的机房。全局锁等待耗时。高并发情况下AT 模式的全局锁竞争会导致 SQL 执行前长时间阻塞。这里建议拆细事务减少单事务锁定的行数或者考虑热点场景换 TCC。undo_log 写入的开销。每一条业务 SQL 都要额外插一条 undo_log数据量翻倍是真实的开销。如果业务表本身就很大建议定期清理 undo_log 表并给branch_id和xid建索引否则回滚查询会越来越慢。6. 接入半年后的几条经验总结Seata 用久了有个很深的体会分布式事务方案没有银弹Seata 的价值在于把四种模式统一到了一个框架里让你不用每换一种方案就换一套中间件。AT 模式确实省心但它的“省心”是建立在数据库资源开销和全局锁之上的适合事务链路短、并发要求不极端的场景TCC 模式性能上限更高但你必须认认真真把补偿逻辑设计好这是纯粹的业务功SAGA 适合长链路但是你得想清楚每个补偿节点的细节。最后想提一件事Seata 的事务边界是跟着 XID 传播走的如果你们团队有自研的 RPC 框架一定要确认好 XID 能不能在自定义协议里透传。我自己就踩过一次网关层用自研框架转发请求XID 到了一半就丢了下游的 RM 全部无法注册全局事务直接失效但又不会报错数据就悄悄不对了。这种“静默失败”才是最可怕的。排查了整整一个下午最后是看日志发现 XID 为空才定位到问题。所以接 Seata 之前先把上下文传播链路捋一遍比什么都重要。
返回列表