醉鱼

V1

2022/06/14阅读:12主题:自定义主题1

分布式事务Get

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

  • 本地事务
  • 跨库事务
  • 微服务分布式事务
  • 2PC
  • XA
  • TCC
  • 本地事务
  • 消息事务
  • 最大努力通知

事务

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

  • 原子性(Atomicity)

    事务作为一个整体执行,包括其中对数据库的操作要么全部执行,要么全部不执行;因此事务的操作如果成功的话就必须全部应用到数据库层面,如果操作失败则不能对数据库中的数据有任何影响

  • 一致性(Consistency)

    一致性是指事务执行前到执行之后都处于一种一致性状态

  • 隔离性(Isolation)

    隔离性是指当有多个事务并发访问数据库时,多个事务之间的数据不能被其他事务所影响,多个并发事务之间要互相隔离

  • 持久性(Durability)

    持久性是指一个事务一旦提交了,那么对数据库的更改就是永久性的

单体应用事务

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

单体数据结构
单体数据结构

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

分库应用事务

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

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

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

分布式事务-微服务

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

两阶段提交

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

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

  • 提交请求(投票)阶段

    • 协调者向所有参与者发送prepare请求与事务内容,询问是否可以准备事务提交,并等待参与者的响应

    • 参与者执行事务中包含的操作,并记录Undo日志(用与回滚)和Redo日志(用于重放),但不是真正的提交(类似MySQL的两阶段提交)

    • 参与者向协调者返回事务操作的执行结果,执行成功返回yes,否则返回no

  • 提交(执行)阶段

    分析成功与失败的两种情况

    若所有参与者都返回yes,说明事务可以提交

    • 协调者向所有参与者发送commit提交请求
    • 参与者收到commit请求后,将事务真正的提交上去,并释放占用的事务资源,并向协调者返回ack
    • 协调者收到所有参与者的ack消息后,事务执行成功

    若有参与者返回no或者超时未返回,说明事务中断,需要回滚

    • 协调者向所有参与者发送rollback请求
    • 参与者收到rollback请求后,根据Undo日志回滚到事务执行前的状态,释放占用的事务资源,并向协调返回ack
    • 协调者收到所有参与者的ack消息,事务回滚完成

    下图分别示出这两种情况:

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

什么是XA

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

什么是两阶段

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

  • TM (事务管理器)

    Transaction Manager ,负责分配事务唯一标识,监控事务的执行进度,并负责事务的提交、回滚等

  • RM(资源管理器)

    Resource Manager 可以理解为就是数据库(MySQL/Oracle)并提供访问资源的方式

  • AP

    Application Program 定义事务边界(即定义事务的开始和结束),并且在事务边界内对资源进行操作

两阶段提交协议(2PC)

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

三阶段提交

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

下面我们再来看一下TCC

TCC

TCC就是Try Confirm Cancel

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

  • Try:是在业务逻辑阶段把数据操作更新到中间表并记录操作痕迹,也就是请求操作方预留资源或进行锁定

  • Confirm:是把所有中间步骤更新到原表操作,确认执行每个业务方的资源操作,也就是真正的业务执行操作,不做任何业务检查,只适用Try阶段预留的业务资源,Confirm操作要求具有幂等性,Confirm失败后需要进行重试

  • Cancel:是回滚,如果任何一个服务的业务方法执行出错,那么这里就需要进行补偿,即执行回滚操作,释放Try阶段预留的业务资源,Cancel操作也需要具备幂等设计,Calcel失败后需要进行重试

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

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

分类:

后端

标签:

后端

作者介绍

醉鱼
V1