事务(TRANSACTION)是一个不可分割的逻辑单元,包含了一组数据库操作命令,并且把所有的命令作为一个整体向系统提交,要么都执行、要么都不执行。
事务作为系统中必须考虑的问题,无论是在单体项目还是在分布式项目中都需要进行处理,而尤其在分布式微服务调用的情况下,事务的处理就变得复杂。
本篇博客介绍分布式事务产生的场景,阐述了CAP理论,BASE理论,以及由此衍生出来的XA协议,2PC、3PC提交模式,此外,还有分布式事务解决方案的介绍,基于MQ的最终一致性方案。
其他关于事务的博客文章如下:
1.介绍分布式事务产生的场景,阐述了CAP理论;
2. 分布式理论的CP -> 强一致性,刚性事务,遵循ACID,对数据要求强一致性;
3.分布式理论的AP+BASE -> 弱一致性,柔性事务,最终一致性;
4.基于 XA 协议实现的分布式事务:TM事务管理器(Transaction Manager)+ RM本地资源管理器(Resource Manager);
5.2PC二阶段提交协议:准备阶段(Preparephase)、提交阶段(commit phase);
6.3PC,全称 “three phase commit”,是 2PC 的改进版,将 2PC 的 “提交事务请求” 过程一分为二,共形成了由CanCommit、PreCommit和doCommit三个阶段组成的事务处理协议;
当系统的体量很小时,单体架构完全可以满足现有业务需求,所有的业务共用一个数据库,整个下单流程或许只用在一个方法里同一个事务下操作数据库即可。此时做到所有操作要么全部提交 或 要么全部回滚很容易。
可随着业务量的不断增长,单体架构渐渐扛不住巨大的流量,此时就需要对数据库、表做 分库分表
处理,将应用 SOA
服务化拆分。也就产生了订单中心、用户中心、库存中心等,由此带来的问题就是业务间相互隔离,每个业务都维护着自己的数据库,数据的交换只能进行 RPC
调用。
当用户再次下单时,需同时对订单库 order
、库存库 storage
、用户库 account
进行操作,可此时我们只能保证自己本地的数据一致性,无法保证调用其他服务的操作是否成功,所以为了保证整个下单流程的数据一致性,就需要分布式事务介入。
微服务架构下,单体应用被拆分成微服务应用,分别使用独立的数据源,业务操作需要调用多个服务来完成。此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证。
不同的数据落在不同的数据库中,原来一个数据库中的事务操作,现在变成了跨数据库的操作。此时@Transactional注解就失效了,这就是跨数据库分布式事务问题。
一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项。
因此,要么AP,要么CP,要么AC,但是不存在CAP。
而分布式系统中由于拆分成多个微服务部署在不同的网络分区,总是要有 P 的。分区容忍性又是不可或缺的。
BASE 理论
BASE 理论是对 CAP 中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。
Base与ACID对比:
ACID 理论是传统数据库常用的设计理念,追求强一致性模型。BASE 理论支持的是大型分布式系统,通过牺牲强一致性获得高可用性。BASE 理论在很大程度上,解决了事务型系统在性能、容错、可用性等方面痛点。BASE 理论在 NoSQL 中应用广泛,是 NoSQL 系统设计的事实上的理论支撑。
对于任何集群而言,不可预知的故障的最终后果,都是系统过载。如何设计过载保护,实现系统在过载时的基本可用,是开发和运营互联网后台的分布式系统的重中之重。
遵循BASE,允许一定时间内不同节点的数据不一致,但要求最终一致。
遵循ACID,对数据要求强一致性
遵循BASE,允许一定时间内不同节点的数据不一致,但要求最终一致。
XA是由X/Open组织提出的分布式事务的规范。
基于 XA 协议实现的分布式事务,是基于数据库层面的分布式事务,组成部分主要分为两部分:
TM事务管理器
作为一个全局的调度者,负责对各个本地资源管理器统一号令提交或者回滚。也就是说,在基于XA的一个事务中,我们可以针对多个资源进行事务管理,这样就能够实现在多个数据库统一提交、或全部操作。
本地资源管理器
往往由数据库实现,比如 Oracle、MYSQL 这些数据库都实现了 XA 接口
注意:XA规范不是java的规范,而是一种通用的规范。
2PC二阶段提交协议,是根据XA协议衍生出来而来,引入了事务协调者来统一管理事务资源。有两个阶段分别是准备阶段与提交阶段。
2PC ( Two-Phase Commit缩写)即两阶段提交协议,是将整个事务流程分为两个阶段,准备阶段(Preparephase)、提交阶段(commit phase),2是指两个阶段,P是指准备阶段,C是指提交阶段。
第一阶段:准备阶段(prepare)。协调者通知参与者准备提交订单,参与者完成准备工作向协调者回应。
事务协调器向三个库发起prepare后,这三个数据库收到消息分别执行本地事务并记录日志,但不提交,如果执行成功则回复yes,否则回复no。
第二阶段:当事务协调器收到全部回复yes,此时向参与者发起提交事务。如果参与者有一方提交事务失败则由事务协调器发起回滚事务。
事务协调器收到只要有一方回复no,则分别向参与者发起回滚事务,参与者开始回滚事务。
(1)、两阶段提交(2PC
),对业务侵⼊很小,它最⼤的优势就是对使⽤⽅透明,用户可以像使⽤本地事务⼀样使⽤基于 XA 协议的分布式事务,能够严格保障事务 ACID 特性。
(2)、2PC优缺点 。
2PC的优点:
2PC
),对业务侵⼊很小,用户可以像使⽤本地事务⼀样使⽤基于 XA 协议的分布式事务,能够严格保障事务 ACID 特性。2PC的缺点:
3PC,全称 “three phase commit”,是 2PC 的改进版,将 2PC 的 “提交事务请求” 过程一分为二,共形成了由CanCommit、PreCommit和doCommit三个阶段组成的事务处理协议。
3PC 保证了在最后提交阶段之前,各参与者节点的状态都一致。同时在协调者和参与者中都引入超时机制,2PC
中只有协调者有超时机制。
当参与者各种原因未收到协调者的commit请求后,会对本地事务进行commit,不会一直阻塞等待,解决了2PC的单点故障问题,但3PC还是没能从根本上解决数据一致性的问题。
虽然 3PC
用超时机制,解决了协调者故障后参与者的阻塞问题,但与此同时却多了一次网络通信,性能上反而变得更差。
刚性事务:分布式理论的CP,遵循ACID,对数据要求强一致性。
XA协议
是一个基于数据库层面的分布式事务协议,其分为两部分:
事务管理器(Transaction Manager)和本地资源管理器(Resource Manager)。事务管理器作为一个全局的调度者,负责对各个本地资源管理器统一号令提交或者回滚。主流的诸如Oracle、MySQL等数据库均已实现了XA接口。
Java事务规范
柔性事务:分布式理论的AP,遵循BASE,允许一定时间内不同节点的数据不一致,但要求最终一致。
TCC(Try-Confirm-Cancel)又被称补偿事务,TCC与2PC的思想很相似,事务处理流程也很相似,但2PC是应用于在DB层面,TCC则可以理解为在应用层面的2PC,是需要我们编写业务逻辑来实现。
TCC它的核心思想是:”针对每个操作都要注册一个与其对应的确认(Try)和补偿(Cancel)”。
还拿下单扣库存解释下它的三个操作:
TCC的缺点:
1、空回滚
TCC服务在未收到Try请求的情况下收到Cancel请求,这种场景被称为空回滚;
是否出现空回滚,我们需要需要判断是否执行了try方法,如果执行了就没有空回滚。解决方法就是当主业务发起事务时,生成一个全局事务记录,并生成一个全局唯一ID,贯穿整个事务,再创建一张分支事务记录表,用于记录分支事务,try执行时将全局事务ID和分支事务ID存入分支事务表中,表示执行了try阶段,当cancel执行时,先判断表中是否有该全局事务ID的数据,如果有则回滚,否则不做任何操作。比如seata的AT模式中就有分支事务表。
2.幂等问题
由于服务宕机或者网络问题,方法的调用可能出现超时,为了保证事务正常执行我们往往会加入重试的机制,因此就需要保证confirm和cancel阶段操作的幂等性。
我们可以在分支事务记录表中增加事务执行状态,每次执行confirm和cancel方法时都查询该事务的执行状态,以此判断事务的幂等性。
3.悬挂问题
TCC中,在调用try之前会先注册分支事务,注册分支事务之后,调用出现超时,此时try请求还未到达对应的服务,因为调用超时了,所以会执行cancel调用,此时cancel已经执行完了,然而这个时候try请求到达了,这个时候执行了try之后就没有后续的操作了,就会导致资源挂起,无法释放。
执行try方法时我们可以判断confirm或者cancel方法是否执行,如果执行了那么就不执行try阶段。同样借助分支事务表中事务的执行状态。如果已经执行了confirm或者cancel那么try就执行。
对于已经空回滚的业务,如果以后继续执行try,就永远不可能confirm或cancel,这就是业务悬挂。应当阻止执行空回滚后的try操作,避免悬挂。
上图中整体的处理步骤如下:
RabbitMQ,RocketMQ,Kafka
如果我们的系统不追求强一致性,那么最常用的还是最终一致性方案。这里可用选择基于 RabbitMQ
来实现消息最终一致性方案的分布式事务。
基于 MQ 的分布式事务方案其实是对本地消息表的封装,将本地消息表基于 MQ 内部,其他方面的协议基本与本地消息表一致。
从上图可以看出和本地消息表方案唯一不同就是将本地消息表存在了MQ内部,而不是业务数据库中。
在本地消息表方案中,保证事务主动方发写业务表数据和写消息表数据的一致性是基于数据库事务,RocketMQ 的事务消息相对于普通 MQ提供了 2PC 的提交接口,方案如下:
正常情况:事务主动方发消息
这种情况下,事务主动方服务正常,没有发生故障,发消息流程如下:
异常情况:事务主动方消息恢复
在断网或者应用重启等异常情况下,图中 4 提交的二次确认超时未到达 MQ Server,此时处理逻辑如下:
最大努力通知也称为定期校对,是对MQ事务方案的进一步优化。它在事务主动方增加了消息校对的接口,如果事务被动方没有接收到消息,此时可以调用事务主动方提供的消息校对的接口主动获取。
在可靠消息事务中,事务主动方需要将消息发送出去,并且消息接收方成功接收,这种可靠性发送是由事务主动方保证的;
但是最大努力通知,事务主动方尽最大努力(重试,轮询….)将事务发送给事务接收方,但是仍然存在消息接收不到,此时需要事务被动方主动调用事务主动方的消息校对接口查询业务消息并消费,这种通知的可靠性是由事务被动方保证的。
最大努力通知适用于业务通知类型,例如微信交易的结果,就是通过最大努力通知方式通知各个商户,既有回调通知,也有交易查询接口。
1.介绍分布式事务产生的场景,阐述了CAP理论;
2. 分布式理论的CP -> 强一致性,刚性事务,遵循ACID,对数据要求强一致性;
3.分布式理论的AP+BASE -> 弱一致性,柔性事务,最终一致性;
4.基于 XA 协议实现的分布式事务:TM事务管理器(Transaction Manager)+ RM本地资源管理器(Resource Manager);
5.2PC二阶段提交协议:准备阶段(Preparephase)、提交阶段(commit phase);
6.3PC,全称 “three phase commit”,是 2PC 的改进版,将 2PC 的 “提交事务请求” 过程一分为二,共形成了由CanCommit、PreCommit和doCommit三个阶段组成的事务处理协议;