分布式系统(4)分布式事务

一. 什么是分布式事务

首先,事务指的是一系列数据库操作,它是保证数据库正确性的基本逻辑单元,拥有ACID四个特性:原子性、一致性、隔离性与持久性

  • 原子性(Atomicity),可以理解为一个事务内的所有操作要么都执行成功,要么就不执行,不存在中间状态,即一部分操作成功,一部分操作失败的状态
  • 一致性(Consistency),可以理解为数据是满足完整性约束的,也就是不会存在中间状态的数据,比如你账上有400,我账上有100,你给我打200块,此时你账上的钱应该是200,我账上的钱应该是300,不会存在我账上钱加了,你账上钱没扣的中间状态
  • 隔离性(Isolation),指的是多个事务并发执行的时候不会互相干扰,即一个事务内部的数据对于其他事务来说是隔离的
  • 持久性(Durability),指的是一个事务完成了之后数据就被永远保存下来,之后的其他操作或故障都不会对事务的结果产生影响。

事务的定义

一个事务由开始标识(begin_transaction)、数据库操作和结束标识(commit或rollback)三部分组成。如下图所示:
在这里插入图片描述

  1. 事务开始:begin_transaction,说明事务的开始;

  2. 数据库上的操作:表现为一条或多条SQL语句;

    • 事务提交:commit_transaction,提交事务操作,操作生效;
    • 事务回滚:rollback_transaction,事务取消,操作废弃。

分布式事务就是一次操作由每个不同的事务组成,这些事务分布在不同的服务器上,且属于不同的应用,分布式事务需要保证每个事务要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性

二. 分布式事务理论

1. CAP 理论

CAP:Consistency Acailability Partition tolerance的缩写

  • C (一致性)
    一致性指的是多个数据副本是否能够保持一致的特性,在一致性的条件下,系统在执行数据更新操作之后能够从一致性状态转移到另一个一致性状态
    对系统的一个数据更新成功之后,如果所有用户都能够读取到最新的值,该系统就被认为具有强一致性

  • A (可用性)
    可用性指分布式系统在面对各种异常时可以提供正常的服务能力,可以用系统可用时间占总时间的比值来衡量,4 个 9 的可用性表示系统 99.99%的时间是可用的。
    在可用性的条件下,要求系统提供的服务一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果

  • P (分区容忍性)
    网络分区指分布式系统中的节点被划分了多个区域,每个区域内部都可以通信,但是区域之间无法通信。
    在分区容忍性条件下,分布式系统在遇到任何网络分区故障的时候,仍然需要能对外提供一致性的可用服务,除非整个网络都出现了故障

CAP理论认为:一个 分布式系统最多只能同时满足,一致性,可用性,分区容错性的三项中的两项,由于分区容错性是必然存在的,所以大部分分布式软件系统都在CPAP中做取舍,比如:

  • Zookeeper 采用CP一致性,强调一致性,弱化可用性。
  • Eureka 采用AP可用性,强调可用性,弱化一致性。

2. CAP为什么不能同时满足

假设有两台服务器N1和N2,它们各自有一个应用程序AB和数据库,两台服务器数据库里面的数据是一样的
在这里插入图片描述

  • 在满足一致性的时候,N1和N2数据库中的数据保持一致
  • 在满足可用性的时候,用户不管是请求N1或者N2,都会得到立即响应
  • 在满足分区容错性的情况下,N1和N2有任何一方宕机,或者网络不通的时候,都不会影响N1和N2彼此之间的正常运作

问题来了:

假设某个时刻N1和N2之间的网络通信突然中断了。如果系统满足分区容错性,那么显然可以支持这种异常。问题是在此前提下,一致性和可用性是否可以做到不受影响呢

如下图,突然某一时刻N1和N2之间的关联断开:
在这里插入图片描述
用户向N1发送了请求更改了数据,将数据库从V0更新成了V1。由于网络断开,所以N2数据库依然是V0,如果这个时候有一个请求发给了N2,但是N2并没有办法可以直接给出最新的结果V1,这个时候该怎么办呢?

这个时候一致性 和 可用性就无法同时满足,这里必须做出取舍:

  1. 第一种是满足可用性,将错就错,将错误的V0数据返回给用户
  2. 第二种是满足一致性,阻塞等待,等待网络通信恢复,N2中的数据更新之后再返回给用户
  • 为了保证一致性(CP):就需要让所有节点下线成为不可用状态,等待同步完成。
  • 为了保证可用性(AP):在同步过程中允许读取所有节点的数据,但是数据可能不一致。

3. CP,AP还是CA

3.1 舍弃A,保留CP

一个系统保证了一致性和分区容错性,舍弃可用性。也就是说在极端情况下,允许出现系统无法访问的情况出现,这个时候往往会牺牲用户体验,让用户保持等待,一直到系统数据一致了之后,再恢复服务
对于一些应用而言,一致性是必须要满足的,比如Hbase、Redis这种分布式存储,数据一致性是最基本的要求。不满足一致性的存储显然不会有用户愿意使用

ZooKeeper也是一样,任何时候访问ZK都可以获得一致性的结果。它的职责就是保证管辖下的服务保持同步和一致,显然不可能放弃一致性。但是在极端情况下,ZK可能会丢弃调一些请求,消费者需要重新请求才能获得结果

3.2. 舍弃C,保留AP

这种是大部分的分布式系统的设计,举个例子,我们在12306买票的时候就经常会遇到。在我们点击购买的时候,系统并没有提示没票。等我们输入了验证码,付款的时候才会告知,已经没有票了。这就是因为我们在点击购买的时候,数据没有达成一致性,在付款校验的时候才检验出余票不足。这种设计会牺牲一些用户体验,但是可以保证高可用,让用户不至于无法访问或者是长时间等待,也算是一种取舍吧

3.3 舍弃P,保留CA

这种情况几乎不存在。

首先,在分布式系统中,网络分区是必然的,P(分区容忍性)必不可少, 如果要舍弃P,那么就是要舍弃分布式系统,CAP也就无从谈起了。可以说P是分布式系统的前提,因为需要总是假设网络是不可靠的,因此,CAP 理论实际上是要在一致性和可用性之间做权衡

4. BASE 理论

BASE理论是对CAP中CA,一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)

  • Basically Available(基本可用) 不追求强可用性,而且强调系统基本能够一直运行对外提供服务,当分布式系统遇到不可预估的故障时,允许一定程度上的不可用,比如:对请求进行限流排队,使得部分用户响应时间变长,或对非核心服务进行降级
  • Soft state(软状态) 对于数据库中事务的原子性:要么全部成功,要不全部不成功。软状态允许系统中的数据存在中间状态
  • Eventually consistent(最终一致性) 数据不可能一直都是软状态,必须在一个时间期限之后达到各个节点的一致性。在此之后,所有的节点的数据都是一致的。系统达到最终一致性

三. 分布式事务实现方式

1. 2PC(两阶段提交协议)

分布式事务在执行时会被分解为多个服务器或多个应用上的子事务来执行,为协调各个服务器(应用)上的子事务的执行,我们需要一个协调者,而各个子事务的具体执行,需要本地的参与者来完成。为进一步理解两阶段提交协议,我们先了解下协调者与参与者这两个概念:

  • 协调者:协调各个子事务的执行,负责决定所有子事务的提交或回滚;
  • 参与者:负责各个子事务的提交与回滚,并向协调者提出子事务的提交或回滚意向

协调者与参与者的关系如下图所示:
在这里插入图片描述
协调者和每个参与者均拥有一个本地日志文件,用来记录各自的执行过程。无论是协调者还是参与者,他们在进行操作前都必须将该操作记录到相应的日志文件中,以用来进行事务故障恢复。
协调者可以向各个参与者发送命令,使各个参与者在协调者的领导下执行命令,各个参与者也可以将自身的命令执行状态以应答的形式反馈给协调者,由协调者收集并分析这些应答以决定下一步的操作

1.1 两阶段提交的基本思想

两阶段提交是为了实现分布式事务提交而采用的协议。

其基本思想是把全局事务的提交分为如下两个阶段:

在这里插入图片描述

1.2 两阶段提交的基本流程
  1. 协调者在征求各参与者的意见之前,首先要在它的日志文件中写入一条“开始提交”(Begin_commit)的记录。然后,协调者向所有参与者发送“预提交”(Prepare)命令,此时协调者进入等待状态,等待收集各参与者的应答

  2. 各个参与者接收到“预提交”(Prepare)命令后,根据情况判断其是否已经准备好提交子事务。若可以提交,则在参与者日志文件中写入一条“准备提交”(Ready)的记录,并将“准备提交”(Ready)的应答发送给协调者,否则,在参与者的日志文件中写入一条“准备废弃”(Abort)的记录,并将“准备废弃”(Abort)的应答发送给协调者。发送应答后,参与者将进入等待状态,等待协调者所做出的最终决定

  3. 协调者收集各参与者发来的应答,判断是否存在某个参与者发来“准备废弃”的应答,若存在,则采取两阶段提交协议的“一票否决制”,在其日志文件中写入一条“决定废弃”(Abort)的记录,并发送“全局废弃”(Abort)命令给各个参与者,否则,在其日志中写入一条“决定提交”(Commit)的记录,向所有参与者发送“全局提交”(Commit)命令。此时,协调者再次进入等待状态,等待收集各参与者的确认信息

  4. 各个参与者接收到协调者发来的命令后,判断该命令类型,若为“全局提交”命令,则在其日志文件中写入一条“提交”(Commit)的记录,并对子事务实施提交,否则,参与者在其日志文件中写入一条“废弃”(Abort)的记录,并对子事务实施废弃。实施完毕后,各个参与者要向协调者发送确认信息(Ack)

  5. 当协调者接收到所有参与者发送的确认信息后,在其日志文件中写入“事务结束”(End_transaction)记录,全局事务终止

基本流程图:
在这里插入图片描述

1.3 两阶段提交出现的问题
1.3 两阶段提交出现的问题
1.3.1 如果协调者挂了

如果协调者故障,可以通过选举得到新协调者

1.3.2 如果协调者在发送准备命令之前挂了,事务未开始状态

如果处于第一阶段,其实影响不大都回滚好了,在第一阶段事务肯定还没提交

1.3.3 如果第一阶段有参与者返回失败

协调者就会向所有参与者发送回滚事务的请求,即分布式事务执行失败

1.3.4 如果第二阶段提交失败

这里有两种情况

  • 第一种是第二阶段执行的是回滚事务操作,那么答案是不断重试,直到所有参与者都回滚了,不然那些在第一阶段准备成功的参与者会一直阻塞着
  • 第二种是第二阶段执行的是提交事务操作,那么答案也是不断重试,因为有可能一些参与者的事务已经提交成功了,这个时候只有一条路,就是头铁往前冲,不断的重试,直到提交成功,到最后真的不行只能人工介入处理
1.3.5 如果协调者出现故障或者网络故障

如果协调者故障或者网络故障,导致参与者不能及时收到协调者发送的“提交”命令时,那么参与者将处于等待状态,直到获得所需要的信息后才可以做出决定。在故障恢复前,参与者的行为将始终停留不前,子事务所占用的系统资源也不能被释放,这时我们称事务进入了阻塞状态,阻塞状态会降低系统的可靠性与可用性

1.3.6 如果协调者在第二阶段发送提交请求之后挂掉

如果协调者在第二阶段发送提交请求之后挂掉,而唯一接受到这条消息的参与者执行之后也挂掉了,即使协调者通过选举协议产生了新的协调者并通知其他参与者进行提交或回滚操作的话,都可能会与这个已经执行的参与者执行的操作不一样,当这个挂掉的参与者恢复之后,就会产生数据不一致的问题

1.4 两阶段提交总结

两阶段提交优点:尽量保证了数据的强一致,但不是100%一致

两阶段提交的缺点:

  • 单点故障,由于协调者的重要性,一旦协调者发生故障,参与者会一直阻塞,尤其时在第二阶段,协调者发生故障,那么所有的参与者都处于锁定事务资源的状态中,而无法继续完成事务操作
  • 同步阻塞,由于所有节点在执行操作时都是同步阻塞的,当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态
  • 数据不一致,在第二阶段中,当协调者想参与者发送提交事务请求之后,发生了局部网络异常或者在发送提交事务请求过程中协调者发生了故障,这会导致只有一部分参与者接收到了提交事务请求。而在这部分参与者接到提交事务请求之后就会执行提交事务操作。但是其他部分未接收到提交事务请求的参与者则无法提交事务。从而导致分布式系统中的数据不一致

2. 3PC(三阶段提交协议)

三阶段提交(Three-phase commit),三阶段提交是为解决两阶段提交 的缺点而设计的。

三阶段提交协议在一定程度上解决了两阶段提交协议中的同步阻塞问题,这是因为当协调者故障或是网络故障时,参与者长时间未收到协调者的命令,参与者可以通过启动恢复处理过程,不必进入等待状态而可以独立的做出决定。三阶段提交协议中协调者和参与者都引入了超时机制,然后把两阶段提交协议里的第一个阶段拆分为两步:先询问(CanCommit),再锁资源(PreCommit),再最后提交(DoCommit)

虽然三阶段提交协议虽然可以在一定程度上解决两阶段提交协议中的阻塞问题,但因为流程逻辑复杂,代码编写难度太高,也很难用于实际应用

2.1 三阶段提交的三个阶段:

在这里插入图片描述

2.1.1 询问阶段 CanCommit

协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。

2.1.2 准备阶段 PreCommit

协调者根据参与者在询问阶段的响应判断是否执行事务还是中断事务:

  • 如果所有参与者都返回Yes,则执行事务
  • 如果参与者有一个或多个参与者返回No或者超时,则中断事务

参与者执行完操作之后返回ACK响应,同时开始等待最终指令

2.1.3 提交阶段 DoCommit

协调者根据参与者在准备阶段的响应判断是否执行事务还是中断事务:

  • 如果所有参与者都返回正确的ACK响应,则提交事务
  • 如果参与者有一个或多个参与者收到错误的ACK响应或者超时,则中断事务
  • 如果参与者无法及时接收到来自协调者的提交或者中断事务请求时,会在等待超时之后,会继续进行事务提交

协调者收到所有参与者的ACK响应,完成事务

2.2 解决二阶段提交时的问题

在三阶段提交中,如果在第三阶段协调者发送提交请求之后挂掉,并且唯一的接受的参与者执行提交操作之后也挂掉了,这时协调者通过选举协议产生了新的协调者,在二阶段提交时存在的问题就是新的协调者不确定已经执行过事务的参与者是执行的提交事务还是中断事务,但是在三阶段提交时,肯定得到了第二阶段的再次确认,那么第二阶段必然是已经正确的执行了事务操作,只等待提交事务了,所以新的协调者可以从第二阶段中分析出应该执行的操作,进行提交或者中断事务操作,这样即使挂掉的参与者恢复过来,数据也是一致的

所以,三阶段提交解决了二阶段提交中存在的由于协调者和参与者同时挂掉可能导致的数据一致性问题和单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行提交事务,而不会一直持有事务资源并处于阻塞状态

2.3 三阶段提交的问题

在提交阶段如果发送的是中断事务请求,但是由于网络问题,导致部分参与者没有接到请求,那么参与者会在等待超时之后执行提交事务操作,这样这些由于网络问题导致提交事务的参与者的数据就与接受到中断事务请求的参与者存在数据不一致的问题

所以无论是2PC还是3PC都不能保证分布式系统中的数据100%一致

3. 最大努力通知

最大努力通知主要用于对于事务的一致性不是特别敏感的事务,实现事务的弱一致性,适用于一些最终一致性要求较低的业务,比如支付通知,短信通知这种业务。可以通过消息中间件实现,与前面异步确保型操作不同的一点是, 在消息由MQ Server投递到消费者之后, 允许在达到最大重试次数之后正常结束事务

基本流程:

  • 主业务完成后,通过mq通知被动业务(允许消息丢失)
  • 通知失败后主动方 按照一定的时间阶梯进行消息发送重试,直到超过最大重试次数为止
  • 主动方提供业务查询接口,以便被动方后续进行通知失败后的补偿,恢复丢失消息

最大努力通知其实只是表明了一种柔性事务的思想:我已经尽力我最大的努力想达成事务的最终一致了

4. 补偿事务TCC

TCC Try-Confirm-Cancel的简称,针对每个操作,都需要有一个其对应的确认和取消操作,当操作成功时调用确认操作,当操作失败时调用取消操作,类似于二阶段提交,只不过是这里的提交和回滚是针对业务上的,所以基于TCC实现的分布式事务也可以看做是对业务的一种补偿机制

4.1 TCC的三阶段:
  • Try阶段:对业务系统做检测及资源预留
  • Confirm阶段:对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功
  • Cancel阶段:在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放

比如订单和存储操作,需要检查库存剩余数量是否够用,并进行预留,预留操作的话就是新建一个可用库存数量字段,Try阶段操作是对这个可用库存数量进行操作

执行流程:

  1. Try阶段:订单系统将当前订单状态设置为支付中,库存系统校验当前剩余库存数量是否大于1,然后将可用库存数量设置为库存剩余数量-1,
  2. 如果Try阶段执行成功,执行Confirm阶段,将订单状态修改为支付成功,库存剩余数量修改为可用库存数量
  3. 如果Try阶段执行失败,执行Cancel阶段,将订单状态修改为支付失败,可用库存数量修改为库存剩余数量
    在这里插入图片描述

5. 本地消息表+MQ

本地消息表方案最初是ebay提出的,其实也是BASE理论的应用,属于可靠消息最终一致性的范畴。其基本的设计思想是将远程分布式事务拆分成一系列的本地事务。如果不考虑性能及设计优雅,借助关系型数据库中的表即可实现。

方案通过在事务主动发起方额外新建事务消息表,事务发起方处理业务和记录事务消息在本地事务中完成,轮询事务消息表的数据发送事务消息,事务被动方基于消息中间件消费事务消息表中的事务。这样设计可以避免”业务处理成功 + 事务消息发送失败",或"业务处理失败 + 事务消息发送成功"的棘手情况出现,保证 2 个系统事务的数据一致性

5.1 处理流程

下面把分布式事务最先开始处理的事务方称为事务主动方,在事务主动方之后处理的业务内的其他事务称为事务被动方

为了方便理解,下面继续以 电商下单为例进行方案解析,这里把整个过程简单分为扣减库存,订单创建 2 个步骤,库存服务和订单服务分别在不同的服务器节点上,其中库存服务是事务主动方,订单服务是事务被动方

事务的主动方需要额外新建事务消息表,用于记录分布式事务的消息的发生、处理状态

整个业务处理流程如下:
在这里插入图片描述

  1. 事务主动方处理本地事务
    事务主动方在本地事务中处理业务更新操作和写消息表操作。上面例子中库存服务阶段在本地事务中完成扣减库存和写消息表(图中 1、2)
  2. 事务主动方通过消息中间件,通知事务被动方处理事务通知事务待消息
    消息中间件可以基于 Kafka、RocketMQ 消息队列,事务主动方主动写消息到消息队列,事务消费方消费并处理消息队列中的消息

前两步,库存服务把事务待处理消息写到消息中间件,订单服务消费消息中间件的消息,完成新增订单(图中 3 - 5)

  1. 事务被动方通过消息中间件,通知事务主动方事务已处理的消息
    订单服务把事务已处理消息写到消息中间件,库存服务消费中间件的消息,并将事务消息的状态更新为已完成(图中 6 - 8)
    为了数据的一致性,当处理错误需要重试,事务发送方和事务接收方相关业务处理需要支持幂等
5.2 一致性的容错处理
  • 当步骤 1 处理出错,事务回滚,相当于什么都没发生。
  • 当步骤 2、步骤 3 处理出错,由于未处理的事务消息还是保存在事务发送方,事务发送方可以定时轮询为超时消息数据,再次发送到消息中间件进行处理。事务被动方消费事务消息重试处理。
  • 如果是业务上的失败,事务被动方可以发消息给事务主动方进行回滚。
  • 如果多个事务被动方已经消费消息,事务主动方需要回滚事务时需要通知事务被动方回滚。
5.3 总结

优点:

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

缺点:

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

6. 基于 MQ 的最终一致性

利用消息中间件来异步完成事务的后一半更新,实现系统的最终一致性。它采用的不是强一致性,而是最终一致性,这也就是 BASE 理论。并且我们直到 MQ 是异步的,所以性能也比较快,可以说完美的解决了上面两种方式带来的问题
主要是基于 MQ 消息投递的可靠性,将分布式事务发送给 MQ 中间件之后,中间件将事务持久化,这一点非常重要,保证消息不丢失。消费者端异步消费,如果遇到失败情况,由于我们的消息是持久化的,所以可以根据业务规则不断重试,有必要的话,人工补偿,保证数据最终一致性

6.1 原理
  • MQ的确认机制 确保生产者一定将增加积分的消息投递到MQ中
  • 手动ack应答形式 确保消费者消费消息一定成功,如果没有异常情况下通知MQ删除该消息,否则情况下不断重试消费 (需要保证幂等性问题)
  • 补偿队列 如果MQ异步代码执行之后,代码出现异常的情况下,可以保证分布式事务问题
6.2 处理流程

以转账业务为例,有两步操作,一个是操作用户A扣钱,一个是操作用户B加钱

  1. A 扣款成功后,发送“扣款成功消息”到消息中间件
  2. 加钱业务订阅“扣款成功消息”,再对用户B加钱

在这里插入图片描述

如何用户A扣款失败,可加钱业务订阅到了消息,用户B加了钱的场景?

基于RocketMQ事务方案

因为上面的问题,RocketMq消息中间件把消息分为两个阶段:

  1. **Prepared阶段(预备阶段)**该阶段主要发一个消息到rocketmq,但该消息只储存在commitlog中,但consumeQueue中不可见,也就是消费端(订阅端)无法看到此消息
  2. **Commit/rollback阶段(确认阶段)**该阶段主要是把prepared消息保存到consumeQueue中,即让消费端可以看到此消息,也就是可以消费此消息

整个流程:

  1. 在扣款之前,先发送预备消息
  2. 发送预备消息成功后,执行本地扣款事务
  3. 扣款成功后,再发送确认消息
  4. 消息端(加钱业务)可以看到确认消息,消费此消息,进行加钱
    在这里插入图片描述

确认消息说明

注意:上面的确认消息可以为commit消息,可以被订阅者消费;也可以是Rollback消息,即执行本地扣款事务失败后,提交rollback消息,即删除那个预备消息,订阅者无法消费

6. 出现的问题
  • 问题1:如果发送预备消息失败,下面的流程不会走下去;这个是正常的
  • 问题2:如果发送预备消息成功,但执行本地事务失败;这个也没有问题,因为此预备消息不会被消费端订阅到,消费端不会执行业务。
  • 问题3:如果发送预备消息成功,执行本地事务成功,但发送确认消息失败;这个就有问题了,因为用户A扣款成功了,但加钱业务没有订阅到确认消息,无法加钱。这里出现了数据不一致。

那RocketMq是怎么解决的呢?

RocketMQ回查
RocketMq如何解决上面的问题,核心思路就是【状态回查】,也就是RocketMq会定时遍历commitlog中的预备消息,因为预备消息最终肯定会变为commit消息或Rollback消息,所以遍历预备消息去回查本地业务的执行状态,如果发现本地业务没有执行成功就rollBack,如果执行成功就发送commit消息

问题3解决方案:发送预备消息成功,本地扣款事务成功,但发送确认消息失败;因为RocketMq会进行回查预备消息,在回查后发现业务已经扣款成功了,就补发“发送commit确认消息”;这样加钱业务就可以订阅此消息了。这个思路其实把问题2也解决了,因为本地事务没有执行成功,RocketMQ回查业务,发现没有执行成功,就会发送RollBack确认消息,把消息进行删除。
在这里插入图片描述
回查判断业务是否成功
在回查业务中,如何判断本地事务是否执行成功?其实这个跟具体的业务场景有关,比如订单的话,我们可以查询订单表中是否有记录,有记录则表明成功。如果本地事务执行了很多张表,那是不是我们要把那些表都要进行判断是否执行成功呢?这样是不是太麻烦了,而且和业务很耦合。

有没有更好的方式呢? 就是设计一张Transaction表,将业务表和Transaction绑定在同一个本地事务中,如果扣款本地事务成功时,Transaction中应当已经记录该TransactionId的状态为「已完成」。当RocketMq回查时,只需要检查对应的TransactionId的状态是否是「已完成」就好,而不用关心具体的业务数据。

如果消费端消费失败了怎么办?

如果有消息消费失败了,则将失败的消息回传给broker,即重新写入commitLog文件,消费者将重新消费;如果消息回传的时候,consumer和broker之间网络断开,则consumer会调用submitConsumeRequestLater()方法,在consumer端进行重新消费,如果仍然消费失败,会不断重试直到达到默认的16次,你可以使用msg.getReconsumeTimes()方法来获取当前重试次数,如果重试次数足够多之后仍然无法消费成功,必须通过工单、日志等方式进行人工干预以让producer事务进行回退处理。

猜你喜欢

转载自blog.csdn.net/haiyanghan/article/details/115337510