前言
事务(TRANSACTION)是一个不可分割的逻辑单元,包含了一组数据库操作命令,并且把所有的命令作为一个整体向系统提交,要么都执行、要么都不执行。
事务作为系统中必须考虑的问题,无论是在单体项目还是在分布式项目中都需要进行处理,而尤其在分布式微服务调用的情况下,事务的处理就变得复杂。
本篇博客介绍分布式事务产生的场景,阐述了CAP理论,BASE理论,以及由此衍生出来的XA协议,2PC、3PC提交模式,此外,还有分布式事务解决方案的介绍,基于MQ的最终一致性方案。
其他关于事务的博客文章如下:
- MySQL进阶(事务)——转账事务的问题 COMMIT,ROLLBACK & 百万条数据插入的性能调优=3万次提交变成1次
- MySQL进阶(再论事务)——什么是事务 & 事务的隔离级别 & 结合MySQL案例详细分析
- 分布式事务——CAP理论 & 解决分布式事务的思路 & Seata组件初识 和 部署
- 分布式事务(Seata)——Seata分布式事务XA模式、AT模式、TCC模式的介绍和对比 & 结合案例分析AT模式和XA模式【源码】
目录
- 前言
- 引出
- 一、分布式事务场景
- 二、分布式事务理论基础
- 1、CAP理论
- 2、分布式事务类型
- 3、BASE理论
- 4、XA 协议,强一致性
- XA 协议
- 2PC 两阶段提交
- 3PC 三阶段提交
- 5、Java事务规范
- 6、分布式事务解决方案
- 三、Java分布式事务解决方案
- 概述
- TCC 事务补偿机制
- 本地消息表
- 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注解就失效了,这就是跨数据库分布式事务问题。
二、分布式事务理论基础
1、CAP理论
一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项。
- Consistency(一致性):在分布式系统中的所有数据副本,在同一时刻是否同样的值。
- Availability(可用性):保证每个请求不管成功或者失败都有响应。
- Partition tolerance(分区容错性):系统中任意信息的丢失或失败不会影响系统的继续运作。
因此,要么AP,要么CP,要么AC,但是不存在CAP。
而分布式系统中由于拆分成多个微服务部署在不同的网络分区,总是要有 P 的。分区容忍性又是不可或缺的。
- CA 放弃分区容忍性,即不进行分区。比如就一个节点,不用考虑网络不通或其他节点挂掉的问题,那么系统将不是一个标准的分布式系统。
- CP 放弃可用性,追求一致性。比如银行系统中跨行转账,可以不可用,但不能有错账。
- AP 放弃一致性,追求可用性。这是很多分布式系统设计时的选择,通常实现 AP 都会保证最终一致性。
- 强一致性是指数据在一段时间内严格一致。
- 最终一致性是允许可以在一段时间内每个结点的数据不一致,但是经过一段时间每个结点的数据必须一致,它强调的是最终数据的一致性。
2、分布式事务类型
- 强一致性:是指在事务没有完成前,所有其他的访问事务中的资源都处于阻塞状态,一直到事务完成后。后续的操作只会读取带更新后的资源。
- 弱一致性:在两个操作进行时,一个操作要读取另一个操作的资源,可能会读取到更新前的资源也可能读取到更新后的资源。
- 最终一致性:在这种情况下,事务不会进入阻塞状态,在另一个请求读取这种事务的时候,只会读取到更新后的资源,因为这种会执行预执行,一旦出错,会进行逆操作回到执行前的状态,但最终一定是一致的。
3、BASE理论
BASE 理论
BASE 理论是对 CAP 中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。
- 基本可用(Basically Available): 基本可用是指分布式系统在出现故障的时候,允许损失部分可用性,即保证核心可用。电商大促时,为了应对访问量激增,部分用户可能会被引导到降级页面,服务层也可能只提供降级服务。这就是损失部分可用性的体现。
- 软状态(Soft State): 允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延时。允许不同节点间副本同步的延时就是软状态的体现。
- 最终一致性(Eventual Consistency): 最终一致性是指系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。弱一致性和强一致性相反,最终一致性是弱一致性的一种特殊情况。
Base与ACID对比:
ACID 理论是传统数据库常用的设计理念,追求强一致性模型。BASE 理论支持的是大型分布式系统,通过牺牲强一致性获得高可用性。BASE 理论在很大程度上,解决了事务型系统在性能、容错、可用性等方面痛点。BASE 理论在 NoSQL 中应用广泛,是 NoSQL 系统设计的事实上的理论支撑。
对于任何集群而言,不可预知的故障的最终后果,都是系统过载。如何设计过载保护,实现系统在过载时的基本可用,是开发和运营互联网后台的分布式系统的重中之重。
遵循BASE,允许一定时间内不同节点的数据不一致,但要求最终一致。
- 分布式理论的CP -> 强一致性,刚性事务
遵循ACID,对数据要求强一致性
- 分布式理论的AP+BASE -> 弱一致性,柔性事务
遵循BASE,允许一定时间内不同节点的数据不一致,但要求最终一致。
4、XA 协议,强一致性
XA 协议
XA是由X/Open组织提出的分布式事务的规范。
基于 XA 协议实现的分布式事务,是基于数据库层面的分布式事务,组成部分主要分为两部分:
- TM事务管理器(Transaction Manager)
- RM本地资源管理器(Resource Manager)。
TM事务管理器
作为一个全局的调度者,负责对各个本地资源管理器统一号令提交或者回滚。也就是说,在基于XA的一个事务中,我们可以针对多个资源进行事务管理,这样就能够实现在多个数据库统一提交、或全部操作。
本地资源管理器
往往由数据库实现,比如 Oracle、MYSQL 这些数据库都实现了 XA 接口
注意:XA规范不是java的规范,而是一种通用的规范。
2PC 两阶段提交
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的优点:
- 实现强一致性,部分关系数据库支持(Oracle、MySQL等)。
- 两阶段提交(
2PC
),对业务侵⼊很小,用户可以像使⽤本地事务⼀样使⽤基于 XA 协议的分布式事务,能够严格保障事务 ACID 特性。
2PC的缺点:
- 1、整个事务的执行需要由协调者在多个节点之间去协调,增加了事务的执行时间,性能低下。因此,在⾼并发性能⾄上的场景中,基于 XA 协议的分布式事务并不是最佳选择。
- 2、一旦事务协调者宕机或者发生网络抖动,会让参与者一直处于锁定资源的状态或者只有一部分参与者提交成功,导致数据的不一致
- 3、2PC它是一个强一致性的同步阻塞协议,事务执⾏过程中需要将所需资源全部锁定。所以它比较适⽤于执⾏时间确定的短事务,整体性能比较相对会差。
3PC 三阶段提交
3PC,全称 “three phase commit”,是 2PC 的改进版,将 2PC 的 “提交事务请求” 过程一分为二,共形成了由CanCommit、PreCommit和doCommit三个阶段组成的事务处理协议。
- CanCommit:协调者向所有参与者发送CanCommit命令,询问是否可以执行事务提交操作。如果全部响应YES则进入下一个阶段。
- PreCommit:协调者向所有参与者发送PreCommit命令,询问是否可以进行事务的预提交操作,参与者接收到PreCommit请求后,如参与者成功的执行了事务操作,则返回Yes响应,进入最终commit阶段。一旦参与者中有向协调者发送了No响应,或因网络造成超时,协调者没有接到参与者的响应,协调者向所有参与者发送abort请求,参与者接受abort命令执行事务的中断。
- DoCommit:在前两个阶段中所有参与者的响应反馈均是YES后,协调者向参与者发送DoCommit命令正式提交事务,如协调者没有接收到参与者发送的ACK响应,会向所有参与者发送abort请求命令,执行事务的中断。
3PC 保证了在最后提交阶段之前,各参与者节点的状态都一致。同时在协调者和参与者中都引入超时机制,2PC
中只有协调者有超时机制。
当参与者各种原因未收到协调者的commit请求后,会对本地事务进行commit,不会一直阻塞等待,解决了2PC的单点故障问题,但3PC还是没能从根本上解决数据一致性的问题。
虽然 3PC
用超时机制,解决了协调者故障后参与者的阻塞问题,但与此同时却多了一次网络通信,性能上反而变得更差。
5、Java事务规范
- JTA:Java事务API(Java Transaction API)是一个Java企业版的应用程序接口,在Java环境中,允许完成跨越多个XA资源的分布式事务。
- JTS:Java事务服务(Java Transaction Service)是J2EE平台提供了分布式事务服务的具体实现规范,J2EE服务器提供商根据JTS规范实现事务并提供JTA接口。
6、分布式事务解决方案
三、Java分布式事务解决方案
概述
刚性事务:分布式理论的CP,遵循ACID,对数据要求强一致性。
-
XA协议
是一个基于数据库层面的分布式事务协议,其分为两部分:
事务管理器(Transaction Manager)和本地资源管理器(Resource Manager)。事务管理器作为一个全局的调度者,负责对各个本地资源管理器统一号令提交或者回滚。主流的诸如Oracle、MySQL等数据库均已实现了XA接口。
- 二阶提交协议(2PC): 根据XA协议衍生出来而来; 引入一个作为协调者的组件来统一掌控所有参与者的操作结果并最终指示这些节点是否要把操作结果进行真正的提交; 参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。所谓的两个阶段是指:第一阶段:准备阶段 (投票阶段) 和第二阶段:提交阶段(执行阶段)
- 三阶提交协议(3PC): 是对两段提交(2PC)的一种升级优化,3PC在2PC的第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前,各参与者节点的状态都一致。同时在协调者和参与者中都引入超时机制,当参与者各种原因未收到协调者的commit请求后,会对本地事务进行commit,不会一直阻塞等待,解决了2PC的单点故障问题,但3PC还是没能从根本上解决数据一致性的问题。
-
Java事务规范
- JTA:Java事务API(Java Transaction API)是一个Java企业版的应用程序接口,在Java环境中,允许完成跨越多个XA资源的分布式事务。
- JTS:Java事务服务(Java Transaction Service)是J2EE平台提供了分布式事务服务的具体实现规范,j2ee服务器提供商根据JTS规范实现事务并提供JTA接口。
柔性事务:分布式理论的AP,遵循BASE,允许一定时间内不同节点的数据不一致,但要求最终一致。
- 基于业务层
- TCC: TCC(Try-Confirm-Cancel)又被称补偿事务,TCC与2PC的思想很相似,事务处理流程也很相似,但2PC是应用于在DB层面,TCC则可以理解为在应用层面的2PC,是需要我们编写业务逻辑来实现。
- SAGA:Saga是由一系列的本地事务构成。每一个本地事务在更新完数据库之后,会发布一条消息或者一个事件来触发Saga中的下一个本地事务的执行。如果一个本地事务因为某些业务规则无法满足而失败,Saga会执行在这个失败的事务之前成功提交的所有事务的补偿操作。Saga的实现有很多种方式,其中最流行的两种方式是:基于事件的方式和基于命令的方式。
- 最终一致性
- 消息表:本地消息表的方案最初是由 eBay 提出,核心思路是将分布式事务拆分成本地事务进行处理。
- 消息队列:基于 MQ 的分布式事务方案其实是对本地消息表的封装,将本地消息表基于 MQ 内部,其他方面的协议基本与本地消息表一致。
- 最大努力通知:最大努力通知也称为定期校对,是对MQ事务方案的进一步优化。它在事务主动方增加了消息校对的接口,如果事务被动方没有接收到消息,此时可以调用事务主动方提供的消息校对的接口主动获取。
TCC 事务补偿机制
TCC(Try-Confirm-Cancel)又被称补偿事务,TCC与2PC的思想很相似,事务处理流程也很相似,但2PC是应用于在DB层面,TCC则可以理解为在应用层面的2PC,是需要我们编写业务逻辑来实现。
TCC它的核心思想是:”针对每个操作都要注册一个与其对应的确认(Try)和补偿(Cancel)”。
还拿下单扣库存解释下它的三个操作:
- Try阶段:阶段是做业务检查(一致性)及资源预留(隔离),此阶段仅是一个初步操作,它和后续的Confirm 一起才能真正构成一个完整的业务逻辑。
- Confirm阶段:确认提交,Try阶段所有分支事务执行成功后开始执行 Confirm。通常情况下,采用TCC则
认为 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。若Confirm阶段真的出错了,需引
入重试机制或人工处理。 - 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操作,避免悬挂。
本地消息表
上图中整体的处理步骤如下:
- 事务主动方在同一个本地事务中处理业务和写消息表操作
- 事务主动方通过消息中间件,通知事务被动方处理事务通知事务待消息。消息中间件可以基于 Kafka、RocketMQ 消息队列,事务主动方主动写消息到消息队列,事务消费方消费并处理消息队列中的消息。
- 事务被动方通过消息中间件,通知事务主动方事务已处理的消息。
- 事务主动方接收中间件的消息,更新消息表的状态为已处理。
MQ事务消息
RabbitMQ,RocketMQ,Kafka
如果我们的系统不追求强一致性,那么最常用的还是最终一致性方案。这里可用选择基于 RabbitMQ
来实现消息最终一致性方案的分布式事务。
基于 MQ 的分布式事务方案其实是对本地消息表的封装,将本地消息表基于 MQ 内部,其他方面的协议基本与本地消息表一致。
从上图可以看出和本地消息表方案唯一不同就是将本地消息表存在了MQ内部,而不是业务数据库中。
在本地消息表方案中,保证事务主动方发写业务表数据和写消息表数据的一致性是基于数据库事务,RocketMQ 的事务消息相对于普通 MQ提供了 2PC 的提交接口,方案如下:
正常情况:事务主动方发消息
这种情况下,事务主动方服务正常,没有发生故障,发消息流程如下:
- 发送方向 MQ 服务端(MQ Server)发送 half 消息。
- MQ Server 将消息持久化成功之后,向发送方 ack 确认消息已经发送成功。
- 发送方开始执行本地事务逻辑。
- 发送方根据本地事务执行结果向 MQ Server 提交二次确认(commit 或是 rollback)。
- MQ Server 收到 commit 状态则将半消息标记为可投递,订阅方最终将收到该消息;MQ Server 收到 rollback 状态则删除半消息,订阅方将不会接受该消息。
异常情况:事务主动方消息恢复
在断网或者应用重启等异常情况下,图中 4 提交的二次确认超时未到达 MQ Server,此时处理逻辑如下:
- MQ Server 对该消息发起消息回查。
- 发送方收到消息回查后,需要检查对应消息的本地事务执行的最终结果。
- 发送方根据检查得到的本地事务的最终状态再次提交二次确认。
- MQ Server基于 commit/rollback 对消息进行投递或者删除。
最大努力通知
最大努力通知也称为定期校对,是对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三个阶段组成的事务处理协议;