分布式事务

分布式事务

分布式事务知识基础

分布式事务概念

1)本地事务: 本地事务就是用关系数据库来控制事务,关系数据库通常都具有ACID特性,传统的单体应用通常会将数据全部存储在一个数据库中,会借助关系数据库来完成事务控制。 事务提供一种机制将一个活动涉及的所有操作纳入到一个不可分割的执行单元,组成事务的所有操作只有在所有操作均能正常执行的情况下方能提交,只要其中任一操作执行失败,都将导致整个事务的回滚。简单地说,事务提供一种要么什么都不做,要么做全套机制。

2)分布式系统:部署在不同结点上的系统通过网络交互来完成协同工作的系统。比如:充值加积分的业务,用户在充值系统向自己的账户充钱,在积分系统中自己积分相应的增加。充值系统和积分系统是两个不同的系统,一次充值加积分的业务就需要这两个系统协同工作来完成。

3)分布式事务:在分布式系统中一次操作由多个系统协同完成,这种一次事务操作涉及多个系统通过网络协同完成的过程称为分布式事务。这里强调的是多个系统通过网络协同完成一个事务的过程,并不强调多个系统访问了不同的数据库,即使多个系统访问的是同一个数据库也是分布式事务,如下图:

1596984307905

另外一种分布式事务的表现是,一个应用程序使用了多个数据源连接了不同的数据库,当一次事务需要操作多个数据源,此时也属于分布式事务,当系统作了数据库拆分后会出现此种情况。

1596984358275

分布式事务场景

1)电商系统中的下单扣库存:电商系统中,订单系统和库存系统是两个系统,一次下单的操作由两个系统协同完成。

2)金融系统中的银行卡充值:在金融系统中通过银行卡向平台充值需要通过银行系统和金融系统协同完成。

3)教育系统中下单选课业务:在线教育系统中,用户购买课程,下单支付成功后学生选课成功,此事务由订单系统和选课系统协同完成。

4)SNS系统的消息发送:在社交系统中发送站内消息同时发送手机短信,一次消息发送由站内消息系统和手机通信系统协同完成。

分布式事务问题

订单服务:用户支付完成会将支付状态和订单状态在订单数据库中进行更新。

学习服务:订单更新完成后,会远程调用选课接口在学习中心数据库进行选课更新。

1596983533725

如果更新支付表失败则抛出异常,不再执行远程调用选课服务,此设想没有问题。如果更新支付表成功,网络远程调用超时会拉长订单数据库事务时间,影响数据库性能,异常回滚也没有问题。如果更新支付表成功,远程调用成功,但是更新选课表失败,此时选课回滚,而订单已经更新不会回滚,这时就会出现操作不一致的情况。

CAP理论知识

CAP理论:一致性(Consistency)、可用性(Availability)、分区容忍性(Partition Tolerance)

img

一致性:服务A、B、C三个结点都存储了用户数据, 三个结点的数据需要保持同一时刻数据一致性。

可用性:服务A、B、C三个结点,其中一个结点宕机不影响整个集群对外提供服务,如果只有服务A结点,当服务A宕机整个系统将无法提供服务,增加服务B、C是为了保证系统的可用性。

分区容忍性:分区容忍性就是允许系统通过网络协同工作,分区容忍性要解决由于网络分区导致数据的不完整及无法访问等问题。分布式系统不可避免的出现了多个系统通过网络协同工作的场景,结点之间难免会出现网络中断、网延延迟等现象,这种现象一旦出现就导致数据被分散在不同的结点上,这就是网络分区。一般来说,分布式系统是分布在多个位置的。比如我们的一台服务器在北京,一台在上海。可能由于天气等原因的影响。造成了两条服务器直接不能互相通信,数据不能进行同步,这就是分区容错。我们认为,分区容错是不可避免的。也就是说P是必然存在的。

CAP组合方式

在保证分区容忍性的前提下一致性和可用性无法兼顾,如果要提高系统的可用性就要增加多个结点,如果要保证数据的一致性就要实现每个结点的数据一致,结点越多可用性越好,但是数据一致性越差。所以,在进行分布式系统设计时,同时满足一致性、可用性和分区容忍性三者是几乎不可能的。

1、CA:放弃分区容忍性,加强一致性和可用性,关系数据库按照CA进行设计,即一个系统连接多个数据库。
2、AP:放弃一致性,加强可用性和分区容忍性,追求最终一致性,很多NoSQL数据库按照AP进行设计。说明:这里放弃一致性是指放弃强一致性,强一致性就是写入成功立刻要查询出最新数据。追求最终一致性是指允许暂时的数据不一致,只要最终在用户接受的时间内数据一致即可。
3、CP:放弃可用性,加强一致性和分区容忍性,一些强一致性要求的系统按CP进行设计,比如跨行转账,一次转账请求要等待双方银行系统都完成整个事务才算完成。说明:由于网络问题的存在CP系统可能会出现待等待超时,如果没有处理超时问题则整理系统会出现阻塞。

在分布式系统设计中AP的应用较多,即保证分区容忍性和可用性,牺牲数据的强一致性(写操作后立刻读取到最新数据),保证数据最终一致性。比如:订单退款,今日退款成功,明日账户到账,只要在预定的用户可以接受的时间内退款事务走完即可。

BASE理论知识

BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的缩写,是对CAP中AP的一个扩展。

1、基本可用(BA):分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。
2、软状态(S):允许系统中存在中间状态,这个状态不影响系统可用性,这里指的是CAP中的不一致。
3、最终一致(E):最终一致是指经过一段时间后,所有节点数据都将会达到一致。

BASE解决了CAP中理论没有网络延迟,在BASE中用软状态和最终一致,保证了延迟后的一致性。BASE和ACID是相反的,它完全不同于ACID的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。

分布式事务解决方案

image-20230323103049704

刚性事务:分布式理论的CP,遵循ACID,对数据要求强一致性。

1)XA协议是一个基于数据库的分布式事务协议,其分为两部分:事务管理器(Transaction Manager)和本地资源管理器(Resource Manager)。事务管理器作为一个全局的调度者,负责对各个本地资源管理器统一号令提交或者回滚。二阶提交协议(2PC)和三阶提交协议(3PC)就是根据此协议衍生出来而来。主流的诸如Oracle、MySQL等数据库均已实现了XA接口。XA接口是双向的系统接口,在事务管理器(Transaction Manager)以及一个或多个资源管理器(Resource Manager)之间形成通信桥梁。也就是说,在基于XA的一个事务中,我们可以针对多个资源进行事务管理,例如一个系统访问多个数据库,或即访问数据库、又访问像消息中间件这样的资源。这样我们就能够实现在多个数据库和消息中间件直接实现全部提交、或全部取消的事务。XA规范不是Java的规范,而是一种通用的规范; Java中的规范是JTA和JTS:Java事务API(Java Transaction API)是一个Java企业版的应用程序接口,在Java环境中,允许完成跨越多个XA资源的分布式事务;Java事务服务(Java Transaction Service)是J2EE平台提供了分布式事务服务的具体实现规范,J2EE服务器提供商根据JTS规范实现事务并提供JTA接口。

2)二阶提交协议(2PC): 根据XA协议衍生出来而来; 引入一个作为协调者的组件来统一掌控所有参与者的操作结果并最终指示这些节点是否要把操作结果进行真正的提交; 参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。所谓的两个阶段是指:第一阶段:准备阶段 (投票阶段) 和第二阶段:提交阶段(执行阶段)

3)三阶提交协议(3PC): 是对两段提交(2PC)的一种升级优化,3PC在2PC的第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前,各参与者节点的状态都一致。同时在协调者和参与者中都引入超时机制,当参与者各种原因未收到协调者的commit请求后,会对本地事务进行commit,不会一直阻塞等待,解决了2PC的单点故障问题,但3PC还是没能从根本上解决数据一致性的问题。

4)Java事务规范
JTA:Java事务API(Java Transaction API)是一个Java企业版的应用程序接口,在Java环境中,允许完成跨越多个XA资源的分布式事务。
JTS:Java事务服务(Java Transaction Service)是J2EE平台提供了分布式事务服务的具体实现规范,j2ee服务器提供商根据JTS规范实现事务并提供JTA接口。

柔性事务:分布式理论的AP,遵循BASE,允许一定时间内不同节点的数据不一致,但要求最终一致。

1)基于业务层
TCC: TCC(Try-Confirm-Cancel)又被称补偿事务,TCC与2PC的思想很相似,事务处理流程也很相似,但2PC是应用于在DB层面,TCC则可以理解为在应用层面的2PC,是需要我们编写业务逻辑来实现。

SAGA:Saga是由一系列的本地事务构成。每一个本地事务在更新完数据库之后,会发布一条消息或者一个事件来触发Saga中的下一个本地事务的执行。如果一个本地事务因为某些业务规则无法满足而失败,Saga会执行在这个失败的事务之前成功提交的所有事务的补偿操作。Saga的实现有很多种方式,其中最流行的两种方式是:基于事件的方式和基于命令的方式。

2)最终一致性
消息表:本地消息表的方案最初是由eBay提出,核心思路是将分布式事务拆分成本地事务进行处理。

消息队列:基于MQ的分布式事务方案其实是对本地消息表的封装,将本地消息表基于MQ内部,其他方面的协议基本与本地消息表一致。

最大努力通知:最大努力通知也称为定期校对,是对MQ事务方案的进一步优化。它在事务主动方增加了消息校对的接口,如果事务被动方没有接收到消息,此时可以调用事务主动方提供的消息校对的接口主动获取。

2PC两阶段提交协议

为解决分布式系统的数据一致性问题出现了两阶段提交协议(2 Phase Commitment Protocol),两阶段提交由协调者和参与者组成,共经过两个阶段和三个操作,部分关系数据库如Oracle、MySQL支持两阶段提交协议,本次讲解关系数据库两阶段提交协议。

image-20221019174918788

第一阶段(准备prepare阶段):协调者(事务管理器)通知参与者(服务)准备提交订单,参与者开始投票,参与者完成准备工作向协调者回应Yes。事务管理器要求每个涉及到事务的数据库预提交(precommit)此操作,并反映是否可以提交。

第二阶段(提交commit/回滚rollback阶段):协调者根据参与者的投票结果发起最终的提交指令,如果有参与者没有准备好则发起回滚指令。事务协调器要求每个数据库提交数据,或者回滚数据。

2PC缺陷说明

2PC的优点:实现强一致性,部分关系数据库支持(Oracle、MySQL等)。 

2PC的缺点:整个事务的执行需要由协调者在多个节点之间去协调,增加了事务的执行时间,性能低下。单点问题:事务管理器在整个流程中扮演的角色很关键,如果其宕机,比如在第一阶段已经完成,在第二阶段正准备提交的时候事务管理器宕机,资源管理器就会一直阻塞,导致数据库无法使用。同步阻塞:在准备就绪之后,资源管理器中的资源一直处于阻塞,直到提交完成,释放资源。数据不一致:两阶段提交协议虽然为分布式数据强一致性所设计,但仍然存在数据不一致性的可能,比如在第二阶段中,假设协调者发出了事务commit的通知,但是因为网络问题该通知仅被一部分参与者所收到并执行了commit操作,其余的参与者则因为没有收到通知一直处于阻塞状态,这时候就产生了数据的不一致性。

2PC除本身的算法局限外,还有一个使用上的限制,就是它主要用在两个数据库之间(数据库实现了XA协议)。两个系统之间是无法使用2PC的,因为不会直接在底层的两个业务数据库之间做一致性,而是在两个服务上面实现一致性。2PC只适用两个数据库(数据库实现了XA协议)之间;2PC有诸多问题和不便,在实践中一般很少使用。

2PC案例详解

1597064649437

应用程序连接两个数据源。应用程序通过事务协调器向两个库发起prepare,两个数据库收到消息分别执行本地事务(记录日志),但不提交,如果执行成功则回复yes,否则回复no。事务协调器收到回复,只要有一方回复no则分别向参与者发起回滚事务,参与者开始回滚事务。事务协调器收到回复,全部回复yes,此时向参与者发起提交事务。如果参与者有一方提交事务失败则由事务协调器发起回滚事务。

3PC三阶段提交协议

三段提交(3PC)是对两段提交(2PC)的一种升级优化,3PC在2PC的第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前,各参与者节点的状态都一致。同时在协调者和参与者中都引入超时机制,当参与者各种原因未收到协调者的commit请求后,会对本地事务进行commit,不会一直阻塞等待,解决了2PC的单点故障问题,但3PC还是没能从根本上解决数据一致性的问题。3PC的三个阶段分别是CanCommit、PreCommit、DoCommit:

1)CanCommit:协调者向所有参与者发送CanCommit命令,询问是否可以执行事务提交操作。如果全部响应YES则进入下一个阶段。
2)PreCommit:协调者向所有参与者发送PreCommit命令,询问是否可以进行事务的预提交操作,参与者接收到PreCommit请求后,如参与者成功的执行了事务操作,则返回Yes响应,进入最终commit阶段。一旦参与者中有向协调者发送了No响应或因网络造成超时,协调者没有接到参与者的响应,协调者向所有参与者发送abort请求,参与者接受abort命令执行事务的中断。
3)DoCommit:在前两个阶段中所有参与者的响应反馈均是YES后,协调者向参与者发送DoCommit命令正式提交事务,如协调者没有接收到参与者发送的ACK响应,会向所有参与者发送abort请求命令,执行事务的中断。

3PC缺陷说明

3PC工作在同步网络模型上,它假设消息传输时间是有上界的,只存在机器失败而不存在消息失败。这个假设太强,现实的情形是,机器失败是无法完美地检测出来的,消息传输可能因为网络拥堵花费很多时间。同时,说阻塞是相对,存在协调者和参与者同时失败的情形下, 3PC事务依然会阻塞。实际上,很少会有系统实现3PC,多数现实的系统会通过复制状态机解决2PC阻塞的问题。比如,如果失败模型不是失败停止, 而是消息失败(消息延迟或网络分区),那样3PC会产生不一致的情形。3PC并没有完美解决2PC的阻塞,也引入了新的问题(不一致问题),所以3PC很少会被真正的使用。

TCC事务补偿

image-20221020092107383

TCC事务补偿是基于2PC实现的业务层事务控制方案,它是Try、Confirm和Cancel三个单词的首字母,含义如下:

1、Try检查及预留业务资源,完成提交事务前的检查,并预留好资源。 
2、Confirm确定执行业务操作,对try阶段预留的资源正式执行。 
3、Cancel取消执行业务操作,对try阶段预留的资源释放。 

TCC缺陷说明

TCC优点:最终保证数据的一致性,在业务层实现事务控制,灵活性好。
TCC缺点:开发成本高,每个事务操作每个参与者都需要实现try/confirm/cancel三个接口。

1、空回滚
当一个分支事务所在的服务发生宕机或者网络异常导致调用失败,并未执行try方法,当恢复后事务执行回滚操作就会调用此分支事务的cancel方法,如果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就执行。

TCC案例详解

1597064871541

Try:下单业务由订单服务和库存服务协同完成,在try阶段订单服务和库存服务完成检查和预留资源。订单服务检查当前是否满足提交订单的条件(比如:当前存在未完成订单的不允许提交新订单)。库存服务检查当前是否有充足的库存,并锁定资源。

Confirm:订单服务和库存服务成功完成Try后开始正式执行资源操作。订单服务向订单写一条订单信息。库存服务减去库存。

Cancel:如果订单服务和库存服务有一方出现失败则全部取消操作。订单服务需要删除新增的订单信息。库存服务将减去的库存再还原。

注意:TCC的try、confirm、cancel接口都要实现幂等性,在为在try、confirm、cancel失败后要不断重试。幂等性是指同一个操作无论请求多少次,其结果都相同。幂等操作实现方式有:

1、操作之前在业务方法进行判断如果执行过了就不再执行。 
2、缓存所有请求和处理的结果,已经处理的请求则直接返回结果。 
3、在数据库表中加一个状态字段(未处理,已处理),数据操作时判断未处理时再处理。

Saga事务

Saga是分布式事务领域最有名气的解决方案之一,最初出现在1987年Hector Garcaa-Molrna & Kenneth Salem发表的论文SAGAS里。Saga是由一系列的本地事务构成。每一个本地事务在更新完数据库之后,会发布一条消息或者一个事件来触发Saga中的下一个本地事务的执行。如果一个本地事务因为某些业务规则无法满足而失败,Saga会执行在这个失败的事务之前成功提交的所有事务的补偿操作。Saga的实现有很多种方式,其中最流行的两种方式是:

1)基于事件的方式。这种方式没有协调中心,整个模式的工作方式就像舞蹈一样,各个舞蹈演员按照预先编排的动作和走位各自表演,最终形成一只舞蹈。处于当前Saga下的各个服务,会产生某类事件,或者监听其它服务产生的事件并决定是否需要针对监听到的事件做出响应。
2)基于命令的方式。这种方式的工作形式就像一只乐队,由一个指挥家(协调中心)来协调大家的工作。协调中心来告诉Saga的参与方应该执行哪一个本地事务。

Saga缺陷说明

基于事件方式的优缺点优点:简单且容易理解。各参与方相互之间无直接沟通,完全解耦。这种方式比较适合整个分布式事务只有2-4个步骤的情形。缺点:这种方式如果涉及比较多的业务参与方,则比较容易失控。各业务参与方可随意监听对方的消息,以至于最后没人知道到底有哪些系统在监听哪些消息。更悲催的是,这个模式还可能产生环形监听,也就是两个业务方相互监听对方所产生的事件。

基于命令方式的优缺点优点:避免了业务方之间的环形依赖。将分布式事务的管理交由协调中心管理(实现协调中心的一个比较好的方式是使用状态机),协调中心对整个逻辑非常清楚。减少了业务参与方的复杂度。这些业务参与方不再需要监听不同的消息,只是需要响应命令并回复消息。测试更容易(分布式事务逻辑存在于协调中心,而不是分散在各业务方)。回滚也更容易。缺点:一个可能的缺点就是需要维护协调中心,而这个协调中心并不属于任何业务方。

Saga模式建议
1、给每一个分布式事务创建一个唯一的Tx id。这个唯一的Tx id可以用来在各个业务参与方沟通时精确定位哪一笔分布式事务。
2、对于基于命令的方式,在命令中携带回复地址。这种方式可以让服务同时响应多个协调中心请求。
3、幂等性。幂等性能够增加系统的容错性,让各个业务参与方服务提供幂等性操作,能够在遇到异常情况下进行重试。
4、尽量在命令或者消息中携带下游处理需要的业务数据,避免下游处理时需要调用消息产生方接口获取更多数据。减少系统之间的相互依赖。

本地消息表

image-20230323134804584

本地消息表的方案最初是由eBay提出,核心思路是将分布式事务拆分成本地事务进行处理。上图中整体的处理步骤如下:

1)事务主动方在同一个本地事务中处理业务和写消息表操作
2)事务主动方通过消息中间件,通知事务被动方处理事务通知事务待消息。消息中间件可以基于Kafka、RocketMQ消息队列,事务主动方主动写消息到消息队列,事务消费方消费并处理消息队列中的消息。
3)事务被动方通过消息中间件,通知事务主动方事务已处理的消息。
4)事务主动方接收中间件的消息,更新消息表的状态为已处理。

一些必要的容错处理如下:

1)当1处理出错,由于还在事务主动方的本地事务中,直接回滚即可。
2)当2、3处理出错,由于事务主动方本地保存了消息,只需要轮询消息重新通过消息中间件发送,事务被动方重新读取消息处理业务即可。
3)如果是业务上处理失败,事务被动方可以发消息给事务主动方回滚事务
4)如果事务被动方已经消费了消息,事务主动方需要回滚事务的话,需要发消息通知事务主动方进行回滚事务。

本地缺陷说明

优点:从应用设计开发的角度实现了消息数据的可靠性,消息数据的可靠性不依赖于消息中间件,弱化了对MQ中间件特性的依赖。方案轻量,容易实现。

缺点:与具体的业务场景绑定,耦合性强,不可公用。消息数据与业务数据同库,占用业务系统资源。业务系统在使用关系型数据库的情况下,消息服务性能会受到关系型数据库并发性能的局限。

MQ消息队列

本方案是将分布式事务拆分成多个本地事务来完成,并且由消息队列异步协调完成,如下图:

1597065106952

1、订单服务和库存服务完成检查和预留资源。

2、订单服务在本地事务中完成添加订单表记录和添加一条减少库存任务的消息到MQ。

3、由定时任务根据消息表的记录发送给MQ通知库存服务执行减库存操作。

4、库存服务执行减少库存,并且记录执行消息状态(为避免重复执行消息,在执行减库存之前查询是否执行过此消息)。

5、库存服务向MQ发送完成减少库存的消息。

6、订单服务接收到完成库存减少的消息后,MQ删除原来添加的那条减少库存任务消息。

实现最终事务一致要求:预留资源成功理论上要求正式执行成功,如果执行失败会进行重试,要求业务执行方法实现幂等。

这种情况下,事务主动方服务正常,没有发生故障,发消息流程如下:发送方向 MQ 服务端(MQ Server)发送half消息。MQ Server将消息持久化成功之后,向发送方ack确认消息已经发送成功。发送方开始执行本地事务逻辑。发送方根据本地事务执行结果向MQ Server提交二次确认(commit 或是 rollback)。MQ Server收到commit状态则将半消息标记为可投递,订阅方最终将收到该消息;MQ Server收到rollback状态则删除半消息,订阅方将不会接受该消息。

在断网或者应用重启等异常情况下,提交的二次确认超时未到达MQ Server,此时处理逻辑如下:MQ Server对该消息发起消息回查。发送方收到消息回查后,需要检查对应消息的本地事务执行的最终结果。发送方根据检查得到的本地事务的最终状态再次提交二次确认。MQ Server基于commit/rollback对消息进行投递或者删除。

MQ缺陷说明

MQ优点:由MQ按异步的方式协调完成事务,性能较高。不用实现try、confirm、cancel接口,开发成本比TCC低。 

MQ缺点:此方式基于关系数据库本地事务来实现,会出现频繁读写数据库记录,浪费数据库资源,另外对于高并发操作不是最佳方案。

MQ案例详解

1597068238355

订单下单之后发送消息给学习中心系统

1、支付成功后,订单服务向本地数据库更新订单状态并向消息表写入“添加选课消息”,通过本地数据库保证订单状态和添加选课消息的事务。

2、定时任务扫描消息表,取出“添加选课任务“并发向MQ。 

3、学习服务接收到添加选课的消息,先查询本地数据库的历史消息表是否存在消息,存在则说明已经添加选课,否则向本地数据库添加选课,并向历史消息表添加选课消息。这里选课表和历史消息表在同一个数据库,通过本地事务保证。 

4、学习服务接收到添加选课的消息,通过查询消息表判断如果已经添加选课也向MQ发送“完成添加选课任务的消息”,否则则添加选课,完成后向MQ发送“完成添加选课任务的消息”。

5、订单服务接收到完成选课的消息后删除订单数据库中消息表的“添加选课消息”,为保证后期对账将消息表的消息先添加到历史消息表再删除消息,表示此消息已经完成。

定时任务发送消息流程如下:

1、每隔1分钟扫描一次任务表,定时任务扫描task表,一次取出多个任务,取出超过1分钟未处理的任务。

2、考虑订单服务可能集群部署,为避免重复发送任务使用乐观锁的方式每次从任务列表取出要处理的任务。乐观锁的方式修改任务版本,防止多个订单服务获取相同的任务,即第一个服务获取任务之后,修改版本号为+1,当另一个服务再次调用该方法修改时就获取不到该任务了。

3、任务发送完毕更新任务发送时间。

最大努力通知

image-20230323142156672

最大努力通知也称为定期校对,是对MQ事务方案的进一步优化。它在事务主动方增加了消息校对的接口,如果事务被动方没有接收到消息,此时可以调用事务主动方提供的消息校对的接口主动获取。在可靠消息事务中,事务主动方需要将消息发送出去,并且消息接收方成功接收,这种可靠性发送是由事务主动方保证的;但是最大努力通知,事务主动方尽最大努力(重试,轮询等)将事务发送给事务接收方,但是仍然存在消息接收不到,此时需要事务被动方主动调用事务主动方的消息校对接口查询业务消息并消费,这种通知的可靠性是由事务被动方保证的。最大努力通知适用于业务通知类型,例如微信交易的结果,就是通过最大努力通知方式通知各个商户,既有回调通知,也有交易查询接口。

分布式事务中间件Seata

image-20230323143534678

Seata是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata将为用户提供了AT、TCC、SAGA和XA事务模式,为用户打造一站式的分布式解决方案。

TC(Transaction Coordinator) 事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM(Transaction Manager)事务管理器: 定义全局事务的范围,开始全局事务、提交或回滚全局事务。
RM(Resource Manager)资源管理器: 管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

Seata AT模式

两阶段提交协议的演变:
一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
二阶段:提交异步化,非常快速地完成。回滚通过一阶段的回滚日志进行反向补偿。

写隔离:一阶段本地事务提交前,需要确保先拿到全局锁。拿不到全局锁,不能提交本地事务。拿全局锁的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。

读隔离:在数据库本地事务隔离级别读已提交(Read Committed)或以上的基础上,Seata(AT模式)的默认全局隔离级别是读未提交(Read Uncommitted)。如果应用在特定场景下,必需要求全局的读已提交,目前Seata的方式是通过SELECT FOR UPDATE语句的代理。SELECT FOR UPDATE语句的执行会申请全局锁,如果全局锁被其他事务持有,则释放本地锁(回滚SELECT FOR UPDATE语句的本地执行)并重试。这个过程中,查询是被block住的,直到全局锁拿到,即读取的相关数据是已提交的,才返回。出于总体性能上的考虑,Seata目前的方案并没有对所有SELECT语句都进行代理,仅针对FOR UPDATE的SELECT语句。

Seata XA模式

在Seata定义的分布式事务框架内,利用事务资源(数据库、消息服务等)对XA协议的支持,以XA协议的机制来管理分支事务的一种事务模式。

Seata TCC模式

一个分布式的全局事务,整体是两阶段提交的模型。全局事务是由若干分支事务组成的,分支事务要满足两阶段提交的模型要求,即需要每个分支事务都具备自己的:一阶段prepare行为、二阶段commit或rollback行为。根据两阶段行为模式的不同,我们将分支事务划分为Automatic (Branch) Transaction Mode和TCC (Branch) Transaction Mode。

AT模式基于支持本地ACID事务的关系型数据库:
一阶段prepare行为:在本地事务中,一并提交业务数据更新和相应回滚日志记录。
二阶段commit行为:马上成功结束,自动异步批量清理回滚日志。
二阶段rollback行为:通过回滚日志,自动生成补偿操作,完成数据回滚。

相应的,TCC模式,不依赖于底层数据资源的事务支持:
一阶段prepare行为:调用自定义的prepare逻辑。
二阶段commit行为:调用自定义的commit逻辑。
二阶段rollback行为:调用自定义的rollback逻辑。
所谓TCC模式,是指支持把自定义的分支事务纳入到全局事务的管理中。

Seata Saga模式

Saga模式是SEATA提供的长事务解决方案,在Saga模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。

适用场景:业务流程长、业务流程多参与者包含其它公司或遗留系统服务,无法提供TCC模式要求的三个接口。
优势:一阶段提交本地事务,无锁,高性能事件驱动架构,参与者可异步执行,高吞吐补偿服务易于实现。
缺点:不保证隔离性。

目前SEATA提供的Saga模式是基于状态机引擎来实现的,机制是:通过状态图来定义服务调用的流程并生成json状态语言定义文件状态图中一个节点可以是调用一个服务,节点可以配置它的补偿节点状态图json由状态机引擎驱动执行,当出现异常时状态引擎反向执行已成功节点对应的补偿节点将事务回滚。注意: 异常发生时是否进行补偿也可由用户自定义决定可以实现服务编排需求,支持单项选择、并发、子流程、参数转换、参数映射、服务执行状态判断、异常捕获等功能。
posted @ 2019-05-26 11:10  肖德子裕  阅读(16328)  评论(0编辑  收藏  举报