seata 是什么

seata 是阿里开源的,阿里是国内最早一批进行应用分布式(微服务化)改造的企业,所以很早就遇到微服务架构下的分布式事务问题。阿里对于分布式事务的解决方案历程如下:

  • 2014 年,阿里中间件团队发布 TXC(Taobao Transaction Constructor) ,为集团内应用提供分布式事务服务。

  • 2016 年,TXC 经过产品化改造,以 GTS(Global Transaction Service) 的身份登陆阿里云,成为当时业界唯一一款云上分布式事务产品,在阿云里的公有云、专有云解决方案中,开始服务于众多外部客户。

  • 2019 年起,基于 TXC 和 GTS 的技术积累,阿里中间件团队发起了开源项目 Fescar(Fast & EaSy Commit And Rollback, FESCAR) ,和社区一起建设这个分布式事务解决方案。

  • 2019 – fescar被重命名为了 seata (simple extensiable autonomous transaction architecture)。

因此,TXC/GTS/Fescar/seata 一脉相承,为解决微服务架构下的分布式事务问题交出了一份与众不同的答卷。

那什么是分布式事务呢?

如果是单体应用,那么针对业务流程数据的处理完全可以由数据库的 事务CAID特性来保证数据完整性。但是分布式应用或者说微服务中,一个业务流程会涉及到多个不同服务,此时,对于其中某个服务来说可以通过本地事务保证数据一致性,但是业务层面的整体数据一致性该如何保证呢? 这就是微服务架构下面临的,典型的分布式事务需求:我们需要一个分布式事务的解决方案保障业务全局的数据一致性

对分布式事务的要求

高性能:业务场景使用分布式事务支持,必将会影响到业务执行性能,因此对分布式事务框架的一个要求就是高性能,不能因为引入分布式事务支持导致业务性能明显下降。高性能就要求分布式事务框架有:高性能的网络通信能力、高性能的数据存取能力等。业务场景由于引入分布式事务,那么分布式事务也成为了一个强依赖,因此对于其 高可用 也有要求。

侵入性:分布式事务支持大体上有 业务侵入非侵入 两种类型,这里的 侵入 是指,因为分布式事务这个技术问题的制约,要求应用在业务层面进行设计和改造。这种设计和改造往往会给应用带来很高的研发和维护成本。针对开发人员来说,对于业务侵入会造成较大的阻力,因此最好是在不影响分布式事务能力的情况下提供非侵入能力。

业务无侵入的分布式事务方案有 XA ,有侵入的有 基于可靠消息的最终一致性方案、TCC、Saga 等。目前的 XA 分布式事务方案,支持的数据库类型不多,事务资源锁定周期较长,由于是数据库层面实现的分布式事务能力,因此对于其中的某些可优化环节应用层无能为力,因此基于 XA 的解决方案性能往往不理想并且不容易优化。目前已落地的基于 XA 的分布式解决方案,都依托于重量级的应用服务器(Tuxedo/WebLogic/WebSphere 等),这是不适用于微服务架构的。

seata 是什么样的分布式事务

不可否认,侵入业务的分布式事务方案都经过大量实践验证,能有效解决问题,在各行各业的业务应用系统中起着重要作用。但回到原点来思考,这些方案的采用实际上都是 迫于无奈 。设想,如果基于 XA 的方案能够不那么 ,并且能保证业务的性能需求,相信不会有人愿意把分布式事务问题拿到业务层面来解决。

一个理想的分布式事务解决方案应该:像使用 本地事务 一样简单,业务逻辑只关注业务层面的需求,不需要考虑事务机制上的约束。

分布式事务该如何定义?

一个分布式事务理解成一个包含了若干 分支事务全局事务 。全局事务 的职责是协调其下管辖的 分支事务 达成一致,要么一起成功提交,要么一起失败回滚。此外,通常 分支事务 本身就是一个满足 ACID 的 本地事务。这是对分布式事务结构的基本认识,与 XA 是一致的。

seata定义 3 个组件来协调分布式事务的处理过程,

  • Transaction Coordinator (TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。

  • Transaction Manager (TM):控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。

  • Resource Manager (RM):控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。

注意:TC 是一个独立的 server 服务,是分布式事务核心的协调服务,TM 和 RM 相当于是 client 端。

一个典型的分布式事务过程:

  1. 当需要发起一个分布式事务流程时,TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID。

  2. XID 在微服务调用链路的上下文中传播(这里需要适配 dubbo/spring cloud 分布式框架)。

  3. 当进行数据操作时,RM首先进行本地事务操作,但是先不 commit,然后向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖。

  4. RM提交本地事务,上报本地事务状态给 TC。

  5. TM 向 TC 发起针对 XID 的全局提交或回滚决议。

  6. TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。

至此,Fescar 的协议机制总体上看与 XA 是一致的。

与XA差别

二者架构上差别如下:

XA 方案的 RM 实际上是在数据库层,RM 本质上就是数据库自身(通过提供支持 XA 的驱动程序来供应用使用)。

而 seata 的 RM 是以二方包的形式作为中间件层部署在应用程序这一侧的,不依赖与数据库本身对协议的支持,当然也不需要数据库支持 XA 协议。这点对于微服务化的架构来说是非常重要的:应用层不需要为本地事务和分布式事务两类不同场景来适配两套不同的数据库驱动。这个设计,剥离了分布式事务方案对数据库在 协议支持 上的要求。

seata事务传播机制

XID 是一个全局事务的唯一标识,事务传播机制要做的就是把 XID 在服务调用链路中传递下去,并绑定到服务的事务上下文中,这样,服务链路中的数据库更新操作,就都会向该 XID 代表的全局事务注册分支,纳入同一个全局事务的管辖。

基于这个机制,Fescar 是可以支持任何微服务 RPC 框架的。只要在特定框架中找到可以透明传播 XID 的机制即可,比如,Dubbo 的 Filter + RpcContext。

seata使用注意事项

远程调用事务上下文XID的的传播

XID为全局事务ID,在事务的服务调用流程中需要传递,目前已对dubbo/spring cloud/sofa等服务调用做了适配,XID的传递就是通过服务调用流程中的机制来携带过期,然后在服务侧提取出来设置到本地上下文中,逻辑如下:

远程调用前获取当前 XID:
String xid = RootContext.getXID();

远程调用过程把 XID 也传递到服务提供方,在执行服务提供方的业务逻辑前,把 XID 绑定到当前应用的运行时:
RootContext.bind(rpcXid);

事务的暂停和恢复

在一个全局事务中,如果需要某些业务逻辑不在全局事务的管辖范围内,则在调用前,调用unbind把 XID 解绑。待相关业务逻辑执行完成,调用bind再把 XID 绑定回去,即可实现全局事务的恢复。

String unbindXid = RootContext.unbind();
RootContext.bind(unbindXid);

seata事务与非seata事务

seata事务涉及到全局事务/全局锁和分支事务/本地锁,因为seata的本地事务执行完成就是MySQL事务commit了,此时如果该数据库还有seata事务更新相同数据那么会因为获取不到全局锁而无法更新,如果此时是非seata事务更新相同数据,那么是有问题的,这点要注意,seata在二阶段回滚时会进行数据校验,校验流程如下:通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录,拿 UNDO LOG 中的后镜与当前数据进行比较,如果有不同,说明数据被当前全局事务之外的动作做了修改。这种情况,需要根据配置策略来做处理。