分布式事务

2022-06-12  本文已影响0人  醉鱼java

事务是工作中常用的,想必大家也都知道,单体应用中的事务也称为本地事务,但是大家知道除了本地事务,分布式事务了解吗?通过阅读下面这篇文章,可以知道什么是分布式事务?为什么分布式事务不能百分百的解决事务的问题?接下来我从以下几个方面入手来分析一波事务

事务

下面先说一下什么是事务,事务(Transaction)简单的说就是更新数据库中各种数据项的一个程序执行单元,常说的事务有四大特性,分别是:

单体应用事务

单体应用对应的就是如下图所示的数据结构

单体数据结构

这种结构在事务开发中,如果发生了异常,直接使用回滚就可以回滚,因为全部都在同一个事务管理器中

分库应用事务

对于上面这种分库应用,这种情况下如果还使用刚才的事务管理是无法做到的,有可能会有这种情况,数据库A的操作成功,数据库B操作失败了,此时需要回滚,但是它俩不是同一个事务管理器管理的,所以无法发生回滚,也就无法保持事务的特性了。

此时如果发生了异常,也只有通过日志进行回滚数据或者人工介入

上面两种情况我们呢已经了解了,有一个点不知道大家发现没有,不管是单体应用还是分库的应用,都是部署在同一个机器上的,最多就是一个跨库事务,下面我们再来看一下跨机器与跨数据库的事务是怎么处理的

分布式事务-微服务

通过上面的图不难看出,如果再做事务管理,不光要跨库,还要跨服务,所以此时再实现事务管理是非常难的,所以有没有什么方式可以实现分库的事务管理呢,有,那就是两阶段提交

两阶段提交

两阶段提交协议(2PC),在说两阶段提交一些前,先说两个概念,在分布式系统中,为了让每个节点都能够感知到其它节点的事务执行情况,需要引入一个中心节点来统一处理所有节点的执行逻辑,这个中心节点叫做协调者(coordinator),被中心节点调度的其它业务节点叫做参与者(participant)

接下来正式介绍一下2PC,顾名思义,2PC就是将分布式事务分成两个阶段,两个阶段分别是提交请求(投票)和提交(执行)。协调者根据参与者的响应来决定是否需要真正的执行事务,具体流程如下:

知道了两阶段提交,下面我们在看下XA与两阶段提交的关系

什么是XA

XA定义了两阶段提交协议中使用到的接口。引用百度百科的定义:XA协议由Tuxedo首先提出的,并交给X/Open组织,作为资源管理器(数据库)与事务管理器的接口标准,Oracle、Informix、DB2和Sybase等各大数据库厂家都提供对XA的支持。XA协议采用两阶段提交方式来管理分布式事务。XA事务提供资源管理器与事务管理器之间进行通信的标准接口。XA协议包括两套函数,以xa_开头的及ax_开头的。

什么是两阶段

两阶段是事务管理器(TM)和资源管理器(RM)形成的预提交和提交两个阶段。

两阶段提交协议(2PC)

上面呢,我们了解了普通事务与分库事务以及两阶段提交协议,而我们的分布式事务就是通过两阶段提交协议控制的。除了两阶段提交还有三阶段提交,以及刚性事务和柔性事务,下面将一一展开

三阶段提交

三阶段提交是两阶段提交的改进版,将两阶段提交协议的阶段一,即提交事务请求(投票)阶段分为两个阶段,形成CanCommit、PreCommit和doCommit三个阶段组成的事务处理协议

下面我们再来看一下TCC

TCC

TCC就是Try Confirm Cancel

两阶段提交(2PC)和三阶段提交(3PC)并不适用与并发量大的业务场景。TCC事务机制相对于2PC,3PC,不会锁定整个资源,而是通过引入补偿机制,将资源转换为业务逻辑形式,锁力度变小。所以TCC的核心思想就是针对每一个操作,都要注册一个与其对应的确认和补偿操作接口,下面分为三个阶段

TCC与XA两阶段提交有什么区别

1、XA是资源(数据库的分布式事务),强一致性,在整个过程中,数据一直锁住状态;即从prepare到commit、rollback的整个过程中,TM一直持有数据库的锁,如果有其他线程要修改数据库的该条数据,就必须等待锁的释放(简单来说就是长事务风险)

开发过程中,也没用代码的入侵(可以参考Atomikos的实现)

2、TCC是业务的分布式事务,是最终一致性,不会出现长事务的锁风险

需要开发人员开发三个接口(try,confirm,cancel)

通过上面的描述,我们已经了解到了TCC与XA事务的区别,也就是强一致性和最终一致性,他们还有个名称就是刚性事务和柔性事务,那么什么是刚性事务?什么是柔性事务呢?

刚性事务与柔性事务

分布式事务实现方案从类型上分刚性事务和柔性事务

刚性事务:XA协议(2PC,JTA,JTS) 、3PC

柔性事务:TCC/FMT、Saga(状态机模式,Aop模式)、本地事务消息、消息事务(半消息)

本地消息表

本地消息表其实就是利用了 各系统本地的事务来实现分布式事务。

本地消息表顾名思义就是会有一张存放本地消息的表,一般都是放在数据库中,然后在执行业务的时候 将业务的执行和将消息放入消息表中的操作放在同一个事务中,这样就能保证消息放入本地表中业务肯定是执行成功的。

然后再去调用下一个操作,如果下一个操作调用成功了好说,消息表的消息状态可以直接改成已成功。

如果调用失败也没事,会有 后台任务定时去读取本地消息表,筛选出还未成功的消息再调用对应的服务,服务更新成功了再变更消息的状态。

这时候有可能消息对应的操作不成功,因此也需要重试,重试就得保证对应服务的方法是幂等的,而且一般重试会有最大次数,超过最大次数可以记录下报警让人工处理。

可以看到本地消息表其实实现的是最终一致性,容忍了数据暂时不一致的情况。

消息事务

RocketMQ 就很好的支持了消息事务,让我们来看一下如何通过消息实现事务。

第一步先给 Broker 发送事务消息即半消息,半消息不是说一半消息,而是这个消息对消费者来说不可见,然后发送成功后发送方再执行本地事务

再根据本地事务的结果向 Broker 发送 Commit 或者 RollBack 命令

并且 RocketMQ 的发送方会提供一个反查事务状态接口,如果一段时间内半消息没有收到任何操作请求,那么 Broker 会通过反查接口得知发送方事务是否执行成功,然后执行 Commit 或者 RollBack 命令。

如果是 Commit 那么订阅方就能收到这条消息,然后再做对应的操作,做完了之后再消费这条消息即可。

如果是 RollBack 那么订阅方收不到这条消息,等于事务就没执行过。

可以看到通过 RocketMQ 还是比较容易实现的,RocketMQ 提供了事务消息的功能,我们只需要定义好事务反查接口即可。

可以看到消息实现的也是最终一致性

最大努力通知

其实我觉得本地消息表也可以算最大努力,事务消息也可以算最大努力。

就本地消息表来说会有后台任务定时去查看未完成的消息,然后去调用对应的服务,当一个消息多次调用都失败的时候可以记录下然后引入人工,或者直接舍弃。这其实算是最大努力了。

事务消息也是一样,当半消息被commit了之后确实就是普通消息了,如果订阅者一直不消费或者消费不了则会一直重试,到最后进入死信队列。其实这也算最大努力。

所以最大努力通知其实只是表明了一种柔性事务的思想:我已经尽力我最大的努力想达成事务的最终一致了。

适用于对时间不敏感的业务,例如短信通知。

总结

可以看出 2PC 和 3PC 是一种强一致性事务,不过还是有数据不一致,阻塞等风险,而且只能用在数据库层面。

而 TCC 是一种补偿性事务思想,适用的范围更广,在业务层面实现,因此对业务的侵入性较大,每一个操作都需要实现对应的三个方法。

本地消息、事务消息和最大努力通知其实都是最终一致性事务,因此适用于一些对时间不敏感的业务。

本文由mdnice多平台发布

上一篇 下一篇

猜你喜欢

热点阅读