Skip to content

第7章 分布式事务处理——基于Seata

随着业务的快速发展,以及业务复杂度越来越高,传统的单体应用逐渐暴出一些问题——开发效率低、可维护性差、架构扩展性差、部署不灵活、健壮性差等。

微服务架构将单个服务拆分成一系列小服务,且这些小服务都拥有独立的进程,彼此独立,能很好地解决传统的单体应用的问题。但是服务被拆分后,需要开发人员来解决“确保在分布式环境下服务之间的数据一致性”这个技术难点。Seata 就是为了解决这个技术难点而生的。

🚀 7.1 认识分布式事务

🚀 7.1.1 什么是分布式事务

🚀 1.事务的概念

事务是由多个计算任务构成的一个具有明确边界的工作集合。它包含以下两个目的:

  • 为数据库操作提供从“失败态”恢复到“正常态”的方法,提供在数据库出现异常时确保数据一致性的方法。
  • 当多个应用并发访问数据库时,可以隔离应用,确保应用能够安全地访问数据库。

事务具备如下 4个特性:

  • 原子性(Atomicity):事务作为一个整体被执行,对数据库的操作要么都被执行,要么都不被执行。
  • 一致性(Consistency):事务应确保数据库的状态从一个“一致状态”转变为另一个“一致状态”。“一致状态”是指数据库中的数据满足完整性约束。
  • 隔离性(lsolation):在多个事务并发执行时,一个事务的执行不能影响其他事务的执行。
  • 持久性(Durability):一旦某个事务被提交,它对数据库的修改应该被永久保存在数据中。

🚀 2.分布式事务的概念

分布式事务是指:事务的参与者、支持事务的服务器、资源服务器及事务管理器分别位于分布式系统的不同的实例上。

简单说,一个大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上且属于不同的应用。分布式事务需要保证这些小操作要么全部执行成功,要么全部执行失败

从本质上来说,分布式事务就是为了确保不同应用间的数据一致性,包括 RPC 的数据一致性和数据存储的数据一致性

如图 7-1 所示,将实例 A 中的本地事务拆分为 3个部分:实例 B中的事务1、实例C中的事务2和实例D中的事务3。事务之间的关系如下:

本地事务 = 事务1 + 事务2 + 事务3

说明:事务1、事务2和事务3利用RPC通信来传输事务数据并协调彼此的执行

数据一致性可以分为如下3类:

  • 强一致性:在数据更新成功后,任意时刻所有副本中的数据都是一致的
  • 弱一致性:在数据更新成功后,系统不承诺立即可以读到最新写入的数据,也不承诺具体多久后可以读取最新写入的数据。

在分布式环境中,弱一致性具有以下两个缺点:

  • 如果更新数据时出现异常,则弱一致性不能确保数据最终能够被更新到数据副本中
  • 如果主副本更新成功并完成了数据的持久化,但是在主副本同步数据到从副本的过程中出现了数据同步异常,则弱一致性也不能确保主/从副本之间的数据一致性。
  • 最终一致性:弱一致性的一种形式。在数据更新成功后,系统不承诺可以实时地返回最新写入的数据,但是可以保证一定会返回上一次更新操作之后的最新数据,只是会有一些时延。

如图 7-2 所示,实例A、实例B及实例C同时对数据库进行读和写的操作,实例A执行 SQL语句“insert into datasource0.t_order value(7878,1234,测试订单”);”,并插入一条数据,强一致性、弱一致性及最终一致性的表现如下:

(1) 如果实例之间的数据是强一致性的,则实例B和实例C能实时地读取新插入的数据,并且实例A、实例B和实例C观察到的数据状态是一样的。

(2) 如果3个实例之间的数据是弱一致性的,则实例B和实例C不一定能够读取新插入的数据,并且实例 A、实例B和实例C观察到的数据的状态可能是不一致的。

(3) 如果3个实例之间的数据是最终一致性的,则实例B和实例C一定能够读取新插入的数据。在新插入的数据没有被同步到实例B和C之前,数据是不一致的;但是最终一致性会确保在一定时间之内,将新插入的数据从实例A对应的数据库同步到实例B和实例C对应的数据库。

🚀 7.1.2 为什么需要分布式事务

从系统整体的架构角度来看,分布式事务涉及的场景分为两类:

  • 事务只涉及一个应用,但涉及多个数据存储
  • 事务涉及多个应用,且每个应用可能连接着一个或者多个数据存储

图 7-3 所示的是电商领域最常见的两个服务:订单服务和商品服务。

假设订单服务和商品服务没有被拆分,而是强耦合的,若将商品库和订单库进行水平拆分,则订单服务和商品服务将访问不同的实例数据库。如果在商品服务扣除商品库存成功后订单服务出现了故障,导致订单状态更新失败,若没有分布式事务,则商品库存和订单库存相关表数据就会不一致。假如,在订单服务出现故障时服务的流量非常大,比如 TPS 为10,0000,则会影响用户10,0000 次的正常购买商品。

服务之间的依赖关系越强,则故障造成的教据不一致性的影响越大

如果将订单服务和商品服务进行水平拆分,则此时既要确保数据库层的数据一致性,还要确保 RPC 层的数据一致性。比如,在更新完商品库存后,通过 RPC 调用(比如Dubbo)订单服务去更新订单状态,则存在以下两种情况:

  • 如果在调用应用层的 RPC 接口时失败,则调用方可以通过重试或者补偿来确保业务功能的完整性
  • 如果在 RPC 接口更新订单库失败时已经成功调用 RPC 接口,但是数据库操作失败,则此时需要关联的 RPC 接口和数据库操作在一个全局事务里,并且具备分布式的特性

由上可以看到,分布式事务可以解决RPC层数据库层的分布式数据一致性问题

🚀 7.2 认识Seata

Seata是一款开源的分布式事务解决方案,社区活跃度极高,它致力于在微服务架构中提供高性能和简单易用的分布式事务服务。Seata 为开发者提供了 ATTCCSagaXA 共4种事务模式。

🚀 7.2.1 Seata的基础概念

Seata 的基础概念主要包括:基础角色、事务分组、注册中心及配置中心。

1.基础角色

Seata 的基础角色包括以下3个:

  • TC(Transaction Coordinator):事务协调者,它维护全局和分支事务的状态,驱动全局事务提交或回滚
  • TM(Transaction Manager):事务管理器,它定义了全局事务的范围,主要包括开始全局事务、提交全局事务和回滚全局事务。
  • RM(Resource Manager):资源管理器,它管理分支事务处理的资源,与TC通信并注册分支事务和报告分支事务的状态,驱动分支事务的提交和回滚。

三者的关系如图 7-4 所示。

(1) TC 单独部署,TM 和 RM 与应用强绑定,共用一个虚拟机并一起部署。

(2) 在 TM 和 RM 与 TC 之间用 RPC 完成资源数据的交换。

2.事务分组

事务分组是 Seata 的资源逻辑,类似于服务实例。

3.注册中心

Seata 可以将部署事务协调者的机器实例注册到注册中心,事务管理器和资源管理器可以从注册中心获取部署事务协调者的机器实例的集群列表。Seata 支持的注册中心包括:Nacos、Sofa、Etcd、Eureka、Consul等。

4.配置中心

Seata的服务器端(TC)和客户端(TM和RM)都支持分布式配置中心,开发人员可以将配置信息动态地存储在配置中心中。Seata 支持的配置中心包括:Nacos、Apollo、Etcd、ZooKeeper等。

🚀 7.2.2 Seata的事务模式

在 Seata 定义的全局事务基础框架中,不同角色的功能是不一样的。在全局事务基础框架中,Seata 处理分布式事务的整体流程如图 7-5 所示:

  • (1) TM 向 TC 发起请求,包括开启(Begin)、提交(Commit)和回滚(Rolback)事务。
  • (2) TM 把代表全局事务的 XID 绑定到分支事务。
  • (3) RM 向TC 发起请求,并注册分事务,把分支事务关联到 XID 所代表的全局事务。
  • (4) RM 把分支事务的执行结果上报给 TC。
  • (5) TC 发送分支事务提交(Branch Commit)或分支事务回滚(Branch Rollback)命令给RM。

在 Seata 中,全局事务的处理过程分为如下两个阶段

  • (1) 执行阶段:Seata 会执行与分支事务相关的逻辑,并保证执行结果是可以回滚和持久化的。
  • (2) 完成阶段:应用会根据全局事务中所有的分支事务在执行阶段的结果,形成全局事务的决议,并通过 TM 向 TC 发起提交或者回滚全局事务的请求;之后,TC 向 RM 发起请求,提交或者回滚分支事务。

在 Seata 中,事务模式是指运行在 Seata 全局事务框架下的“分支事务的行为模式”(准确来讲应该是“分支事务模式”)。

不同的事务模式会使用不同的方式去完成全局事务的两个阶段,从而形成了Seata的4种事务模式:AT 模式、XA 模式、TCC 模式及 Saga 模式。

🚀 1. AT 模式

AT 模式是 Seata 主推的分布式事务解决方案,它最早来源于阿里中间件团队发布的 TXC 服务,“上云”后改名为 GTS。AT模式屏蔽了底层 JDBC 数据层的细节,让应用能够无感知地使用分布式事务,自动代理应用数据源,并进行事务相关的操作,图 7-6 是 Seata 的 AT 模式的架构示意图。在 AT 模式中,Seata 完成全局事务的两个阶段的过程如下。

(1) 第一阶段:应用只需要关注自己的“业务 SQL代码”,Seata 将应用的“业务 SQL 代码的执行”作为第一阶段。Seata 框架会自动代理应用的数据源,并生成事务第二阶段的提交和回滚事务操作,记录在 UNDO LOG 日志表中

(2) 第二阶段:如果 TC 事务协调器通知分支事务处理成功,则 Seata 会提交分支事务(在Seata的 AT 模式中,提交分支事务就是“删除 UNDO LOG 日志表中的事务相关日志”);如果TC事务协调器通知分支事务处理失败,则 Seata 会回滚分支事务(从 UNDO LOG 日志表中读取事务回滚的日志)。

🚀 2. XA 模式

XA 模式是 Seata 利用事务资源(数据库、消息服务等)来提供对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种事务模式。

图7-7 是 Seata 的 XA 模式的架构示意图。在 XA 模式中,Seata 完成全局事务的两个阶段的过程如下:

(1) 第一阶段,利用 RM 代理应用的数据源,并创建数据库的代理连接。通过开启 XA 模式事务拦截应用的 SQL 语句,执行 XA 模式预处理。为了防止应用宕机,造成数据丢失,第一阶段的 XA 模式操作会被 Seata 持久化。

(2) 第二阶段,TC 通知 RM 执行提交或者回滚 XA 模式的分支事务。

(3) 在XA 模式中,应用在开启 XA 模式事务之后,会注册分支事务;在预处理 XA 模式事务之后,会分析并上传分支事务的状态。

3. TCC 模式

2019年3月,Seata 开源了 TCC 模式,它是一个分布式的全局事务,并且是一个两阶段的分布式事务。在 TCC 模式中,全局事务是由若干分支事务组成的。分支事务要满足两阶段提交模型的要求(即需要每个分支事务都具备该模型),支持把自定义的分支事务纳入全局事务的管理中。

图 7-8 是 Seata 的 TCC 模式的架构示意图。

(1) 在TCC 模式中,用Try()、Confirm()及 Cancel()这3个方法来实现事务的两阶段提交3 个方法的描述如下。

  • Try():在应用中检测和预留系统资源
  • Confirm():在应用中提交执行的业务操作。TCC 模式要求,如果Try()方法执行成功,则
  • Confirm()方法一定要执行成功。
  • Cancel():在应用中释放预留的资源

(2) RM 负责管理第一阶段的 Try 语句,以及第二阶段的 Confirm 和 Cancel 语句。

(3) TM开启事务,在RM 调用TM 后,TM 执行一阶段的Try()方法并注册分支事务,以及分析和上传分支事务的状态。

(4) 如果调用链路已经完成,则 TM 向 TC 发起第二阶段的分支事务决议的请求;如果所有分支事务的决议都是通过的,则 TC 驱动 RM 去执行第二阶段的事务 Confirm 或者 Cancel 语句。

(5) 应用需要在代码中用Try()、Confirm()及 Cancel()方法实现分布式事务。即针对不同的代码,应用都需要重新实现分布式事务处理的业务逻辑。

4. Saga 模式

在 Saga 模式中,业务流程中的每个参与者都可以提交本地事务。如果某个参与者失败,则补偿本地事务提交成功的参与者,第一阶段正向服务和第二阶段补偿服务都由开发人员来实现。

Saga 的核心就是补偿:第一阶段就是服务的正常顺序调用(数据库事务正常提交),如果都执行成功,则第二阶段什么都不做;但如果在第一阶段中有执行发生异常,则在第二阶段中会依次调用其补偿服务(以保证整个交易的一致性)。

图7-9是 Saga 模式的架构示意图。

(1) 在开启状态机后,会注册分支事务;在关闭状态机后,会分析并上传分支事务的状态。

(2) 在TC 通知 RM 去提交分支事务时,会转发状态机;在 TC 通知 RM 回滚分支事务时,会重新加载状态机。

(3) 在运行状态机之后,将产生的数据存储在数据库中。

(4) 在 Saga 模式中,主要用状态机来编排分布式事务中应用执行的顺序,以及事务执行的阶段。

(5) 在Saga 模式中,分布式事务通常是由事件驱动的,各个参与者之间是异步执行的。Saga模式是一种长事务解决方案。

在 Saga 模式中,分布式事务具备如下优势:

  • ①在第一阶段提交本地数据库事务时,采用的是无锁设计,所以应用具备高性能;
  • ②参与者可以采用事务驱动来异步执行,所以应用具备高吞吐量;
  • ③补偿服务即正向服务的“反向”,所以应用非常容易实现,具备易用性。

5.对比四种事务模式

AT、XA、TCC、Saga 四种事务模式的对比见表 7-1。

7.3 将应用接入Seata

7.3.1 搭建Seata Server的高可用环境

7.3.2 【实例】使用seata-spring-boot-starter将应用接入Seata

7.3.3 【实例】使用Spring Cloud Alibaba 将应用接入Seata

7.4 用Netty实现客户端与服务器端之间的通信渠道

7.4.1 “用Netty实现通信渠道的服务器端”的原理

7.4.2 “用Netty实现通信渠道的客户端”的原理

7.5 用拦截器和过滤器适配主流的RPC框架

7.5.1 “用过滤器适配Dubbo”的原理

7.5.2 “用拦截器适配gRPC”的原理

7.6 用AT模式实现分布式事务

7.6.1 “用数据源代理实现AT模式的零侵入应用”的原理

7.6.2 “用全局锁实现AT模式第二阶段的写隔离”的原理

7.6.3 【实例】搭建Seata的AT模式的环境,并验证AT模式的分布式事务场景

7.7 用TCC模式实现分布式事务

7.7.1 用GlobalTransactionScanner类扫描客户端,开启TCC动态代理

7.7.2 用拦截器TccActionInterceptor校验TCC事务

7.7.3 【实例】搭建Seata的TCC模式的环境,并验证TCC模式的分布式事务场景

7.8 用XA模式实现分布式事务

7.8.1 “用数据源代理实现XA模式的零侵入应用”的原理

7.8.2 用XACore类处理XA模式的事务请求

7.8.3 【实例】搭建Seata的XA模式的客户端运行环境,并验证XA模式的分布式事务回滚的效果

7.9 用Saga模式实现分布式事务

7.9.1 “用状态机实现Saga模式”的原理

7.9.2 【实例】搭建Seata的Saga模式的客户端运行环境,并验证Saga模式的分布式事务场景