引言
上一篇文章我们介绍了分布式事务和2PC方案,和进一步优化的3PC方案。2PC方案的性能较低,那大部分互联网公司,在遇到分布式事务问题,是如何解决的呢?
前情回顾:分布式事务(一):概念介绍与刚性事务
当下使用Java作为开发语言的公司,解决分布式事务,非常常见的一种方案,是使用Seata开源框架。
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
今天我们就来聊一下Seata的设计原理与工作流程, 并介绍Seata提供AT事务模式的方案与底层原理。话不多说,我们开始吧。
Seata架构
Seata 是一款针对分布式架构下产生的数据一致性问题而诞生的分布式事务产品,使用2pc或基于base理论的最终一致性来达成事务,2pc我们已经了解了,那么base理论又是什么呢?
BASE理论
- Basically Available(基本可用):
- 在分布式系统出现故障的时候,允许损失部分可用性,保证核心功能或者绝大部分功能可用。
- 例如,在电商系统中,当某个非核心服务出现故障时,仍然可以让用户进行商品浏览和下单等主要操作,只是可能一些不太重要的功能(如个性化推荐)暂时不可用。
- Soft state(软状态):
- 允许系统存在中间状态,而该中间状态不会影响系统整体可用性。
- 在分布式事务中,事务的执行过程可能会存在一些临时的状态,这些状态不会影响系统的正常运行。例如,在 SEATA 中,事务在执行过程中可能处于 “准备提交” 的中间状态。
- Eventually consistent(最终一致性):
- 系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。
- 虽然在某个时刻,系统中的数据可能存在不一致的情况,但是随着时间的推移和系统的不断修复,数据最终会达到一致。
举一个电商平台的例子,假设我们有一个电商平台,包括商品服务、订单服务、库存服务和支付服务。用户在平台上进行购物操作,涉及多个服务之间的交互。
用户下单后,订单服务会立即生成订单并处于 “待支付” 状态。此时,库存服务可能会先将用户购买的商品进行预扣减,但库存状态并不是最终确定的状态,处于一种软状态。如果用户在一定时间内未完成支付,库存会自动恢复。
当用户完成支付后,支付服务会通知订单服务将订单状态更新为 “已支付”。同时,订单服务会通知库存服务进行实际的库存扣减,并通知物流服务准备发货。这个过程可能不是瞬间完成的,在一段时间内,各个服务的数据可能不一致。例如,支付服务已经确认支付成功,但库存服务可能还未完成库存扣减,物流服务也可能还未开始准备发货。
但是,随着系统的不断运行和各个服务之间的消息传递与处理,最终各个服务的数据会达到一致的状态。订单状态会正确反映支付和发货情况,库存也会与实际销售情况相符,也就是最终一致性(Eventually consistent)。
那Seata是如何实现的呢?我们得先从它的领域模型说起。
领域模型
我们首先需要连接Seata中最关键的三个概念。
事务协调者(Transaction Coordinator):
负责全局事务的管理和协调,维护全局事务的状态,决定全局事务是提交还是回滚。
事务管理者(Transaction Manager):
定义全局事务的范围,开始、提交或回滚全局事务。通常嵌入在业务应用中,由业务应用负责启动和控制全局事务。
资源管理者(Resource Manager):
管理分支事务,与数据库等资源进行交互,向 TC 注册分支事务、汇报分支事务的状态,并根据 TC 的指令提交或回滚分支事务。
工作流程
Seata事务的生命周期为Begin(TM),Registry(RM),Commit/Rollback(TM&TC)这三部分。
在业务逻辑开始时,TM 会根据业务需求决定是否开启一个全局事务。如果需要开启全局事务,TM 会向 TC 注册该事务,并获得一个唯一的事务 ID(XID)。
在业务逻辑执行过程中,TM 会调用各个服务的业务方法,这些业务方法会通过 RM 对资源进行操作,从而形成分支事务。
在分支事务执行过程中,RM 会记录分支事务对资源的修改,并将这些修改暂存在本地。如果分支事务执行成功,RM 会向 TC 汇报分支事务的执行状态,并等待 TC 的提交或回滚指令。
在全局事务执行过程中,各个 RM 会向 TC 注册分支事务,并汇报分支事务的执行状态。TC 根据这些状态信息来决定全局事务的提交或回滚。
如果所有分支事务都执行成功,TC 会通知各个 RM 提交分支事务,并将全局事务状态更新为 “已提交”。如果有任何一个分支事务执行失败,TC 会通知各个 RM 回滚分支事务,并将全局事务状态更新为 “已回滚”。
TC、RM 和 TM 三个核心组件的协作,实现了分布式事务的管理和协调,保证了分布式系统中数据的一致性。
Seata AT模式实现
Seata AT 不仅是官方最推荐的一套分布式事务解决方案,也是大多数 Seata 使用者选用的方案。AT 方案备受推崇,一个最主要的原因就在于省心。
Seata AT 可以给你带来一种“无侵入”式的编程体验,你不需要改动任何业务代码,只需要一个注解@GlobalTransactional和少量的配置信息,就可以实现分布式事务。为什么一个注解就能帮我们解决问题呢,AT模式是怎么做的呢?
我通过实际的业务例子,介绍AT方案的底层原理。
我们有一个商城下单的例子,用户从商品服务下单商品,商品服务调用物流服务生成物流信息。
Seata AT的业务流程分为两个阶段执行。
一、第一阶段:
- 解析 SQL:
- 业务数据被访问时,Seata 的代理数据源会对业务 SQL 进行解析,得到 SQL 的类型(UPDATE、INSERT、DELETE 等)、表信息、条件等相关信息。
- 查询前镜像:
- 根据解析得到的表信息和条件,查询当前数据的 “前镜像”,即数据在业务 SQL 执行前的状态,并将其记录在 undo log(回滚日志)中。例如,对于一条 UPDATE 语句,会查询出被更新的数据在更新前的值。
- 执行业务 SQL:
- 获取全局锁、本地锁,执行实际的业务 SQL,对数据进行操作。这个操作会改变数据库中的数据状态。
- 写入后镜像:
- 在业务 SQL 执行后,将数据的 “后镜像”,即数据在业务 SQL 执行后的状态,写入 undo log。这样,undo log 中就同时保存了数据的前镜像和后镜像,为后续的事务回滚或提交提供了依据。
- 向 TC 注册分支事务:
- 资源管理器(RM)向事务协调器(TC)注册分支事务,并将 undo log 的相关信息汇报给 TC。TC 记录分支事务的状态,此时分支事务处于 “准备提交” 状态。
二、第二阶段:
- 提交阶段:
- 如果全局事务正常提交,TC 会通知所有分支事务进行提交。
- RM 收到提交指令后,会将分支事务提交,并删除相应的 undo log。
- 具体的提交过程是将业务 SQL 对数据的修改正式生效,例如将更新后的数据持久化到数据库中。
- 回滚阶段:
- 如果全局事务需要回滚,TC 会通知所有分支事务进行回滚。
- RM 收到回滚指令后,会根据 undo log 中的前镜像数据,获取全局锁、本地锁后,对数据进行回滚操作,将数据恢复到业务 SQL 执行前的状态。
- 回滚完成后,删除相应的 undo log。
通过这两个阶段的执行,SEATA 的 AT 模式能够在分布式环境下实现高效的事务管理,保证数据的一致性。
在第一阶段和第二阶段,seata都用到了一个表undo log,这个表就是AT方案的核心,undo log的表主要解决了两个问题
- 性能问题,核心逻辑在一阶段本地事务直接提交,DB资源释放。如果需要回滚,只需要读取undo log的数据把数据库回滚即可
- 全局事务的Commit和Rollback异步执行
说在最后
第一次了解Seata的实现原理时,内心有一个疑问:如果undo log 记录的数据,在回滚前被另外的事务修改了,此时收到回滚请求,那数据用undo log的数据回滚,那岂不是把别的事务的修改给丢失了?
确实会有这种情况,Seata会使用本地锁与全局锁来解决这个问题,那本地锁和全局锁的作用到底是做什么用的,undo log的数据到底是如何在回滚中使用的,我们下一篇文章再说。
本篇文章是第60篇原创文章,2024目标进度60/100,欢迎围观。