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

Posted 鮀城小帅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了29 除了使用事务消息方案,还有什么解决发送消息零丢失的方案相关的知识,希望对你有一定的参考价值。

1.事务消息方案的弊端

在RocketMQ中,生产者发送消息的时候,可能存在消息的丢失,即消息根本没有进入到MQ就丢了。

前面说过了,通过使用RocketMQ事务消息机制去发送消息到MQ,一定是可以保证消息必然发送到MQ的,不会丢。

但是,要注意的是,这一整个流程中,先得发送half消息,完了生产者还得发送rollback or commit的请求,要是中间有点什么问题,MQ还得回调生产者的接口。

这种复杂的机制很可能导致整体性能的拉低,从而导致吞吐量降低。

2.基于重试机制来确保消息到达MQ

当MQ收到消息之后写入本地磁盘文件了,即使只是写入os cache缓存的情况下,MQ就相当于是将消息写入自己本地存储了,它就会返回响应给订单系统(生产者)。

此时只要生产者在代码中发送消息到MQ之后,同步等待MQ返回响应给我们,一直等待,如果半路中有网络异常或者MQ内部异常,我们肯定会受到一个异常,比如网络错误,或者请求超时之类的。

重试发送消息

订单系统(生产者)在收到异常之后,就认为消息到MQ发送失败了,然后再次重试尝试发送消息到MQ,接着再次同步等待MQ返回响应给我们,这样一直反复重试。

如果短时间网络异常导致消息一直没法发送,那么只要不停的重试,网络一旦恢复了,消息就可以发送到MQ了。

而如果在反复重试多次后发现一直没法把消息投递到MQ,此时就可以考虑直接让订单系统(生产者)回滚之前的流程,比如发起退款流程,判定本次订单支付交易失败了。

基于这种 “同步发送消息 + 反复重试” 的方案,是可以保证消息一定可以投递到MQ中的。

3.如何保证消息发送到MQ失败时,本地事务一定回滚

(1)本地事务与推送消息逻辑分开

这里的分开是指,先执行订单本地事务,然后再发送消息到MQ去。

在上述伪代码中,先执行订单本地事务,接着发送消息到MQ,如果订单本地事务执行失败了,则不会继续发送消息到MQ了。

如果订单事务执行成功了,发送MQ失败了,自动进行几次重试,重试如果一直失败,就回滚订单事务。

问题:当刚执行完成了订单本地事务了,结果还没等生产者发送消息到MQ,此时订单系统就突然崩溃了。

这就会导致订单状态可能已经修改为了“已完成” , 但是消息却没发送到MQ去。在这种场景中,写好的多次重试发送MQ之类的代码根本没有机会执行。

而此时订单本地事务还已经执行成功了,消息没发送出去,红包系统没机会派发红包,就导致了用户支付成功了,结果看不到自己的红包。

(2)把订单本地事务和重试发送MQ消息放到一个事务代码中

在上述的伪代码中,对payOrderSuccess()方法加入了事务。那么,在这个事务方法中,即使执行了orderService.finishOrderPay(),但也只是执行了增删改SQL语句,并没有提交订单本地事务。

如果发送MQ消息失败了,而且多次重试还不成功,就抛出异常会自动回滚订单本地事务;

如果刚执行了orderService.finishOrderPay(),结果订单系统直接崩溃了,此时订单本地事务会回滚,因为根本没提交过。

这个方案可以很好的解决前面的MQ发送失败,但是订单事务还是提交了的问题。

弊端:当订单系统卡在多次重试MQ的代码那里时,可能耗费好几秒钟,此时回调通知你的系统早就等不及可能都超时异常了。

另外,把重试MQ的代码放在这个逻辑里,可能会导致订单系统的这个接口性能很差。

4. 本地事务回滚一定可靠吗

上述代码中,虽然在方法上加了事务注解,但是代码里还有更新Redis缓存和Elasticsearch数据的代码逻辑,如果订单系统已经完成了订单数据库更新、Redis缓存更新、ES数据更新,结果没发送MQ时订单系统就崩溃了。

这里的问题在于,虽然订单数据库的操作会回滚,但是Redis、Elasticsearch中的数据更新并不会自动回滚。此时数据还是会不一致。

由此可见,完全寄希望于本地事务自动回滚是不现实的。

5.保证业务系统一致性的最佳方案:基于RocketMQ的事务消息机制

真正要保证消息一定投递到MQ,同时保证业务系统之间的数据完全一致,最佳的方案还是用基于RocketMQ的事务消息机制。

基于这个方案落地之后,就可以保证订单系统的本地事务一旦成功,那么必然会投递消息到MQ去,通知红包系统去派发红包,保证业务系统的数据是一致的。

而且整个过程中,不需要进行长时间的阻塞和重试。

如果half消息发送失败了,就直接回滚整个流程。如果half消息发送成功了,后续的rollback或者commit发送失败了,也不需要自己去卡在那里反复重试,可以直接让代码结束即可,因为后续MQ会过来回调生产者的接口来判断执行rollback or commit的。

以上是关于29 除了使用事务消息方案,还有什么解决发送消息零丢失的方案的主要内容,如果未能解决你的问题,请参考以下文章

33 基于RocketMQ设计的全链路消息零丢失方案总结

是否可以通过 GTalk Intent 发送消息?

RabbitMQ事务和Confirm发送方消息确认——深入解读

java微服务架构的分布式事务解决方案

分布式事务中常见的三种解决方案

分布式事务中常见的三种解决方案