【得物技术】浅谈分布式事务中间件 Seata

简介
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。
Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
Part 1
角色
Seata 的设计思路是将一个分布式事务可以理解成一个全局事务,下面挂了若干个分支事务,而一个分支事务是一个满足 ACID 的本地事务,因此我们可以操作分布式事务像操作本地事务一样。

01
三个组件
Seata 内部定义了 3 个模块来处理全局事务和分支事务的关系和处理过程,这三个组件分别是:
Transaction Coordinator (TC): 事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。
Transaction Manager (TM): 控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。
Resource Manager (RM): 控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。
02
执行步骤
简要说说整个全局事务的执行步骤:
TM 向 TC 申请开启一个全局事务,TC 创建全局事务后返回全局唯一的 XID,XID 会在全局事务的上下文中传播;
RM 向 TC 注册分支事务,该分支事务归属于拥有相同 XID 的全局事务;
TM 向 TC 发起全局提交或回滚;
TC 调度 XID 下的分支事务完成提交或者回滚。
Part 2
支持模式
01
Seata AT 模式
两阶段提交协议的演变:
一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
二阶段:
提交异步化,非常快速地完成。
回滚通过一阶段的回滚日志进行反向补偿。
02
Seata TCC 模式
整体是两阶段提交的模型。
全局事务是由若干分支事务组成的,分支事务要满足 两阶段提交 的模型要求,即需要每个分支事务都具备自己的:
一阶段 prepare 行为
二阶段 commit 或 rollback 行为
03
Seata Saga 模式
目前 SEATA 提供的 Saga 模式是基于状态机引擎来实现的,机制是:
通过状态图来定义服务调用的流程并生成 json 状态语言定义文件。
状态图中一个节点可以是调用一个服务,节点可以配置它的补偿节点。
状态图 json 由状态机引擎驱动执行,当出现异常时状态引擎反向执行已成功节点对应的补偿节点将事务回滚。
可以实现服务编排需求,支持单项选择、并发、子流程、参数转换、参数映射、服务执行状态判断、异常捕获等功能。

04
Seata XA 模式
利用事务资源(数据库、消息服务等)对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种 事务模式。
执行阶段:
可回滚:业务 SQL 操作放在 XA 分支中进行,由资源对 XA 协议的支持来保证可回滚。
持久化:XA 分支完成后,执行 XA prepare,同样,由资源对 XA 协议的支持来保证持久化(即,之后任何意外都不会造成无法回滚的情况)。
完成阶段:
分支提交:执行 XA 分支的 commit
分支回滚:执行 XA 分支的 rollback
Part 3
源码分享
01
代码整体结构

seata-common
提供一些工具类,异常等。
seata-core
对 rpc,消息格式,序列化扩展点,eventbus 等做了些封装。

seata-config
配置中心接入,对 naocs,apollo,etcd,zk,file 等做了封装。

seata-discovery
对负载均衡,服务注册发现做了封装,接入 nacos,zk,etcd,eureka 等。

seata-server
coordinator 处理事务协调逻辑
lock 全局锁处理逻辑
store 存储分布式锁

seata-rm
rm 的实现核心逻辑封装。

seata-tm
tm 的核心实现逻辑封装。

seata-tcc
tcc 事务模式扩展。

seata-spring
seata spring 支持,比如 @GlobalLock,@GlobalTransactional 就是在这里支持。

对各种 rpc 支持,比如 xid 透传就在里面实现的。

02
RPC

AbstractNettyRemoting:Remoting 类的最顶层抽象,包含了服务端和客户端公用的成员变量与公用方法
RemotingClient:客户端最顶级接口
RemotingServer:服务端最顶级接口
AbstractNettyRemotingClient:客户端抽象类,继承 AbstractNettyRemoting 类并实现了 RemotingClient 接口
NettyRemotingServer:服务端实现类,继承 AbstractNettyRemoting 类并实现了 RemotingServer 接口
RmNettyRemotingClient:Rm 客户端实现类,继承 AbstractNettyRemotingClient 类
TmNettyRemotingClient:Tm 客户端实现类,继承 AbstractNettyRemotingClient 类

服务端和客户端启动引导类

*seata server 与 tm,rm 交互图
针对以上交互图,
seata AbstractNettyRemoting 有一个 processMessage,
里面定义了 processorTable。对于不同请求,采用不同的 RemotingProcessor 去处理请求逻辑,成功的将业务逻辑从 netty Netty ChannelHadler 解耦出来。

processor 主要有以下这些。

03
AT 流程
入口 GlobalTransactionScanner,主要完成 TMClient, RMClient 与 Seata Server 建立连接。

第一阶段

核心是对业务 sql 进行解析,转换成 undolog,并同时入库。
GlobalTransactionalInterceptor 会对 @GlobalTransactional 注解进行拦截。

里面会去调用 TransactionalTemplate#execute,进行具体逻辑处理。

主要是有以下几步:
开启全局事务 beginTransaction(txInfo, tx)
执行具体业务逻辑 business.execute()
提交事务 commitTransaction(tx)
如果有异常则进行回滚操作 completeTransactionAfterThrowing(txInfo, tx, ex)
StatementProxy 解析 sql,生成 undolog,执行业务流程,分支事务注册到 seata server 整个流程图。

executeAutoCommitTrue 中先将 autoCommit 设置为 false,原因是要对 sql 进行解析,生成 undolog 在一个事务中入库。

ConnectionProxy#doCommit
如果处于全局事务中,则调用 processGlobalTransactionCommit()处理全局事务提交。
如果加了全局锁 @GlobalLock 注解,processLocalCommitWithGlobalLocks()加全局锁并提交。
如果没有任何注释,按直接进行事务提交。
第二阶段
分布式事务操作成功,则 TC 通知 RM 异步删除 undolog。

RM 具体处理回调逻辑在 RMHandlerAT。

getResourceManager()会用到 DataSourceManager,这个是利用 SPI 机制扩展的。

里面是利用 AsyncWorker 异步执行。

处理逻辑主要分以下几步骤:
先按 resourceId(数据连接)对提交操作进行分组,同个数据库的可以一起操作,提升效率。
根据 resourceId 找到对应 DataSourceProxy,并获取数据库连接 getPlainConnection()。
调用 UndoLogManagerFactory.getUndoLogManager(dataSourceProxy.getDbType()).batchDeleteUndoLog(xids,branchIds, conn)删除 undolog。
分布式事务操作失败,TM 向 TC 发送回滚请求,RM 收到协调器 TC 发来的回滚请求,通过 XID 和 Branch ID 找到相应的回滚日志记录,通过回滚记录生成反向的更新 SQL 并执行,以完成分支的回滚。

具体的实现逻辑在 AbstractUndoLogManager#undo 中可以看到。

处理逻辑主要分以下几步骤:
根据 xid,branchId 找到具体的 undolog
从 undolog beforeImg 解析回滚 sql 执行
删除 undolog,提交
04
其他
TCC 模块扩展了 @TwoPhaseBusinessAction,RMHandlerTCC 等几个点
XA 模块扩展了 DataSourceProxyXA,StatementProxyXA,ConnectionProxyXA 等。
Part 4
优缺点及总结
对于强一致性要求高的场景使用会比较方便。
AT 模式会利用 lock_table 锁住具体行,如果分支事务较多,某个分支事务执行较慢,会影响整体性能。
TCC 模式相对 AT 模式会可控些,因为 AT 模式回滚镜像是由代码自动生成,也不会有 AT 模式下锁住某一行的问题,但是需要整个链路上下游都进行相应改造,相对改造成本会较大。
XA 模式
对 Seata Server 可用性要求很高,一次分布式事务 RM,TM 和 TC 会有多次交互,对服务稳定性要求也比较高,1.3 版本后支持了自动降级,但是降级后,强一致性又保证不了了。
详细的使用例子可参考:https://github.com/seata/seata-samples
【END】
文|Leo
版权声明: 本文为 InfoQ 作者【得物技术】的原创文章。
原文链接:【http://xie.infoq.cn/article/179d5fc030c44c8ac99fe30be】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论