SEATA从Server到Golang Client全链路走读详解
摘要
SEATA通过TC协调全局事务,Golang客户端实现AT、TCC、XA、Saga模式。AT模式使用UndoLog和全局锁保
SEATA在解决什么问题?
想象一下,当微服务架构落地之后,一个看似简单的下单操作,背后却要跨多个服务、多个数据库。扣库存、扣余额、创建订单——这些动作分散在不同的服务里,任何一个环节失败,前面的操作都得撤回。这正是分布式事务要解决的核心难题。
SEATA 是怎么搞定这件事的?其实思路很清晰:
- TC(事务协调器):也就是 Seata Server,目前用 Ja va 实现(Seata Go 是它的 Golang 客户端)。它的职责是维护全局事务和分支事务的状态,最后决定是提交还是回滚。
- TM(事务管理器):事务的发起方。在 Go 生态里,就是调用
tm.WithGlobalTx()的那个服务。 - RM(资源管理器):每个参与分布式事务的服务里都有这个角色,负责管理本地事务、向 TC 注册分支,并执行 TC 下发的 Commit/Rollback 指令。
Golang Client 启动
如果你翻过 incubator-seata-go-samples 这个仓库,会发现每个 sample 的第一行几乎都是 client.InitPath("../../conf/seatago.yml")。这行代码背后藏着不少事情。
详细拆解几个关键节点:
- 服务发现:Go Client 得先知道 TC Server 在哪,服务发现必须先行。
- RM 初始化:这部分最重——得建立到 TC 的 TCP 长连接(基于 dubbo-getty),注册消息处理器来接收 TC 的回调指令(Commit/Rollback),还要初始化四种事务模式各自的资源管理器。
- 数据源袋里:这是 AT 和 XA 模式的基础,通过 Golang 的
database/sql/driver接口注册自定义 Driver,所有 SQL 语句都会经过 Seata 的袋里层。
WithGlobalTx 全流程
这应该算是 Seata Go 最重要的 API,贯穿一个全局事务的生命周期。
事务传播机制
WithGlobalTx 支持类似 Spring 的事务传播语义。当一个全局事务内部又调用了 WithGlobalTx 时,其传播级别决定了行为:
| 传播级别 | 已有事务 | 无事务 |
|---|---|---|
| Required(Default) | 加入当前事务 | 新建事务 |
| RequiresNew | 挂起当前,新建事务 | 新建事务 |
| NotSupported | 挂起当前,无事务执行 | 无事务执行 |
| Supports | 加入当前事务 | 无事务执行 |
| Never | 抛异常 | 无事务执行 |
| Mandatory | 加入当前事务 | 抛异常 |
角色区分:Launcher & Participant
在 Seata 中,发起全局事务的服务叫 Launcher,被调用的下游服务叫 Participant。只有 Launcher 有权提交或回滚全局事务,Participant 只负责执行本地分支。这个区分在 commitOrRollback 中语义非常清晰——Participant 直接跳过二阶段操作。
XID 传播
XID 是全局事务的唯一标识,格式类似 192.168.1.1:8091:123456789。它必须贯穿整个调用链,否则下游事务就不知道自己是哪个全局事务的一部分。
Seata Go 通过中间件或拦截器来实现 XID 的透明传播。业务代码完全不用操心 XID 怎么传——只要引入对应框架的中间件,XID 就会自动跟请求走。Gin 走 HTTP Header,gRPC 用 Metadata,Dubbo 用 Attachment,最终都汇入 Seata Context。
AT 模式
AT 模式是 Seata 用得最多的模式,业务侵入也最小。核心思想很简单:在执行 SQL 前后自动记录数据快照(UndoLog),回滚时用快照恢复。
先说 SQL 袋里层。通过实现 Go 标准库的 database/sql/driver 接口,注册自定义的 Driver。应用使用 sql.Open("seata-at", dsn) 时,所有 SQL 都会经过 ATConn 袋里。袋里层根据 SQL 类型(INSERT/UPDATE/DELETE/SELECT FOR UPDATE)选择不同的 Executor。
接着是 UndoLog 结构。每条 undo log 都包含 beforeImage 和 afterImage。前者是执行前快照,后者是执行后。回滚时用 beforeImage 生成反向 SQL——UPDATE 变回原值,INSERT 变 DELETE,DELETE 变 INSERT。
再看全局锁。AT 模式在注册分支时,会向 TC 申请行锁(lock keys),防止多个全局事务同时修改同一行数据。这是 AT 模式保证隔离性的关键。
最后是异步提交。二阶段提交时,AT 模式只需要删除 undo log,不涉及业务操作,所以 TC 可以异步批量处理,效率很高。
TCC 模式
TCC 模式把事务控制权完全还给业务。开发者需要为每个参与方实现三个方法:Try(预留资源)、Confirm(确认提交)、Cancel(取消回滚)。
使用时,需要实现下面接口方法:
| 方法 | 作用 | 调用时机 |
|---|---|---|
| Prepare(ctx, params) | 预留资源(如冻结库存) | 一阶段,业务主动调用 |
| Commit(ctx, bac) | 确认提交(如扣减冻结库存) | 二阶段,TC 驱动 |
| Rollback(ctx, bac) | 取消回滚(如释放冻结库存) | 二阶段,TC 驱动 |
| GetActionName() | 返回资源标识 | 注册时使用 |
然后通过 tcc.NewTCCServiceProxy(service) 创建袋里,袋里会自动向 TC 注册资源。业务代码只需要在全局事务内调用 proxy.Prepare(),二阶段的 Confirm/Cancel 由 TC 回调触发。
一个经典问题是悬挂问题:如果 Try 请求因为网络延迟还没到达,TC 已经超时触发了 Cancel,之后 Try 请求才到达然后执行。Seata Go 用 Fence 机制来解决,在数据库中维护一张 tcc_fence_log 表,记录每个分支事务的阶段状态,通过本地事务保证幂等性和防止悬挂。
XA 模式
XA 是 DB 层面的分布式事务标准协议,与 AT 模式不同,XA 模式不需要 undo log,依赖 DB 自身的 XA 事务支持。
简单对比 AT 和 XA 模式:
| 维度 | AT 模式 | XA 模式 |
|---|---|---|
| 锁粒度 | 全局锁(Seata 管理) | 数据库锁(DB 管理) |
| 一阶段锁释放 | 本地事务提交后释放 DB 锁 | Prepare 后仍持有 DB 锁 |
| 一致性 | 最终一致 | 强一致 |
| 回滚方式 | undo log 反向补偿 | 数据库原生 XA ROLLBACK |
简单来讲,AT 牺牲了一点一致性来换取高性能,一阶段时就释放 DB 锁。相反,XA 牺牲性能来换取强一致,锁会一直持有到二阶段结束。
Saga 模式
Saga 模式适合长事务场景,通过状态机来编排正向操作和补偿操作。Seata Go 实现了完整的状态机引擎 ProcessCtrlStateMachineEngine。
流程定义用 JSON 格式的 statelang,类似 AWS Step Functions。
| 状态类型 | 作用 |
|---|---|
| ServiceTask | 调用一个服务方法,可配置补偿方法 |
| Choice | 条件分支,根据表达式选择下一步 |
| CompensationTrigger | 触发已执行状态的补偿 |
| Succeed / Fail | 终态 |
| SubStateMachine | 嵌套调用另一个状态机 |
TC Server
前面讲完了 Go Client,下面看看 Seata Server 是怎么处理这些请求的。
DefaultCoordinator 是 TC Server 的入口,处理所有来自 TM/RM 的 RPC 请求。同时管理一组定时任务:
| 定时任务 | 职责 |
|---|---|
| retryRollbacking | 重试回滚中的事务 |
| retryCommitting | 重试提交中的事务 |
| asyncCommitting | 处理异步提交(AT 模式) |
| timeoutCheck | 检测超时事务并触发回滚 |
| undoLogDelete | 清理过期的 undo log |
DefaultCore 是事务处理核心,内部维护了一个 CORE_MAP,按 BranchType 映射到不同的 AbstractCore 实现(ATCore、TCCCore、XACore、SagaCore)。所有分支级别的操作(register、commit、rollback)都委托给对应模式的 Core 处理。
全局事务状态流转
- 异步提交:如果所有分支都支持异步提交(AT 模式的二阶段只需删除 undo log),TC 会直接标记为 AsyncCommitting,由后台定时任务批量处理,吞吐量更高。
- 超时:TC 有专门的定时任务扫描超时事务。一旦发现超时,直接将状态改为 TimeoutRollbacking,触发回滚流程。
- Raft 集群:TC 支持 Raft 模式部署,通过 RaftCoordinator 实现 leader 选举和状态同步,保证 TC 自身的高可用。
RPC 通信
Go Client 和 TC Server 之间的通信基于 TCP 长连接,Go 端使用 dubbo-getty。
通信协议的消息类型覆盖了所有事务操作:
| 消息方向 | 消息类型 | 说明 |
|---|---|---|
| Client → TC | GlobalBeginRequest | TM 开启全局事务 |
| Client → TC | GlobalCommitRequest | TM 提交全局事务 |
| Client → TC | GlobalRollbackRequest | TM 回滚全局事务 |
| Client → TC | BranchRegisterRequest | RM 注册分支事务 |
| Client → TC | BranchReportRequest | RM 报告分支状态 |
| Client → TC | GlobalLockQueryRequest | RM 查询全局锁 |
| Client → TC | RegisterRMRequest | RM 注册资源 |
| TC → Client | BranchCommitRequest | TC 驱动分支提交 |
| TC → Client | BranchRollbackRequest | TC 驱动分支回滚 |
Client 端的消息处理器在初始化时注册:rmBranchCommitProcessor 处理提交回调,rmBranchRollbackProcessor 处理回滚回调。收到 TC 的指令后,处理器会根据 BranchType 找到对应的 ResourceManager,执行实际的 Commit/Rollback,然后将结果通过 Response 返回给 TC。
完整请求链路
最后,通过一个请求的完整处理链路串联上述所有内容:
来源:互联网
本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。