事务是工作中常用的,想必大家也都知道,单体应用中的事务也称为本地事务,但是大家知道除了本地事务,分布式事务了解吗?通过阅读下面这篇文章,可以知道什么是分布式事务?为什么分布式事务不能百分百的解决事务的问题?接下来我从以下几个方面入手来分析一波事务
- 本地事务
- 跨库事务
- 微服务分布式事务
- 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 是一种补偿性事务思想,适用的范围更广,在业务层面实现,因此对业务的侵入性较大,每一个操作都需要实现对应的三个方法。
本地消息、事务消息和最大努力通知其实都是最终一致性事务,因此适用于一些对时间不敏感的业务。
本文由 mdnice 多平台发布