27 发送消息零丢失方案:RocketMQ事务消息的实现流程分析

Posted 鮀城小帅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了27 发送消息零丢失方案:RocketMQ事务消息的实现流程分析相关的知识,希望对你有一定的参考价值。

1.解决消息丢失的第一个问题:订单系统推送消息丢失

当前的问题,在订单系统推送消息到MQ的过程中,就因为常见的网络故障之类的问题,导致消息就丢失了。

在RocketMQ中,有这么一个功能,就是事务消息的功能,凭借这个事务级的消息机制,就可以让我们确保订单系统推送出去的消息一定会成功写入MQ里,绝对不会半路就搞丢了。

2.事务消息:half消息

在基于RocketMQ的事务消息机制中,我们首先要让订单系统去发送一条half消息到MQ去,这个half消息本质就是一个订单支付成功的消息,只不过可以理解为他这个消息的状态就是half状态,这个时候红包系统(消费者)是看不见这个half消息的。

然后我们在订单系统(生产者)这边等待接收这个half消息写入成功的响应通知,如下图:

对于订单系统来说,它需要等待half消息返回写入成功的结果才执行订单数据落库、更新逻辑等操作。

这么做是为了避免当订单系统在更新已完成后再发送消息给MQ,结果报出一堆异常,发现MQ挂了。那么, 这必然导致无法通过消息通知到红包系统去派发红包,那用户一定会发现自己订单支付了,结果红包却没收到。

half消息的作用,让订单系统(生产者)确认一下MQ还活着,MQ也知道你后续可能要发送一条很关键的不希望丢失的消息给他。

3.half消息写入失败场景

订单系统写half消息给MQ失败,原因有可能是网络故障灯因素。最终导致的就是half消息没能成功发送到MQ,这也就意味着订单系统肯定没法跟MQ通信了。

在订单系统中,订单支付了,但是half消息发送失败,导致后续的派发红包、发送优惠券等操作无法执行,所以就必须把钱款退还给用户,说交易失败了。

4.half消息写入成功之后,订单系统完成自己的任务

当half消息写成功了,就说明MQ肯定已经收到这条消息了,MQ还活着,那么订单系统就可以跟MQ正常沟通了。

此时,订单系统应该在自己本地的数据库里执行一些增删改操作。

5.如果订单系统的本地事务执行失败了怎么办?

订单系统更新自己的数据库失败,那么就会进行事务回滚。这时候就无法按照预想的将订单更新为“已完成”的状态。

这时候,需要让订单系统发送一个rollback请求给MQ。这个请求会告诉MQ将之前生产者发给MQ的half消息给删除掉,因为订单系统(生产者)这里出问题了,无法执行后续的流程。

当订单系统(生产者)发送rollback请求给MQ删除那个half消息之后,订单系统就必须走后续的回退流程了,也就是通知支付系统退款。

6.如果订单系统完成了本地事务之后,发送commit

订单系统完成了本地的事务操作,比如把订单状态更新为“已完成”了,此时订单服务(生产者)需要发送一个commit请求给MQ,要求让MQ对之前的half消息进行commit操作,让红包系统可以看见这个订单支付成功消息。

前面发的half消息实际就是订单支付成功的消息,只不过他的状态是half。

当该消息是half状态的时候,红包系统是看不见他的,没法获取到这条消息,必须等到订单系统执行commit请求,消息被commit之后,红包系统才可以看到和获取这条消息进行后续处理。

7.如果发送half消息成功了,但是没收到响应呢?

如果我们把half消息发送给MQ了,MQ给保存下来了,但是MQ返回给我们的响应我们没收到,此时会发生什么问题?

答:这个时候我们没收到响应,可能就会网络超时报错,也可能直接有其他的异常错误,这个时候订单系统会误以为是发送half消息到MQ失败了,订单系统就直接会执行退款流程了,订单状态也会标记为 “已关闭” 。

MQ补偿机制

当MQ已经存储下来一条half消息之后,RocketMQ会有一个补偿流程,该流程会去扫描自己处于half状态的消息,如果生产者一直没有对该消息执行commit/rollback操作,超过了一定的时间,它就会回调订单系统的一个接口。

该接口用于询问订单系统(生产者),对该half消息,生产者是打算commit还是要进行rollback操作?

订单系统(生产者)在接口中就会执行逻辑,去查一下数据库,看看这个订单当前的状态,此时发现订单状态是“已关闭” , 此时就知道,需要发送rollback请求给MQ去删除之前那个half消息了。

8.如果rollback或者commit发送失败了呢?

发送失败的问题

由于网络故障等问题,是可能导致rollback或者commit请求发送失败了的。

补偿机制

场景1:MQ里的消息一直是half状态的,当他过了一定的超时时间就会发现这个half消息有问题,然后去回调订单系统(生产者)的接口。

此时,在订单系统(生产者)会判断,如果该订单的状态更新为了“已完成” ,那么订单系统就要再次执行commit请求,反之则再次执行 rollback请求。这其实就是在进行重试了。

本质上这个MQ的回调就是一个补偿机制,如果你的half消息响应没收到,或者rollback、commit请求没发送成功,它都会来找你问问对half消息后续如何处理。

场景2:如果订单系统收到了half消息写入成功的响应了,同时尝试对自己的数据库更新了,然后根据失败或者成功去执行了rollback或者commit请求,发送给MQ了。这时候mq突然宕机了,导致rollback或者commit请求发送失败。

这时候就等mq自己重启了,在重启之后他会扫描half消息,然后还是通过上面说到的补偿机制,去回调订单系统(生产者)的接口。

8.总结:

在订单系统必然成功的前提下,需要保证MQ里的消息是commit了才可以让红包系统看到他。对订单系统(生产者)发送消息的不丢失是通过发送half消息后的返回结果来决定,如果有MQ返回了half消息的确认写入信息,那么只要本地事务执行成功就能保证订单系统(生产者)的执行是成功的。但这个过程还需要注意,网络延迟等问题会导致在订单系统完成后返回的commit请求给MQ发送失败,通过补偿机制可以在确定half消息写入MQ成功的情况下,保证从生产者到MQ的可靠消息最终一致性。

以上是关于27 发送消息零丢失方案:RocketMQ事务消息的实现流程分析的主要内容,如果未能解决你的问题,请参考以下文章

29 除了使用事务消息方案,还有什么解决发送消息零丢失的方案

RabbitMQ,RocketMQ,Kafka 事务性,消息丢失和重复发送处理策略

分布式事务解决方案 | Seata | 本地消息表 | 事务消息 | 最大努力通知 | 消息丢失重复消费堆积有序

RocketMQ - Consumer消息零丢失方案

分布式事务解决方案 | Seata | 本地消息表 | 事务消息 | 最大努力通知 | 消息丢失重复消费堆积 有序| 缓存数据库一致性

RocketMQ 不丢失消息的方式