查看原文
其他

如何解决分布式事务

TomGE 微观技术 2022-03-15

事务有四个特性:原子性、一致性、隔离性、持久性。这四个属性通常称为 ACID

随着分布式架构理念提出,软件系统架构开始迈入一个新时代。一个臃肿的应用会拆分出若干个微服务中心,按业务域维度划分系统边界,大家各司其职,在自己负责的领域深耕细作,可谓好处多多。但同时也增加了系统复杂度,每个子业务系统都涉及数据库操作,如何解决分布式事务是一个绕不开的话题。

什么是分布式事务,一句话概括:分布式事务就是用来保证多个原子服务数据源一致性的解决方案。

常见解决方案

1、流水任务

执行业务逻辑前,先插入流水任务,如果中间过程调用外部RPC接口服务或者本地数据库操作失败时,流水任务会被定时调度任务周期性触发、重试,直到成功。前提条件,所有接口服务都要实现幂等。当执行成功时,流水记录会被删除。

当然为了缩小接口的重试范围,也可以针对局部调用失败引入局部重试流水。

优点:

•实现简单,不依赖任何外部框架

缺点:

•不支持回滚,只能不断重试直到接口成功。如果中间某一步操作因数据问题无法成功,只能重试若干次后报警人工介入。•无论全局重试、还是片段重试,都要单独处理,复杂度高

2、基于事务消息

在淘宝平台中,广泛使用分布式事务场景的方案是基于消息分布式事务,通过MQ事务消息功能特性达到分布式事务的最终一致性。

•MQ发送方执行第一个本地事务前,会向MQ服务端发送一条消息,但这条消息不同于普通MQ消息,而是一条事务消息。事务消息在MQ的服务端处于一个特殊的状态,此时消息已经保存到MQ服务端,但MQ订阅方是无法感知到该条消息,并且不会进行消费。•完成事务消息的发送后,开始执行本地的数据库事务操作,并根据执行结果走提交或回滚•如果本地事务执行后,因为某些原因没有及时给MQ服务端相应的反馈,MQ服务端会向业务处理服务询问消息状态,业务处理服务根据消息ID或者消息内容确认该消息是否有效。

•如果发现本地事务没有执行,则给MQ服务端返回结果,告知MQ服务端可废弃该事务消息。•如果检查发现本地事务已经实际已经成功执行了,则MQ服务端的消息为正常状态。

     •消息订阅方获取到正常消息后,执行第二个本地事务。如果第二个本地事务执行成功,则最终实现两个不同数据库上的事务同时成功。如果失败,借助MQ框架自身的重试机制,多次重试,实现数据的最终一致性。


3、两阶段提交

•准备阶段

        •协调者向所有的参与者发送事务执行请求,并等待参与者反馈事务执行结果。        •事务参与者收到请求之后,本地执行事务,但不提交。        •参与者将自己事务执行情况反馈给协调者,同时等待协调者的下一步通知。

    •提交阶段

      •根据准备阶段的结果,执行commit或rollback


缺点:

•第一步的数据库事务要等待第二阶段的反馈才能提交,事务锁占用时间较长,会拉低系统的吞吐量。

淘宝很多业务线有一种变种玩法,也是基于两阶原理,但是耦合性做了弱化。

•引入状态机。分为:初始化、上架、失效、删除 等多种状态。当然结合具体业务场景,可能还会有更多业务状态。•接口服务拆成两部分:

            •首次调用,数据库中插入业务数据,但行记录状态标记为 init(初始化状态对外不可见)

     •待所有依赖的RPC接口全部调用一轮,所有接口的数据全部初始化     •然后开始第二轮调用,将状态置为对外可见。当然,此阶段可能会部分调用失败,需要多次重试


如果一个业务逻辑内部涉及多次RPC调用以及本地数据库事务,如何保证数据的全局统一性?还有一种解决方案!

•表结构增加一个字段,引入目标状态。在调用链路的最上层先给目标状态赋值•然后依次调用外部RPC接口,以及更新本地数据库•最后修改本地记录状态的值,并将目标状态值清空

4、三阶段提交

第一阶段:CanCommit

协调者向参与者发送事务执行请求CanCommit,参与者如果可以提交就返回YES响应,否则就返回NO响应。

第二阶段:PreCommit

协调者根据参与者反馈的结果来决定是否继续执行事务的PreCommit操作,根据协调者反馈的结果,有以下两种可能:

1、假如协调者收到参与者的反馈结果都是YES,那么就会执行PreCommit操作。

•协调者向参与者发送PreCommit请求,并进入Prepared阶段。•参与者接收到PreCommit请求后,执行事务操作,但不提交•事务操作执行成功,则返回ACK响应,然后等待协调者的下一步通知。

2、假如有任何一个参与者向协调者发送了NO响应,或者等待超时之后,协调者没有收到参与者的响应,那么就中断事务。

•协调者向所有参与者发送中断请求。•参与者收到中断请求之后(或超时之后,仍未收到协调者的请求),执行事务中断操作。

第三阶段:DoCommit

1、执行提交

•协调者收到ACK之后,向所有的参与者发送DoCommit请求。•参与者收到DoCommit请求之后,提交事务。•事务提交之后,向协调者发送ACK响应。•协调者收到ACK响应之后,完成事务。

2、中断事务

•在第二阶段,协调者没有收到参与者发送的ACK响应,那么就会执行中断事务。

5、TCC 模式

TCC方案分为Try Confirm Cancel三个阶段,属于补偿性分布式事务。

Try:尝试待执行的业务

这个过程并未执行业务,只是完成所有业务的一致性检查,并预留好执行所需的全部资源

Confirm:执行业务

这个过程真正开始执行业务,由于Try阶段已经完成了一致性检查,因此本过程直接执行,而不做任何检查。并且在执行的过程中,会使用到Try阶段预留的业务资源。

Cancel:取消执行的业务

若业务执行失败,则进入Cancel阶段,它会释放所有占用的业务资源,并回滚Confirm阶段执行的操作。

以一个电商系统用户购买商品的流水线为例。

Try阶段:

在Try阶段成功之后进入Confirm阶段,如有任何异常,进入Cancel阶段。


Confirm阶段:

Cancel阶段:

假设库存扣减失败,此时需要回滚事务

TCC方案适用于一致性要求极高的系统,比如金钱、交易相关,基于补偿的原理,因此,需要编写大量的补偿代码,比较冗余。市面可以参考的开源TCC框架,比如TCC-transaction。

6、GTS(开源项目名:Seata)

地址:https://github.com/seata/seata

GTS 把分布式事务定义为由若干本地事务(分支)组成的全局事务。被全局事务管理的全部分支,将在协调器的协调下,保证一起成功或一起回滚。

GTS 定义了一个事务模型,把整个全局事务过程模型化为 TM、RM、TC 三个组件之间协作的机制。

•Transaction Coordinator (TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。•Transaction Manager (TM):控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。•Resource Manager (RM):控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。

执行过程:

•TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID。•XID 在微服务调用链路的上下文中传播。•RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖。•TM 向 TC 发起针对 XID 的全局提交或回滚决议。•TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。

GTS 的 JDBC 数据源代理通过对业务 SQL 的解析,把业务数据在更新前的数据镜像组织成回滚undo日志,执行SQL,并得到redo日志,利用 本地事务 的 ACID 特性,将业务数据的更新和回滚日志的写入在同一个 本地事务 中提交。这样可以保证:任何提交的业务数据的更新一定有相应的回滚日志存在。

参考文章: 分布式事务 GTS 的价值和原理浅析


END


我们热衷于收集&分享高并发、系统架构、微服务、消息中间件、 RPC框架、高性能缓存、搜索、分布式数据框架、分布式协同服务、分布式配置中心、中台架构、领域驱动设计、系统监控、系统稳定性等技术知识。

关注公众号,后台回复“中台,下载PDF学习资料


关注【微观技术】

做互联网技术架构牛人



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存