Seata介绍(目前使用的一些场景)

转载自己在公司内网的一次分享(虽然引用了大量网上的图),中间穿插一些自己的理解总结。
Seata 事务模式是什么?
名词解释:
TM transaction manager,全局事务的管理者,可以理解为全局调用的发起者,哪个服务发起了全局服务调用,这个服务就是TM
RM Resouce Manager,可以简单理解为整个全局调用中,每个节点都是一个RM
TC Tracnaction Coordinator 全局事务的协调者,可以理解为Seata Server
Seata介绍(目前使用的一些场景)_第1张图片

  1. Seata 对事务的定义
    Seata 定义了全局事务的框架。

全局事务定义为若干分支事务的整体协调:

TM 向 TC 请求发起(Begin)、提交(Commit)、回滚(Rollback)全局事务。
TM 把代表全局事务的 XID 绑定到分支事务上。
RM 向 TC 注册,把分支事务关联到 XID 代表的全局事务中。
RM 把分支事务的执行结果上报给 TC。(可选)
TC 发送分支提交(Branch Commit)或分支回滚(Branch Rollback)命令给 RM。
Seata介绍(目前使用的一些场景)_第2张图片

Seata 的全局事务处理过程,分为两个阶段:

执行阶段> :执行分支事务,并保证执行结果满足是可回滚的(Rollbackable)和持久化的(Durable)。
完成阶段> :根据执行阶段结果形成的决议,应用通过 TM 发出的全局提交或回滚的请求给 TC,> TC 命令 RM 驱动 分支事务 进行 Commit 或 Rollback。

Seata 的所谓事务模式是指:运行在 Seata 全局事务框架下的分支事务的行为模式。> > 准确地讲> ,应该叫作> 分支事务模式> 。

不同的事务模式区别在于分支事务使用不同的方式达到全局事务两个阶段的目标。> > 即,回答以下两个问题:

执行阶段> :如何执行并保证执行结果满足是可回滚的(Rollbackable)和持久化的(Durable)。
完成阶段> :收到 TC 的命令后,做到事务的回滚/提交。

  1. 其它二阶段事务如何在 Seata 事务框架下运转
    1)TCC 事务模式
    首先来看下 TCC 事务如何融合在 Seata 事务框架中:

Seata介绍(目前使用的一些场景)_第3张图片

可以发现,其实跟 Seata 的事务框架图长得非常像,而区别为 RM 负责管理就是一阶段的 try 执行和二阶段的 confirm/cancel,一样是由 TM 进行事务的 Begin(发起),RM 被 TM 调用后执行一阶段的 Try 方法,等待调用链路走完的时候,TM 向 TC 告知二阶段决议,此时 TC 对 RM 驱动二阶段执行(下发通知,RM 执行 confirm/cancel)。

补充tcc相关理念:

  1. 空回滚
    在没有调用TCC资源Try方法的情况下,调用了二阶段的Cancel方法,Cancel方法需要识别出这是一个空回滚,然后直接返回成功。

出现原因是当一个分支事务所在服务宕机或网络异常,分支事务调用记录为失败,这个时候其实是没有执行Try阶段,当故障恢复后,分布式事务进行回滚则会调用二阶段的Cancel方法,从而形成空回滚。

解决思路的关键是要识别出这个空回滚。思路很简单就是需要知道一阶段是否执行了,如果执行了,那就是正常回滚;如果没执行,那就是空回滚。前面已经说过TM在发起全局事务时生成全局事务记录,全局事务ID贯穿整个分布式事务调用链。再额外增加一张分支事务记录表,表中有全局事务ID贯穿和分支事务ID,第一阶段Try方法里会插入一条记录,表示一阶段执行了。Cancel接口里读取该记录,如果该记录存在,则正常回滚;如果该记录不存在,则是空回滚。

  1. 幂等性
    TM(即事务管理器)在发起全局事务时生成全局事务记录,全局事务ID贯穿整个分布式事务调用链条,用来记录事务上下s文,追踪和记录状态,由于Confirm和Cancel失败需进行重试,因此需要实现为幂等。幂等性是指同一操作无论请求多少次,其结果都相同。

所以为了保证TCC二阶段提交重试机不会引发数据不一致,要求TCC的二阶段Try、Confirm和Cancel接口保证幂等,这样不会重复使用或者释放资源。如果幂等控制没做好,很有可能导致数据不一致等严重问题。

  1. 悬挂
    悬挂就是对于一个分布式事务,其二阶段Cancel接口比Try接口先执行。

出现原因是在RPC调用分支事务Try时,先注册分支事务,再执行RPC调用,如果此时RPC调用的网络发生拥堵,通常RPC调用是有超时时间的,RPC超时以后,TM就会通知RM回滚分布式事务,可能回滚完成后,RPC请求才到达参与者真正执行,而一个Try方法预留的业务资源,只有该分布式事务才能使用,该分布式事务第一阶段预留的业务资源就再也没有人能够处理了,对于这种情况,我们称之为悬挂,即业务资源预留后没法继续处理。

解决思路是如果二阶段执行完成,那一阶段就不再继续执行。在执行一阶段事务时判断在该全局事务下,”分支事务记录“表中是否已有二阶段事务记录,如果有则不执行Try。

2)XA 事务模式
基于XA协议的两阶段提交
XA是一个分布式事务协议,由Tuxedo提出。XA中大致分为两部分:事务管理器和本地资源管理器。其中本地资源管理器往往由数据库实现,比如Oracle、DB2这些商业数据库都实现了XA接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。,XA协议比较简单,而且一旦商业数据库实现了XA协议,使用分布式事务的成本也比较低。但是,XA也有致命的缺点,那就是性能不理想,特别是在交易下单链路,往往并发量很高,XA无法满足高并发场景。XA目前在商业数据库支持的比较理想,在mysql数据库中支持的不太理想,mysql的XA实现,没有记录prepare阶段日志,主备切换回导致主库与备库数据不一致。许多nosql也没有支持XA,这让XA的应用场景变得非常狭隘。

Seata介绍(目前使用的一些场景)_第4张图片

如图所示,XA 模式其实就是 Seata 底层利用了 XA 接口,在一阶段二阶段时自动处理。如一阶段时,XA 的 RM 通过代理用户数据源,创建 XAConnection,进行开启 XA 事务(XA start)和 XA-prepare(此时 XA 的任何操作都会被持久化,即便宕机也能恢复),在二阶段时,TC 通知 RM 进行 XA 分支的 Commit/Rollback 操作。

AT 模式是什么?

  1. 一阶段

业务 sql:update product set name = ‘GTS’ where name = ‘TXC’。

一阶段的执行过程对用户是无感知的,用户侧的业务 sql 保持不变,而 AT 模式下一阶段具体发生了什么?接下来,简单说下。

解析 sql 并查询得到前镜像:select id, name, since from product where name = ‘TXC’。
执行业务 sql。
查询执行后的数据作为后镜像:select id, name, since from product where id = 1。
2. 二阶段
提交:仅需把事务相关信息删除即可(理论上不删除也没问题)。

回滚:取出前镜像进行回滚。

通过上述简单的例子,其实可以发现,AT 模式就是自动补偿式事务,那 AT 具体都做了哪些呢?下文将会讲述。

AT 如何保证分布式事务一致性?
Seata介绍(目前使用的一些场景)_第5张图片

可能很多人刚看到上图会有疑问,其实这个就是无侵入式 AT 模式的做法示意图。首先用户还是从接口进入,到达事务发起方,此时对业务开发者来说,这个发起方入口就是一个业务接口罢了,一样地执行业务 sql,一样地 return 响应信息给客户端并没有什么改变。而背后就是用户的 sql 被 Seata 代理所托管,Seata-AT 模式能感知到用户的所有 sql,并对之进行操作,来保证一致性。

Seata-AT 是怎么做到无侵入的呢?

Seata介绍(目前使用的一些场景)_第6张图片

如图所示,应用启动时 Seata 会自动把用户的 DataSource 代理,对 JDBC 操作熟悉的用户其实对 DataSource 还是比较熟悉的,拿到了 DataSource,就等于掌握了数据源连接,也就能在背后做些“小动作”,此时对用户来讲也是无感知无入侵。

之后业务有请求进来,执行业务 sql 时,Seata 会解析用户的 sql,提取出表元数据,生成前镜像,再通过执行业务 sql,保存执行 sql 后的后镜像(至于后镜像的介绍之后会讲到),生成行锁之后在注册分支时携带到 Seata-Server,也就是 TC 端。

到此为止,在 Client 端的一阶段操作就已经完成了,无感知、无入侵。此时如果思考下,会发现这里其实有一个行锁,这个行锁是干什么用的呢?这就是要接着讲到 Seata-AT 是如何保证分布式下的事务隔离性,这里直接拿官网的示例来说。

  1. 写隔离
    一阶段本地事务提交前,需要确保先拿到全局锁 。
    拿不到全局锁,不能提交本地事务。
    拿全局锁的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。
    以一个示例来说明:

两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000。

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

Seata介绍(目前使用的一些场景)_第7张图片

tx1 二阶段全局提交,释放全局锁 。tx2 拿到全局锁提交本地事务。

Seata介绍(目前使用的一些场景)_第8张图片

如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。

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

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

这个时候隔离性想必大家已经比较明白了,此时一阶段的大部分操作相信大家也比较明白了,接下来我们继续往下一阶段解析。

  1. AT 模式二阶段处理
    Seata介绍(目前使用的一些场景)_第9张图片

由上图可见,在二阶段提交时,TC 仅是下发一个通知 :把之前一阶段做记录的 undoLog 删除,并把相关事务信息如:行锁删除,之后让因为在竞争锁被阻塞的事务顺利进行。

而二阶段是回滚时,则要多做一些处理。

Seata介绍(目前使用的一些场景)_第10张图片

首先在 Client 端收到 TC 告知的二阶段是回滚时,会去查到对应的事务的 undolog,取出后镜像,对比当前的数据(因为 SeataAT 是从业务应用层面进行保护分布式事务,如果此时在数据库层面直接修改了库内信息,这个时候 SeataAT 的行锁不起隔离性作用),如果出现了在全局事务以外的数据修改,此时判定为脏写,而 Seata 因为无法感知这个脏写如何发生,此时只能打印日志和触发异常通知,告知用户需要人工介入(规范修改数据入口可避免脏写)。

而如果没有发生脏写就比较简单了,拿出前镜像,众所皆知事务是需要有原子性的,要么一起发生,要么都不发生,此时前镜像记录了发生之前的数据,进行回滚后,就达到了类似本地事务那样的原子性效果。回滚后,再把事务相关信息,如 undolog,行锁进行删除。二阶段回滚算是告一段落了。

既然介绍完了 AT 模式的一阶段及二阶段的原理思想方式,那么 AT 在 Seata 的分布式事务框架下是怎么样的呢?

Seata介绍(目前使用的一些场景)_第11张图片

可以看到,AT 与其它事务模式在 Seata 事务框架中,会多出一个 undolog 的表(相对其它模式的入侵点),但是除此之外,对业务来说,几乎是零入侵性,这也就是为什么 AT 模式在 Seata 中受众广泛的原因。

  1. AT 模式与 Seata 支持的其它二阶段模式区别
    首先应该明白,目前为止,不存在有任何一种分布式事务的可以满足所有场景。

无论 AT 模式、TCC 模式还是 Saga 模式,这些模式的提出,本质上都源自 XA 规范对某些场景需求的无法满足。

目前分为 3 点来做出对比:

数据锁定
AT 模式使用全局锁保障基本的写隔离,实际上也是锁定数据的,只不过锁在 TC 侧集中管理,解锁效率高且没有阻塞的问题。

TCC 模式无锁,利用本地事务排他锁特性,可预留资源,在全局事务决议后执行相应操作。

XA 模式在整个事务处理过程结束前,涉及数据都被锁定,读写都按隔离级别的定义约束起来。

死锁(协议阻塞)
XA 模式 prepare 后(老版本的数据库中,需要 XA END 后,再下发 prepare <三阶段由来>),分支事务进入阻塞阶段,收到 XA commit 或 XA rollback 前必须阻塞等待。

AT 可支持降级,因为锁存储在 TC 侧,如果 Seata 出现 bug 或者其它问题,可直接降级,对后续业务调用链无任何影响。

TCC 无此问题。

性能
性能的损耗主要来自两个方面:一方面,事务相关处理和协调过程,增加单个事务的 RT;另一方面,并发事务数据的锁冲突,降低吞吐。其实主要原因就是上面的协议阻塞跟数据锁定造成。

XA 模式它的一阶段不提交,在大并发场景由于锁存储在多个资源方(数据库等),加剧了性能耗损。

AT 模式锁粒度细至行级(需要主键),且所有事务锁存储在 TC 侧,解锁高效迅速。

TCC 模式性能最优,仅需些许 RPC 开销,及 2 次本地事务的性能开销,但是需要符合资源预留场景,且是对业务侵入性较大(需要业务开发者每个接口分为 3 个,一个 try,2 个二阶段使用的 confirm 和 cancel )。
可能很多同学对 XA 和 AT 的锁 & 协议阻塞不是特别理解,那么直接来看下图:

Seata介绍(目前使用的一些场景)_第12张图片

可以试着猜一下是哪个是 XA?其实下图的是 XA,因为它带来的锁粒度更大,且锁定时间更久,导致了并发性能相对 AT 事务模型来说,差的比较多,所以至今XA模式的普及度都不很太高。

总结:关于seata是可以做到对项目代码无入侵,代价是需要部署和维护一个中间件,关于at和xa模式对比从概念上看很难区别,我的理解差异点在于AT模式的隔离就是靠全局锁来保证,粒度细至行级,锁信息存储在Seata-Server一侧。

XA模式的隔离性就是由本地数据库保证,锁存储在各个本地数据库中。由于XA模式一旦执行了prepare后,再也无法重入这个XA事务,也无法跟其他XA事务共享锁。因为XA协议,仅是通过XID来start一个xa事务,本身它不存在所谓的分支事务说法,它本事就是一个XA事务而已,也就是说它只管它自己。at模式的undolog就是把本地事务作用中的undolog,利用他的原理,做到了分布式事务中,来保证了分布式事务下的事务一致性。

目前使用:目前是结合sharding在使用,如xxljob跑任务会用到一些订单实时报价并修改用户订单概览等信息,需要与第三方系统交互,系统就需要保证数据的最终一致性

你可能感兴趣的:(java,分布式,中间件)