分布式事务
发布日期:2021-06-28 23:30:13 浏览次数:3 分类:技术文章

本文共 4200 字,大约阅读时间需要 14 分钟。

分布式事务 Seata AT模式原理

AT,XA,TCC,Saga

Seata 提供四种模式解决分布式事务场景,AT,XA,TCC,Saga。

TC (Transaction Coordinator) - 事务协调者

维护全局和分支事务的状态,驱动全局事务提交或回滚。

TM (Transaction Manager) - 事务管理器

定义全局事务的范围:开始全局事务、提交或回滚全局事务。

RM (Resource Manager) - 资源管理器

管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

分布式事务

分布式事务的产生的原因

数据库分库分表

当数据库单表一年产生的数据超过1000W,那么就要考虑分库分表,具体分库分表的原理在此不做解释,以后有空详细说,简单的说就是原来的一个数据库变成了多个数据库。这时候,如果一个操作既访问01库,又访问02库,而且要保证数据的一致性,那么就要用到分布式事务。.

应用SOA化

所谓的SOA化,就是业务的服务化。比如原来单机支撑了整个电商网站,现在对整个网站进行拆解,分离出了订单中心、用户中心、库存中心。对于订单中心,有专门的数据库存储订单信息,用户中心也有专门的数据库存储用户信息,库存中心也会有专门的数据库存储库存信息。这时候如果要同时对订单和库存进行操作,那么就会涉及到订单数据库和库存数据库,为了保证数据一致性,就需要用到分布式事务。

AT

AT

这是Seata的一大特色,AT对业务代码完全无侵入性,使用非常简单,改造成本低。我们只需要关注自己的业务SQL,Seata会通过分析我们业务SQL,反向生成回滚数据

AT 包含两个阶段

  • 一阶段,所有参与事务的分支,本地事务Commit 业务数据和回滚日志(undoLog)
  • 二阶段,事务协调者根据所有分支的情况,决定本次全局事务是Commit 还是 Rollback(二阶段是完全异步)

写隔离

  • 一阶段本地事务提交,需要确保先拿到 全局锁
  • 拿不到 全局锁 ,不能提交本地事务。
  • 拿 全局锁 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。
    以一个示例来说明:
    两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000。

tx1 先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 - 100 = 900。本地事务提交前,先拿到该记录的 全局锁 ,本地提交释放本地锁。 tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 = 800。本地事务提交前,尝试拿该记录的 全局锁 ,tx1 全局提交前,该记录的全局锁被 tx1 持有,tx2 需要重试等待 全局锁 。

在这里插入图片描述
tx1 二阶段全局提交,释放 全局锁 。tx2 拿到 全局锁 提交本地事务。
在这里插入图片描述
如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。

此时,如果 tx2 仍在等待该数据的 全局锁,同时持有本地锁,则 tx1 的分支回滚会失败。分支的回滚会一直重试,直到 tx2 的 全局锁 等锁超时放弃 全局锁 并回滚本地事务释放本地锁,tx1 的分支回滚最终成功。

因为整个过程 全局锁 在 tx1 结束前一直是被 tx1 持有的,所以不会发生 脏写 的问题。

读隔离

在数据库本地事务隔离级别 读已提交(Read Committed) 或以上的基础上,Seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted) 。

如果应用在特定场景下,必需要求全局的 读已提交 ,目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的代理。

在这里插入图片描述
SELECT FOR UPDATE 语句的执行会申请 全局锁 ,如果 全局锁 被其他事务持有,则释放本地锁(回滚 SELECT FOR UPDATE 语句的本地执行)并重试。这个过程中,查询是被 block 住的,直到 全局锁 拿到,即读取的相关数据是 已提交 的,才返回。

出于总体性能上的考虑,Seata 目前的方案并没有对所有 SELECT 语句都进行代理,仅针对 FOR UPDATE 的 SELECT 语句。

XA

也是我们常说的二阶段提交,XA要求数据库本身提供对规范和协议的支持。XA用起来的话,也是对业务代码无侵入性的。

上述其他三种模式,都是属于补偿型,无法保证全局一致性。啥意思呢,例如刚刚说的AT模式,我们是可能读到这一次分布式事务的中间状态,而XA模式不会。

补偿型 事务处理机制构建在 事务资源(数据库) 之上(要么在中间件层面,要么在应用层面),事务资源 本身对分布式事务是无感知的,这也就导致了补偿型事务无法做到真正的 全局一致性 。

比如,一条库存记录,处在 补偿型 事务处理过程中,由 100 扣减为 50。此时,仓库管理员连接数据库,查询统计库存,就看到当前的 50。之后,事务因为意外回滚,库存会被补偿回滚为 100。显然,仓库管理员查询统计到的 50 就是 脏 数据。
如果是XA的话,中间态数据库存 50 由数据库本身保证,不会被仓库管理员读到(当然隔离级别需要 读已提交 以上)

但是全局一致性带来的结果就是数据的锁定(AT模式也是存在全局锁的,但是隔离级别无法保证 ),例如全局事务中有一条update语句,其他事务想要更新同一条数据的话,只能等待全局事务结束

基于XA协议的两阶段提交

XA是一个分布式事务协议,由Tuxedo提出。XA中大致分为两部分:事务管理器本地资源管理器。其中本地资源管理器往往由数据库实现,比如Oracle、DB2这些商业数据库都实现了XA接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。XA实现分布式事务的原理如下:

在这里插入图片描述
XA协议比较简单,而且一旦数据库实现了XA协议,使用分布式事务的成本也比较低。
XA也有致命的缺点,那就是性能不理想,特别是在交易下单链路,往往并发量很高,XA无法满足高并发场景
XA目前在商业数据库支持的比较理想,在mysql数据库中支持的不太理想,mysql的XA实现,没有记录prepare阶段日志,主备切换回导致主库与备库数据不一致。许多nosql也没有支持XA,这让XA的应用场景变得非常狭隘

消息事务+最终一致性

是基于消息中间件的两阶段提交,本质上是对消息中间件的一种特殊利用,它是将本地事务和发消息放在了一个分布式事务里,保证要么本地操作成功成功并且对外发消息成功,要么两者都失败,开源的RocketMQ就支持这一特性,具体原理如下:

在这里插入图片描述

1、A系统向消息中间件发送一条预备消息

2、消息中间件保存预备消息并返回成功
3、A执行本地事务
4、A发送提交消息给消息中间件

通过以上4步完成了一个消息事务。对于以上的4个步骤,每个步骤都可能产生错误,下面一一分析:

步骤一出错,则整个事务失败,不会执行A的本地操作

步骤二出错,则整个事务失败,不会执行A的本地操作
步骤三出错,这时候需要回滚预备消息,怎么回滚?答案是A系统实现一个消息中间件的回调接口,消息中间件会去不断执行回调接口,检查A事务执行是否执行成功,如果失败则回滚预备消息
步骤四出错,这时候A的本地事务是成功的,那么消息中间件要回滚A吗?答案是不需要,其实通过回调接口,消息中间件能够检查到A执行成功了,这时候其实不需要A发提交消息了,消息中间件可以自己对消息进行提交,从而完成整个消息事务
基于消息中间件的两阶段提交往往用在高并发场景下,将一个分布式事务拆成一个消息事务(A系统的本地操作+发消息)+B系统的本地操作,其中B系统的操作由消息驱动,只要消息事务成功,那么A操作一定成功,消息也一定发出来了,这时候B会收到消息去执行本地操作,如果本地操作失败,消息会重投,直到B操作成功,这样就变相地实现了A与B的分布式事务。原理如下:

在这里插入图片描述

TCC

在这里插入图片描述

TCC 模式同样包含两个阶段

  • Try 阶段 :所有参与分布式事务的分支,对业务资源进行检查和预留
  • 二阶段 Confirm:所有分支的Try全部成功后,执行业务提交
  • 二阶段 Cancel:取消Try阶段预留的业务资源

在这里插入图片描述

对比AT或者XA模式来说,TCC模式需要我们自己抽象并实现Try,Confirm,Cancel三个接口,编码量会大一些,但是由于事务的每一个阶段都由开发人员自行实现。而且相较于AT模式来说,减少了SQL解析的过程,也没有全局锁的限制,所以TCC模式的性能是优于AT 、XA模式。

PS:果然简单和高效难以两全的

根据两阶段行为模式的不同,我们将分支事务划分为 Automatic (Branch) Transaction Mode 和 Manual (Branch) Transaction Mode.

AT 模式(参考链接 TBD)基于 支持本地 ACID 事务 的 关系型数据库:

  • 一阶段 prepare 行为:在本地事务中,一并提交业务数据更新和相应回滚日志记录。
  • 二阶段 commit 行为:马上成功结束,自动 异步批量清理回滚日志。
  • 二阶段 rollback 行为:通过回滚日志,自动 生成补偿操作,完成数据回滚。

相应的,TCC 模式,不依赖于底层数据资源的事务支持:

  • 一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。
  • 二阶段 commit 行为:调用 自定义 的 commit 逻辑。
  • 二阶段 rollback 行为:调用 自定义 的 rollback 逻辑。
    所谓 TCC 模式,是指支持把 自定义 的分支事务纳入到全局事务的管理中。

Saga

在这里插入图片描述

Saga 是长事务解决方案,每个参与者需要实现事务的正向操作和补偿操作。当参与者正向操作执行失败时,回滚本地事务的同时,会调用上一阶段的补偿操作,在业务失败时最终会使事务回到初始状态

Saga与TCC类似,同样没有全局锁。由于相比缺少锁定资源这一步,在某些适合的场景,Saga要比TCC实现起来更简单。

由于Saga和TCC都需要我们手动编码实现,所以在开发时我们需要参考一些设计上的规范,由于不是本文重点,这里就不多说了

转载地址:https://blog.csdn.net/ynchyong/article/details/112346220 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Spring
下一篇:默认网关与默认路由

发表评论

最新留言

哈哈,博客排版真的漂亮呢~
[***.90.31.176]2024年04月12日 16时16分09秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章