您的位置:首页 > 其它

分布式事务的解决方案

2017-07-01 21:07 323 查看
随着大型网站的各种高并发访问、海量数据处理等场景越来越多,如何实现网站的高可用、易伸缩、可扩展、安全等目标就显得越来越重要。为了解决这样一系列问题,大型网站的架构也在不断发展。提高大型网站的高可用架构,不得不提的就是分布式。本文将简单介绍如何有效的解决分布式的一致性问题,其中包括什么是分布式事务,二阶段提交和三阶段提交。

一、普通事务与分布式事务

1.1普通事务

事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。当事务被提交给了DBMS(数据库管理系统),则DBMS(数据库管理系统)需要确保该事务中的所有操作都成功完成且其结果被永久保存在数据库中,如果事务中有的操作没有成功完成,则事务中的所有操作都需要被回滚,回到事务执行前的状态;同时,该事务对数据库或者其他事务的执行无影响,所有的事务都好像在独立的运行。

事务的ACID特性:

* 原子性(A):所谓的原子性就是说,在整个事务中的所有操作,要么全部完成,要么全部不做,没有中间状态。对于事务在执行中发生错误,所有的操作都会被回滚,整个事务就像从没被执行过一样。

* 一致性(C):事务的执行必须保证系统的一致性,就拿转账为例,A有500元,B有300元,如果在一个事务里A成功转给B50元,那么不管并发多少,不管发生什么,只要事务执行成功了,那么最后A账户一定是450元,B账户一定是350元。

* 隔离性(I):所谓的隔离性就是说,事务与事务之间不会互相影响,一个事务的中间状态不会被其他事务感知。

* 持久性(D):所谓的持久性,就是说一单事务完成了,那么事务对数据所做的变更就完全保存在了数据库中,即使发生停电,系统宕机也是如此。

1.2 分布式事务

分布式事务顾名思义就是在分布式环境下运行的事务,对于分布式事务来说,事务的每个操作步骤是运行在不同机器上的服务的。分布式事务处理的关键是必须有一种方法可以知道事务在任何地方所做的所有动作,提交或回滚事务的决定必须产生统一的结果(全部提交或全部回滚)。

在现如今的大型互联网平台中,基本上都是采用分布式的SOA架构,所以分布式事务是非常常见的。比如一个电商平台的下单场景,一般对于用户下单会有两个步骤,一是订单业务采取下订单操作,二是库存业务采取减库存操作,但在大型电子商务平台上这两个业务一般会运行在不同的机器上,这就是一个典型的分布式事务场景。还有一个常见的场景就是支付宝向余额宝转账,而支付宝和余额宝不是一个系统,怎么保证这两个系统之间的一致性就是分布式事务所关注的问题。

1.2.1 分布式系统CAP定律

为了更方便的理解分布式事务,这里插一个分布式系统的CAP定律。在分布式系统里面有一个CAP定律,这个定理的内容是指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼。

* 一致性(C):每次访问都能获得最新数据但可能会收到错误响应

* 可用性(A):每次访问都能收到非错响应,但不保证获取到最新数据

* 分区容错性(P):在任意分区网络故障的情况下系统仍能继续运行

CAP定律是NoSQL数据库的基石,而CAP理论就是说在分布式存储系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的。所以我们只能在一致性和可用性之间进行权衡,没有NoSQL系统能同时保证这三点,而NoSQL则选了可用性。NoSQL数据库主要做的是简单的键值查询,因此NoSQL系统通常注重性能和扩展性,而非事务机制(事务就是强一致性的体现)

1.2.2 一致性理论

通过上面介绍我们知道,对分布式系统来说,CAP 理论告诉我们。为了下一步讨论分布式事务特性,先简单介绍下数据一致性的基础理论。

* 强一致:当更新操作完成之后,任何多个后续进程或者线程的访问都会返回最新的更新过的值。这种是对用户最友好的,就是用户上一次写什么,下一次就保证能读到什么。根据 CAP 理论,这种实现需要牺牲可用性。

* 弱一致性:系统并不保证续进程或者线程的访问都会返回最新的更新过的值。系统在数据写入成功之后,不承诺立即可以读到最新写入的值,也不会具体的承诺多久之后可以读到(也可能读不出来)。

* 最终一致性:弱一致性的特定形式。系统保证在没有后续更新的前提下,系统最终返回上一次更新操作的值。在没有故障发生的前提下,不一致窗口的时间主要受通信延迟,系统负载和复制副本的个数影响。DNS 是一个典型的最终一致性系统。

1.2.3 分布式事务特性—最终一致性

在互联网大型分布式平台场景中,为了保障系统的可用性,他们一般会把强一致性的需求转换成最终一致性的需求。所以,对于大部分分布式事务场景,我们仅需要保证最终一致性即可。

二.分布式事务解决方案

2.1 本地消息(事务)表

这种实现方式的思路,其实是源于ebay经典的BASE (basically available, soft state, eventually consistent)方案。其基本的设计思想是将远程分布式事务拆分成一系列的本地事务。举个例子。假设系统中有以下两个表

user(id, name, amt_sold, amt_bought)

transaction(xid, seller_id, buyer_id, amount)

其中user表记录用户交易汇总信息,transaction表记录每个交易的详细信息。

begin;
INSERT INTO transaction VALUES(xid, $seller_id, $buyer_id, $amount);
UPDATE user SET amt_sold = amt_sold + $amount WHERE id = $seller_id;
UPDATE user SET amt_bought = amt_bought + $amount WHERE id = $buyer_id;
commit;


即在transaction表中记录交易信息,然后更新卖家和买家的状态。假设transaction表和user表存储在不同的节点上,那么上述事务就是一个分布式事务。对于一个分布式事务,我们考虑将其拆分两个独立的子事务,每个子事务都有一张本地消息表。对于transaction表插入的业务,先启动一个事务,插入transaction表后,并不直接去更新user表,而是将更新表以消息的形式插入到本地消息表message。

begin;
INSERT INTO transaction VALUES(xid, $seller_id, $buyer_id, $amount);
put_to_queue “update user(“seller”, $seller_id, amount);
put_to_queue “update user(“buyer”, $buyer_id, amount);
commit;


对于user表更新业务,也需要新建一个message_applied(msg_id)表来记录被成功应用的消息,然后发起一个异步任务轮询队列内容进行处理:

for each message in queue
begin;
//先检查此消息是否已处理
SELECT count(*) as cnt FROM message_applied WHERE msg_id = message.id;
if cnt = 0 then
//若没有处理,对user表做更新操作
if message.type = “seller” then
UPDATE user SET amt_sold = amt_sold + message.amount WHERE id = message.user_id;
else
UPDATE user SET amt_bought = amt_bought + message.amount WHERE id = message.user_id;
end
//插入应用的消息,标记此消息已处理
INSERT INTO message_applied VALUES(message.id);
end
commit;

if 上述事务成功
dequeue message
DELETE FROM message_applied WHERE msg_id = message.id;
end
end


我们来仔细分析一下上面代码:

1. 消息队列与transaction使用同一实例,因此第一个事务不涉及分布式操作;

2. message_applied与user表在同一个实例中,也能保证一致性;

3. 第二个事务结束后,dequeue message之前系统可能出故障,出故障后系统会重新从消息队列中取出这一消息,但通过message_applied表可以检查出来这一消息已经被应用过,跳过这一消息实现正确的行为;

4. 最后将已经成功应用,且已经从消息队列中删除的消息从message_applied表中删除,可以将message_applied表保证在很小的状态(不清除也是可以的,不影响系统正确性)。由于消息队列与message_applied在不同实例上,dequeue message之后,将对应message_applied记录删除之前可能出故障。一但这时出现故障,message_applied表中会留下一些垃圾内容,但不影响系统正确性,另外这些垃圾内容也是可以正确清理的。

2.2 两阶段协议

为了解决分布式一致性问题,前人在性能和数据一致性的反反复复权衡过程中总结了许多典型的协议和算法。其中比较著名的有二阶提交协议(Two Phase Commitment Protocol)、三阶提交协议(Three Phase Commitment Protocol)和Paxos算法。



分布式事务最常用的解决方案就是二阶段提交(Two-phaseCommit,2PC)。在分布式系统中,每个节点虽然可以知晓自己的操作时成功或者失败,却无法知道其他节点的操作的成功或失败。当一个事务跨越多个节点时,为了保持事务的ACID特性,需要引入一个作为协调者的组件来统一掌控所有参与者节点的操作结果并最终指示这些节点是否要把操作结果进行真正的提交。因此,二阶段提交的算法思路可以概括为:参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。

二阶段提交算法的成立基于以下假设:

* 该分布式系统中,存在一个节点作为协调者(Coordinator),其他节点作为参与者(Cohorts)。且节点之间可以进行网络通信。

* 所有节点都采用预写式日志,且日志被写入后即被保持在可靠的存储设备上,即使节点损坏不会导致日志数据的消失。

* 所有节点不会永久性损坏,即使损坏后仍然可以恢复。

所谓的两个阶段是指

准备阶段(投票阶段) prepare

提交阶段(执行阶段) commit

准备阶段

事务协调者给每个参与者发送Prepare消息,每个参与者要么直接返回失败,要么在本地执行事务,写本地的redo和undo日志,但不提交,到达一种“万事俱备,只欠东风”的状态。

可以进一步将准备阶段分为以下三个步骤:

1.协调者节点向所有参与者节点询问是否可以执行提交操作(vote),并开始等待各参与者节点的响应。

2.参与者节点执行询问发起为止的所有事务操作,并将Undo信息和Redo信息写入日志。(注意:若成功这里其实每个参与者已经执行了事务操作)

3.各参与者节点响应协调者节点发起的询问。如果参与者节点的事务操作实际执行成功,则它返回一个”同意”消息;如果参与者节点的事务操作实际执行失败,则它返回一个”中止”消息。

提交阶段

如果协调者收到了参与者的失败消息或者超时,直接给每个参与者发送回滚(Rollback)消息;否则,发送提交(Commit)消息;参与者根据协调者的指令执行提交或者回滚操作,释放所有事务处理过程中使用的锁资源。(注意:必须在最后阶段释放锁资源)

接下来分两种情况分别讨论提交阶段的过程。当协调者节点从所有参与者节点获得的相应消息都为同意”时:

1. 协调者节点向所有参与者节点发出”正式提交(commit)”的请求。

2. 参与者节点正式完成操作,并释放在整个事务期间内占用的资源。

3. 参与者节点向协调者节点发送”完成”消息。

4. 协调者节点受到所有参与者节点反馈的”完成”消息后,完成事务。

如果任一参与者节点在第一阶段返回的响应消息为”中止”,或者协调者节点在第一阶段的询问超时之前无法获取所有参与者节点的响应消息时:

1. 协调者节点向所有参与者节点发出”回滚操作(rollback)”的请求。

2. 参与者节点利用之前写入的Undo信息执行回滚,并释放在整个事务期间内占用的资源。

3. 参与者节点向协调者节点发送”回滚完成”消息。

4. 协调者节点受到所有参与者节点反馈的”回滚完成”消息后,取消事务。

不管最后结果如何,第二阶段都会结束当前事务。二阶段提交看起来确实能够提供原子性的操作,但是不幸的事,二阶段提交还是有几个缺点的:

1. 同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。

2. 单点故障。由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)

3. 数据不一致。在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。

4. 二阶段无法解决的问题:协调者再发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。

由于二阶段提交存在着诸如同步阻塞、单点问题、脑裂等缺陷,所以,研究者们在二阶段提交的基础上做了改进,提出了三阶段提交。如果第一阶段完成后,参与者在第二阶没有收到决策,那么数据结点会进入“不知所措”的状态,这个状态会block住整个事务。也就是说,协调者Coordinator对于事务的完成非常重要,Coordinator的可用性是个关键。 因些,我们引入三段提交,三段提交在Wikipedia上的描述如下,他把二段提交的第一个段准备阶段成了两段:询问,然后再锁资源,最后真正提交。

2.3 三阶段协议

三阶段提交(Three-phase commit),也叫三阶段提交协议(Three-phase commit protocol),是二阶段提交(2PC)的改进版本。



与两阶段提交不同的是,三阶段提交有两个改动点。

引入超时机制。同时在协调者和参与者中都引入超时机制。

在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。

也就是说,除了引入超时机制之外,3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。

CanCommit阶段

3PC的CanCommit阶段其实和2PC的准备阶段很像。协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。

1. 事务询问 协调者向参与者发送CanCommit请求。询问是否可以执行事务提交操作。然后开始等待参与者的响应。

2. 响应反馈 参与者接到CanCommit请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回Yes响应,并进入预备状态,否则反馈No。

PreCommit阶段

协调者根据参与者的反应情况来决定是否可以记性事务的PreCommit操作。根据响应情况,有以下两种可能。

假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务的预执行。

1. 发送预提交请求 协调者向参与者发送PreCommit请求,并进入Prepared阶段。

2. 事务预提交 参与者接收到PreCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中。

3. 响应反馈 如果参与者成功的执行了事务操作,则返回ACK响应,同时开始等待最终指令。

假如有任何一个参与者向协调者发送了No响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断。

1. 发送中断请求 协调者向所有参与者发送abort请求。

2. 中断事务 参与者收到来自协调者的abort请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断。

doCommit阶段

该阶段进行真正的事务提交,也可以分为以下两种情况。

执行提交

1. 发送提交请求 协调接收到参与者发送的ACK响应,那么他将从预提交状态进入到提交状态。并向所有参与者发送doCommit请求。

2. 事务提交 参与者接收到doCommit请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源。

3. 响应反馈 事务提交完之后,向协调者发送Ack响应。

4. 完成事务 协调者接收到所有参与者的ack响应之后,完成事务。

中断事务 协调者没有接收到参与者发送的ACK响应(可能是接受者发送的不是ACK响应,也可能响应超时),那么就会执行中断事务。

1. 发送中断请求 协调者向所有参与者发送abort请求

2. 事务回滚 参与者接收到abort请求之后,利用其在阶段二记录的undo信息来执行事务的回滚操作,并在完成回滚之后释放所有的事务资源。

3. 反馈结果 参与者完成事务回滚之后,向协调者发送ACK消息

4. 中断事务 协调者接收到参与者反馈的ACK消息之后,执行事务的中断。

在doCommit阶段,如果参与者无法及时接收到来自协调者的doCommit或者rebort请求时,会在等待超时之后,会继续进行事务的提交。(其实这个应该是基于概率来决定的,当进入第三阶段时,说明参与者在第二阶段已经收到PreCommit请求,那么协调者产生PreCommit请求的前提条件是他在第二阶段开始之前,收到所有参与者的CanCommit响应都是Yes。(一旦参与者收到了PreCommit,意味他知道大家其实都同意修改了)所以,一句话概括就是,当进入第三阶段时,由于网络超时等原因,虽然参与者没有收到commit或者abort响应,但是他有理由相信:成功提交的几率很大。)

2PC与3PC的区别

三段提交的核心理念是:在询问的时候并不锁定资源,除非所有人都同意了,才开始锁资源。理论上来说,如果第一阶段所有的结点返回成功,那么有理由相信成功提交的概率很大。这样一来,可以降低参与者Cohorts的状态未知的概率。也就是说,一旦参与者收到了PreCommit,意味他知道大家其实都同意修改了。相对于2PC,3PC主要解决的单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit。这一点很重要。而不会一直持有事务资源并处于阻塞状态。但是这种机制也会导致数据一致性问题,因为,由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作。这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。

2.4 采用消息中间件

2.4.1 消息中间件介绍

消息中间件也可称作消息系统(MQ),它本质上是一个暂存转发消息的一个中间件。在分布式应用当中,我们可以把一个业务操作转换成一个消息,比如支付宝转账余额宝操作,支付宝系统执行减掉账户金额操作之后向消息系统发一个消息,余额宝系统订阅这条消息然后进行增加账户金额操作。

2.4.2 两大类消息中间件

尽管存在各种各样的消息系统,每个消息系统都有各自的消息路由方式,但总体上有两种类型的消息系统:queue和topic,它们也各自关联着一种特定的消息处理模型:点对点(point-to-point/queue)和发布/订阅(publish/subscribe/topic)。

在点对点模式中,每个消息只有一个发送者和一个接收者。在点对点模型中, 消息broker会把消息放入一个queue。当一个接收者请求下一个消息时,消息会被从queue中取出并传递给接收者。因为消息从queue中取出便会被移除,所以这保证了一个消息只能有一个接收者。

在发布/订阅模式中,消息是被发送到topic中的。就像queue一样,很多接收者可以监听同一个topic,但是与queue每个消息只传递给一个topic的接收者,订阅了同一个topic的所有接收者都会收到消息的拷贝。从发布/订阅的名字中我们也可看出,发布者发布一条消息,所有订阅者都能收到,这就是发布订阅模式最大的特性。

2.4.3 消息的可靠性

上面讲到了我们可以利用消息中间件化解分布式事务领域内的问题。但是在分布式业务之间引入消息中间件还存在一个问题,就是如何保证业务系统与消息系统之间消息传递的可靠性。在分布式业务场景中,可靠性永远是最重要的。如果采用消息中间件,保证业务之间消息的发送与接收的可靠性是非常重要的问题。

当采用消息中间件时,消息的可靠性体现在两个方面:

消息的发送者端 (生产者):发送者端完成操作后一定能将消息成功发送到消息系统。

消息的接收者端(消费者):消费者端仅且能够从消息系统成功消费一次消息。

下面我们先介绍一下发送者端的消息可靠性:

a. 利用本地事务

与2.1节介绍的类似,主要原理是通过本地消息表做中间表。在数据库中建一张消息表,将消息数据与业务数据保存在同一数据库实例里,这样就可以利用本地数据库的事务机制,保证业务操作和保存消息完全一致:

Begin transaction
update A set amount=amount-10000 where userId=1;
insert into message(userId, amount,status) values(1, 10000, 1);
End transaction
commit;


通过本地事务一定能保证扣完款后消息能保存下来。当上述事务提交成功后,我们再通过消息中间件实时扫描这张消息表,把消息表中的数据转移到消息中间件,若转移消息成功则删除消息表中的数据,若转移失败继续重试。

b. 非事务性的消息中间件

通常情况下,在使用非事务消息支持的MQ产品时,我们很难将业务操作与对MQ的操作放在一个本地事务域中管理。通俗点描述,以“支付宝转账”为例,我们很难保证在支付宝扣款完成之后对MQ投递消息的操作就一定能成功。

先从消息生产者这端来分析,请看伪代码:



根据上述代码及注释,我们来分析下可能的情况:

1. 操作数据库成功,向MQ中投递消息也成功,皆大欢喜

2. 操作数据库失败,不会向MQ中投递消息了

3. 操作数据库成功,但是向MQ中投递消息时失败,向外抛出了异常,刚刚执行的更新数据库的操作将被回滚

所以这种方式基本上能保证发送者发送消息的可靠性。

c. 支持事务的消息中间件

除了上面介绍的通过异常捕获和回滚的方式外,还有没有其他的思路呢?

阿里巴巴的RocketMQ中间件就支持一种事务消息机制,能够确保本地操作和发送消息达到本地事务一样的效果。

- 第一阶段,RocketMQ在执行本地事务之前,会先发送一个Prepared消息,并且会持有这个消息的地址

- 第二阶段,执行本地事物操作

- 第三阶段,确认消息发送,通过第一阶段拿到的地址去访问消息,并修改状态,如果本地事务成功,则修改状态为已提交,否则修改状态为已回滚

整个过程如下图所示:



但是如果第三阶段的确认消息发送失败了怎么办?RocketMQ会定期扫描消息集群中的事物消息,如果发现了prepare状态的消息(既不是提交也不是回滚的中间状态),它会向消息发送者确认本地事务是否已执行成功,如果成功是回滚还是继续发送确认消息呢。RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。

2.4.4 接收者端保证消息的可靠性

接收者端消息的可靠性要简单一些,可以从以下三方面来看。

a. 保证消费者不重复消费消息

什么情况下会产生重复消费的情况呢?比如消费者接收到消息并完成了本地事务(如减库存操作),此时还要返回消息系统一个通知,告诉消息系统把这条消息删除掉。然后不巧恰恰在此时网络出现了问题,返回给消息系统删除消息的通知丢失,则消费者端会再次消费这条消息,导致了重复消费。

那么该怎么处理这种情况呢?

1. 消费端处理消息的业务逻辑保持幂等性

2. 保存消费者消费的状态即保证每条消息都有唯一编号,并且保证消息处理成功后一定能写入到一张去重日志表

关于第1条幂等性,只要业务操作保持幂等性,不管来多少条重复消息,最后处理的结果都一样。这个很明显应该在消费端实现,不属于消息系统要实现的功能。

关于第2条,原理就是利用一张日志表来记录已经处理成功的消息的ID,如果新到的消息ID已经在日志表中,那么就不再处理这条消息。当然这个可以消息系统实现,也可以业务端实现。正常情况下出现重复消息的概率不一定大,且由消息系统实现的话,肯定会对消息系统的吞吐量和高可用有影响,所以,一般消费状态的保存都是在消费者端进行保存。

RocketMQ、Kafka都不保证消息不重复,如果你的业务需要保证严格的不重复消息,那么就需要在我们的业务端保存消费状态,进行去重。

2.4.5 解决消费者消费超时

再回到转账的例子,如果Bob的账户的余额已经减少,且消息已经发送成功,Smith端开始消费这条消息,这个时候就会出现消费失败和消费超时两个问题?解决超时问题的思路就是一直重试,直到消费端消费消息成功,整个过程中有可能会出现消息重复的问题,按照前面的思路解决即可。

2.4.6 解决消费失败:报警系统+人工处理

上面基本上可以解决超时问题, 但是如果消费失败怎么办?比如系统自身有bug或者程序逻辑有问题,那么重试1W次那也是无济于事的。 大家可以考虑一下,如果按照事务的流程,如果事务中的某个步骤操作失败了的话,就要回滚之前的所有操作。如果消息系统要实现这个回滚流程的话,系统复杂度将大大提升,且很容易出现Bug,估计出现Bug的概率会比消费失败的概率大很多。而且一般通过消息系统的处理流程都是一个异步操作,也就是说,但当用户下单时我们不会等到整个流程完成之后才返回给用户结果,而是直接返回给用户下单成功的结果,后端再慢慢处理。如果我们进行回滚操作的话,那么就会出现用户明明下单成功了过段时间一看又失败了这种情况,这是不允许的。

所以针对消费失败这种情况,最好的办法就是通过报警系统及时发现失败情况然后再人工处理。其实为了交易系统更可靠,我们一般会在类似交易这种高级别的服务代码中,加入详细日志记录的,一旦系统内部引发类似致命异常要及时通过短信(钉钉、邮件)通知给业务放。同时,应该设计一个报警系统在后台实时扫描和分析此类日志,检查出这种特殊的情况,通过短信(钉钉、邮件)及时通知相关人员。

文章参考:

关于分布式事务、两阶段提交协议、三阶提交协议

分布式开放消息系统(RocketMQ)的原理与实践
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: