前言:
事务分为本地事务和分布式事务两种,分布式事务的出现场景:跨库事务、分库分表和微服务调用。Seata 是一款阿里开源的分布式事务框架,致力于提供高性能和简单易用的分布式事务服务。
一、分布式事务解决方案
1.1、本地事务
首先我们回顾一下在单体应用中,例如一个业务调用了3个模块,他们都使用同一个数据源,是靠本地事务来保证事务一致性。Spring通过
AOP
的方式对数据库事务进行了整合,使我们平时在解决本地事务时,只需要加上
@Transactional
注解
即可很方便控制本地事务。
用户购买商品的业务逻辑,整个业务逻辑由3个微服务提供支持:
-
库存服务:对给定的商品扣除仓储数量。
-
订单服务:根据采购需求创建订单。
-
帐户服务:从用户帐户中扣除余额。
1.2、分布式事务
对于微服务架构,完成某一个业务功能可能需要横跨多个服务,操作多个数据库。这就涉及到到了分布式事务。分布式事务需要保证在多个数据库连接下,代码要么么全部成功,要么全部失败。
从本质上来说,分布式事务就是操作多个数据库连接时,也能保证数据要么一起修改成功,要么一起失败!为了保证不同资源服务器的数据一致性。
在微服务架构中,上面用户购买商品这3个模块会变为3个独立的微服务,各自有自己的数据源,调用逻辑就变为:
-
Business 是业务入口,在程序中会通过
注解
来说明他是一个
全局事务
,这时他的角色为 TM(事务管理者)。Business 会请求 TC(事务协调器,一个独立运行的服务),说明自己要开启一个全局事务,TC 会生成一个全局事务ID(XID),并返回给 Business。 -
Business 得到 XID 后,开始调用微服务,例如调用 Storage。此时 Storage 的角色是 RM(资源管理者),资源是指本地数据库。Storage 会收到 XID,知道自己的事务属于这个全局事务。Storage 执行自己的业务逻辑,操作本地数据库。Storage 会把自己的事务注册到 TC,作为这个 XID 下面的一个
分支事务
,并且把自己的事务执行结果也告诉 TC。Order、Account 的执行逻辑与 Storage 一致。 -
在各个微服务都执行完成后,TC 可以知道 XID 下各个分支事务的执行结果,TM(Business) 也就知道了。Business 如果发现各个微服务的本地事务都执行成功了,就请求 TC 对这个 XID 提交,否则回滚。
1.3、常见的分布式事务解决方案
①两阶段提交2PC
2PC有XA/JTA、Seata的AT模式等等,主要表现为:有全局锁,保证强一致性,更适合金融领域。
②TCC (Try-Confirm-Cancel)补偿模式
TCC核心思想
是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。
分为三个阶段:
-
Try 阶段:主要是对业务系统做检测(一致性)及资源预留(准隔离性)
-
Confirm 阶段:主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。(Confirm 操作满足幂等性。要求具备幂等设计,Confirm 失败后需要进行重试)
-
Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。(Cancel 操作满足幂等性)
TCC也可以看做是两阶段提交,不过不需要全局锁,保证最终一致性。比XA/JTA,Seata的AT模式效率高,但需要手动实现try、confirm、cancel接口,实现起来比较难!在微服务架构下实现TCC时,很有可能出现网络超时、重发,机器宕机等一系列的异常,出现空回滚、幂等、悬挂的问题。
常用的TCC开源框架有:Tcc-Transaction、 Hmily、 ByteTCC、 EasyTransaction、 Seata TCC等
③ 可靠消息(最终一致性):
可靠消息最终一致性方案是指当事务发起方执行完成本地事务后发出一条消息到消息中间件,事务参与方(消息消费者)一定能够接收到消息并处理事务成功,此方案强调的是只要消息发给事务参与方,则最终事务要达到一致。
强一致性的XA、AT可能更适合金融领域,但对于一些高并发场景时,比如电商,其实更多的是采用补偿的措施去解决分布式问题,比如mq发消息等
,虽然可能存在消息丢失,但可以人工补偿,保证最终一致性即可,避免使用全局锁拖慢整个系统性能!提供两种解决方案本地消息表和RocketMq事务消息。
a>本地消息表(异步确保):
本地消息表这种实现方式应该是业界使用最多的,其核心思想是将分布式事务拆分成本地事务进行处理,这种思路是来源于ebay。
本地消息表的关键在于本地有一张存储消息日志的记录表,需要启动一个定时任务去不停地扫描消息日志记录,确保消息能够被发送。具体流程如下图:
上图流程:
1)事务发起方本地事务执行成功,在本地消息表中记录消息日志。
2)启动定时任务,循环扫描本地消息表。
3)定时任务扫描到消息则发送消息到消息中间件。
4)消息中间件收到消息,成功返回消息发送成功通知给事务发起方。
5)事务发起方收到消息发送成功则删除日志消息。
6)事务参与方订阅消息,消费消息。
7)事务参与方处理本地事务。
8)本地事务处理成功,发送成功ack给消息中间件。
需要注意的点:
事务参与方保证接口幂等性。
b>RocketMq事务消息方案
Apache RocketMQ 4.3之后的版本正式支持事务消息,为分布式事务实现提供了便利性支持。在RocketMQ 4.3后实现了完整的事务消息,实际上其实是对本地消息表的一个封装,将本地消息表移动到了MQ内部,解决 Producer 端的消息发送与本地事务执行的原子性问题。
实现流程:
1)事务发起方发送Half事务消息
2)RocketMq回复Half发送成功
3)事务发起方执行本地事务
4)事务发起方执行本地事务成功,发送commit到RocketMq,mq投递消息到事务参与方;
事务发起方执行本地事务失败,发送rollback到RocketMq,mq删除消息。
5)当RocketMq一定时间内未收到来自事务发起方的确认信息,会对事务发起方进行事务回查。
6)事务发起方查询本地事务状态。
7)事务发起方根据查询到的事务状态发送commint/rollback到RocketMq,继续走8或9。
8)当RocketMq发起commit后,收到失败或一定时间未收到成功ack,则会发起重试。
④ 最大努力通知(最终一致性)
最大努力通知型( Best-effort delivery)是最简单的一种柔性事务,是分布式事务中对一致性要求最低的一种,适用于一些最终一致性时间敏感度低的业务,且被动方处理结果不影响主动方的处理结果。典型的使用场景:如银行通知、商户通知等。
最大努力通知型的实现方案,一般符合以下特点:
不可靠消息:业务活动主动方,在完成业务处理之后,向业务活动的被动方发送消息,直到通知N次后不再通知,允许消息丢失(不可靠消息)。
定期校对:业务活动的被动方,根据定时策略,向业务活动主动方查询(主动方提供查询接口),恢复丢失的业务消息。
所以最大努力通知方案需要实现如下功能:
-
消息重复通知机制。
-
消息查询校对功能。
以发短信业务为例,除了要回调通知发送端外,还要允许发送端查询发送状态,保证最终一致。
二、Seata简介
2.1、Seata是什么
Seata 是一款阿里开源的分布式事务框架,致力于提供高性能和简单易用的分布式事务服务。Seata 为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
Seata 支持的主要是 AT模式。就是自动化事务,使用非常简单,对业务代码没有侵入性。AT模式是阿里首推的模式,阿里云上有商用版本的GTS(Global Transaction Service 全局事务服务)
相对于 AT 模式,TCC 模式对业务代码有一定的侵入性,但是 TCC 模式无 AT 模式的全局行锁,TCC 性能会比 AT 模 式高很多。但需要改 造成
try
、
confirm
、
canel
3个接口,开发成本高
2.2、Seata的三种角色
在 Seata 的架构中,一共有三个角色:
TC (Transaction Coordinator) – 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) – 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) – 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
其中,TC 为单独部署的 Server 服务端,TM 和 RM 为嵌入到应用中的 Client 客户端。
在 Seata 中,一个分布式事务的生命周期如下:
事务管理器TM 请求 事务协调者TC 开启一个全局事务。TC 会生成一个 XID 作为该全局事务的编号。XID会在微服务的调用链路中传播,保证将多个微服务的子事务关联在一起。TM是一个事务的发起者,可以是事务方法的入口
客户端的资源管理器RM 请求 TC 将本地事务注册为全局事务的分支事务,通过全局事务的 XID 进行关联。
TM 请求 TC 告诉 XID 对应的全局事务是进行提交还是回滚。
TC 驱动 RM 们将 XID 对应的自己的本地事务进行提交还是回滚。
2.3、Seata重要机制
(1)全局事务的回滚是如何实现的呢?
Seata 有一个重要的机制:
回滚日志
。
每个分支事务对应的数据库中都需要有一个回滚日志表 UNDO_LOG,在真正修改数据库记录之前,都会先记录修改前的记录值,以便之后回滚。
在收到回滚请求后,就会根据 UNDO_LOG 生成回滚操作的 SQL 语句来执行。
如果收到的是提交请求,就把 UNDO_LOG 中的相应记录删除掉。
(2)RM 是怎么自动和 TC 交互的?
是通过
监控拦截JDBC
实现的,例如监控到开启本地事务了,就会自动向 TC 注册、生成回滚日志、向 TC 汇报执行结果。
(3)二阶段回滚失败怎么办?
例如 TC 命令各个 RM 回滚的时候,有一个微服务挂掉了,那么所有正常的微服务也都不会执行回滚,当这个微服务重新正常运行后,TC 会重新执行全局回滚。
2.4、Seata的设计思路
Seata的AT模式的核心是对业务无侵入,是一种改进后的两阶段提交,其设计思路如下:
第一阶段:RM端提交本地事务,生成undo日志记录,释放本地锁和连接资源。并向TC注册分支事务,通过全局事务的 XID 进行关联。
第二阶段:完全异步
分布式事务操作成功,则TC通知RM异步删除undolog
分布式事务操作失败,则TM向TC发送回滚请求,RM 收到协调器TC发来的回滚请求,通过 XID 和 Branch ID 找到相应的回滚日志记录,通过回滚记录生成反向的更新 SQL 并执行,以完成分支的回滚。
2.5、设计亮点以及存在的问题
设计亮点:
应用层基于SQL解析实现了自动补偿,从而最大程度的降低业务侵入性;
将分布式事务中TC(事务协调者)独立部署,负责事务的注册、回滚;避免单点故障
通过全局锁实现了写隔离与读隔离。
存在的问题:
性能损耗:由于Seata在解决分布式事务时,需要多次与TC通讯,每次都需要一次远程通讯RPC,而且是同步的。还要写undoLog日志,每条写SQL都会增加这么多开销,粗略估计会增加5倍响应时间。
性价比:如果仅有极少的请求会失败,需要触发回滚。在使用了Seata解决分布式事务后,为了极少的交易回滚,需要将大部分的成功交易的响应时间增加5倍,这样的代价有待考量。
死锁问题:Seata的引入全局锁会额外增加死锁的风险,但如果出现死锁,会不断进行重试,最后靠等待全局锁超时,这种方式并不优雅,也延长了对数据库锁的占有时间。
2.5、Seata的DB模式配置
Seata分TC、TM和RM三个角色,TC(Server端)为单独服务端部署,TM和RM(Client端)由业务系统集成。由于Seata的事务协调者TC是单独配置的,所以在使用Seata时需要先配置TC,然后再配置客户端RM 和 TM
TC环境配置
客户端RM 和 TM配置
TC(Server端)存储模式(store.mode)现有file、db、redis三种(后续将引入raft,mongodb)
file模式:无需改动,直接启动即可
db模式:高可用模式,全局事务会话信息通过db共享,相应性能差些
redis模式:Seata-Server 1.3及以上版本支持,性能较高,存在事务信息丢失风险,请提前配置合适当前场景的redis持久化配置.
三、Seata支持的事务模式
3.1、AT 模式
前提
-
基于支持本地 ACID 事务的关系型数据库。
-
Java 应用,通过 JDBC 访问数据库。
整体机制
两阶段提交协议的演变:
-
一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
-
二阶段:
-
提交异步化,非常快速地完成。
-
回滚通过一阶段的回滚日志进行反向补偿。
-
具体工作过程
再从宏观上梳理一下 Seata 的工作过程:
-
TM 请求 TC,开始一个新的全局事务,TC 会为这个全局事务生成一个 XID。
-
XID 通过微服务的调用链传递到其他微服务。
-
RM 把本地事务作为这个XID的分支事务注册到TC。
-
TM 请求 TC 对这个 XID 进行提交或回滚。
-
TC 指挥这个 XID 下面的所有分支事务进行提交、回滚。
3.2、TCC 模式
TCC核心思想
是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。
解决了协调者单点
,由主业务方发起并完成这个业务活动。业务活动管理器也变成多点,引入集群。
同步阻塞:
引入超时,超时后进行补偿,并且不会锁定整个资源,将资源转换为业务逻辑形式,粒度变小。
数据一致性
,有了补偿机制之后,由业务活动管理器控制一致性。
缺点
:在Confirm,Cancel中都有可能失败。TCC属于
应用层
的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC不太好定义及处理。
3.3、Saga 模式
Saga模式是SEATA提供的长事务解决方案,在Saga模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。
理论基础:Hector & Kenneth 发表论⽂ Sagas (1987)
适用场景:
-
业务流程长、业务流程多
-
参与者包含其它公司或遗留系统服务,无法提供 TCC 模式要求的三个接口
优势:
-
一阶段提交本地事务,无锁,高性能
-
事件驱动架构,参与者可异步执行,高吞吐
-
补偿服务易于实现
缺点:
-
不保证隔离性
参考链接: