深入探究 RocketMQ 事务机制的实现流程,为什么它能做到发送消息零丢失?

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

既然我们已经明确了消息在基于MQ传输的过程中可能丢失的几个地方,那么我们接着就得一步一步考虑如何去解决各个环节丢失消息的问题,首先要解决的第一个问题,就是订单系统推送消息到MQ的过程中,可能消息就丢失了。

之前我们也说过了,可能在订单系统推送消息到MQ的过程中,就因为常见的网络故障之类的问题,导致消息就丢失了,这里我们可以看一下下图中的示意。            而在RocketMQ中,有一个非常强悍有力的功能,就是事务消息的功能,凭借这个事务级的消息机制,就可以让我们确保订单系统推送给出去的消息一定会成功写入MQ里,绝对不会半路就搞丢了。

今天我们就来系统的分析一下RocketMQ的事务消息机制的原理。

2、发送half消息到MQ去,试探一下MQ是否正常

首先作为我们的订单系统而言,假设他收到了一个订单支付成功的通知之后,他必然是需要在自己的订单数据库里做一些增删改操作的,比如更新订单状态之类的。

可能有的朋友会觉得,订单系统不就是先在自己数据库里做一些增删改操作,然后就直接发个消息到MQ去,让其他关注这个订单支付成功消息的系统去从MQ获取消息做对应的处理就可以了么?

事实上还真不是这么简单。

在基于RocketMQ的事务消息机制中,我们首先要让订单系统去发送一条half消息到MQ去,这个half消息本质就是一个订单支付成功的消息,只不过你可以理解为他这个消息的状态是half状态,这个时候红包系统是看不见这个half消息的,然后我们去等待接收这个half消息写入成功的响应通知

我们看下面的图

看到这儿可能有的朋友就开始有点郁闷了,可能有的人觉得你没事儿先发个half消息给MQ干什么?

大家先别着急,你可以想一下,假设你二话不说让订单系统直接做了本地的数据库操作,比如订单状态都更新为了已完成,然后你再发送消息给MQ,结果报出一堆异常,发现MQ挂了。

这个时候,必然导致你没法通过消息通知到红包系统去派发红包,那用户一定会发现自己订单支付了,结果红包没收到。

所以,在这里我们首先第一件事,不是先让订单系统做一些增删改操作,而是先发一个half消息给MQ以及收到他的成功的响应,初步先跟MQ做个联系和沟通

大概这个意思就是说,确认一下MQ还活着,MQ也知道你后续可能想发送一条很关键的不希望丢失的消息给他了!

3、万一要是half消息写入失败了呢?

这里我们先来分析第一种情况,万一你订单系统写half消息给MQ就失败了呢?

可能你发现报错了,可能MQ就挂了,或者这个时候网络就是故障了,所以导致你的half消息都没发送成功,总之你现在肯定没法跟MQ通信了。

这个时候你的订单系统就应该执行一系列的回滚操作,比如对订单状态做一个更新,让状态变成“关闭交易”,同时通知支付系统自动进行退款,这才是正确的做法

因为你订单虽然支付了,但是包括派发红包、发送优惠券之类的后续操作是无法执行的,所以此时必然应该把钱款退还给用户,说交易失败了。

这里给大家插播一个我曾经亲身经历过的一个事情,曾经有一次在一家便利店进行购物的时候,我这里都已经显示扫码支付成功了,但是店员那边说在等待他们系统确认

结果等了一会儿,系统显示后台系统有异常,交易失败了,然后过了一会儿就让支付宝自动退款给我了。

其实这就是类似的例子。

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

接着我们来考虑第二种情况,你的half消息写成功了,这时你应该干什么呢?

这时你的订单系统就应该在自己本地的数据库里执行一些增删改操作了,因为一旦half消息写成功了,就说明MQ肯定已经收到这条消息了,MQ还活着,而且目前你是可以跟MQ正常沟通的。

我们看下面的图,示意了下一步是订单系统执行自己的增删改操作。 

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

接着我们继续看下一种情况,万一订单系统更新自己的数据库失败了怎么办?

比如订单系统的数据库当时也有网络异常,或者数据库挂了,总而言之,就是你想把订单更新为“已完成”这个状态,是干不成了。

这个时候其实也很简单,直接就是让订单系统发送一个rollback请求给MQ就可以了

这个意思就是说,你可以把之前我发给你的half消息给删除掉了,因为我自己这里都出问题了,已经无力跟你继续后续的流程了。

我们看下面的图,我给出了这个示意

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

当然这里可能还有一些订单系统自己的高可用降级的机制需要考虑,比如数据库无法更新了,此时你可能需要在机器本地磁盘文件里写入订单支付失败的记录。

然后你可以开一个后台线程在MySQL数据库恢复之后 ,再把订单状态更新为“已关闭”。不过这个不在我们讨专栏的范围之内。

6、如果订单系统完成了本地事务之后,接着干什么?

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

我们看下面的图

之前我们也提到过了,所谓的half消息实际就是订单支付成功的消息,只不过他的状态是half

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

7、让流程严谨一些:如果发送half消息成功了,但是没收到响应呢?

大致的事务消息的流程是讲完了,但是接着让我们来进行比较严谨的分析

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

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

我们看下面的图的示意

但是这时MQ已经存储下来一条half消息了,那对这个消息怎么处理?

其实RocketMQ这里有一个补偿流程,他会去扫描自己处于half状态的消息,如果我们一直没有对这个消息执行commit/rollback操作,超过了一定的时间,他就会回调你的订单系统的一个接口,问问你说,这个消息到底怎么回事?

你到底是打算commit这个消息还是要rollback这个消息?

我们看下图示意

这个时候我们的订单系统就得去查一下数据库,看看这个订单当前的状态,一下发现订单状态是“已关闭”,此时就知道,你必然是得发送rollback请求给MQ去删除之前那个half消息了!

我们看下图

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

我们再假设一种场景,如果订单系统是收到了half消息写入成功的响应了,同时尝试对自己的数据库更新了,然后根据失败或者成功去执行了rollback或者commit请求,发送给MQ了

结果因为网络故障,导致rollback或者commit请求发送失败了呢?

这个时候其实也很简单,因为MQ里的消息一直是half状态,所以他过了一定的超时时间会发现这个half消息有问题,他会回调你的订单系统的接口

你此时要判断一下,这个订单的状态如果更新为了“已完成”,那你就得再次执行commit请求,反之则再次执行rollback请求。

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

9、停一下脚本想想上面这个流程的意义在哪里?

看到这里我们来停下脚步想想,上面这个流程的意义在哪里呢?

其实很简单,如果你的MQ有问题或者网络有问题,half消息根本都发不出去,此时half消息肯定是失败的,那么订单系统就不会执行后续流程了!

如果要是half消息发送出去了,但是half消息的响应都没收到,然后执行了退款流程,那MQ会有补偿机制来回调找你询问要commit还是rollback,此时你选择rollback删除消息就可以了,不会执行后续流程!

如果要是订单系统收到half消息了,结果订单系统自己更新数据库失败了,那么他也会进行回滚,不会执行后续流程了!

如果要是订单系统收到half消息了,然后还更新自己数据库成功了,订单状态是“已完成”了,此时就必然会发送commit请求给MQ,一旦消息commit了,那么必然保证红包系统可以收到这个消息!

而且即使你commit请求发送失败了,MQ也会有补偿机制,回调你接口让你判断是否重新发送commit请求

总之,就是你的订单系统只要成功了,那么必然要保证MQ里的消息是commit了可以让红包系统看到他!

所以大家可以结合我们的图思考一下上述流程,通过这套事务消息的机制,是不是就可以保证我们的订单系统一旦成功执行了数据库操作,就一定会通知到红包系统去派发红包?至少订单系统到MQ之间的消息传输是不会有丢失的问题了!

本文来自狸猫技术窝专栏《从零开始带你成为消息中间件实战高手》,是作者原子弹大侠开放的试读

Image placeholder
IT头条
未设置
  30人点赞

没有讨论,发表一下自己的看法吧

推荐文章
基于 Hyperf 实现 RabbitMQ + WebSocket 消息推送

#介绍 基于Hyperf+WebSocket+RabbitMQ实现的一个简单大屏幕的消息推送。 #思路 利用WebSocket协议让客户端和服务器端保持有状态的长链接,保存链接上来的客户端id。订阅发

你应该知道的RocketMQ

1.概述在很久之前写过一篇Kafka相关的文章,你需要知道的Kafka,那个时候在业务上更多的是使用的是Kafka,而现在换了公司之后,更多的使用的是Rocketmq,本篇文章会尽力全面的介绍Rock

写数据库同时发mq消息事务一致性的一种解决方案

一、引子《事务注解(@Transactional)引起的数据覆盖故障》一文收到不少反馈。事务里不要有rpc,基本原则,sb封装的太好了,把很多人养傻了,function级别的事务,坑太大。网友一这个是

Java并发编程,深入理解ReentrantLock

ReentrantLock简介ReentrantLock重入锁, 是实现Lock接口的一个类 ,也是在实际编程中使用频率很高的一个锁,支持重入性,表示能够对共享资源能够重复加锁,即当前线程获取该锁再次

万亿级消息背后: 小米消息队列的实践

目录业务背景架构与关键问题性能与资源优化平台化效率小米消息中间件的规划与愿景前文《消息队列价值思考》讲述了消息中间件在企业IT架构中的重要价值,本文将呈现这些价值在落地小米业务过程中的遇到的问题和实践

HDFS 源码解读:HadoopRPC 实现细节的探究

桔妹导读:HDSF作为分布式文件系统,常常涉及DataNode、NameNode、Client之间的配合、相互调用才能完成完整的流程。为了降低节点之间的耦合性,HDFS将节点间的调用抽象成不同的接口,

万字长文:聊聊几种主流Docker网络的实现原理

一、容器网络简介容器网络主要解决两大核心问题:一是容器的IP地址分配,二是容器之间的相互通信。本文重在研究第二个问题并且主要研究容器的跨主机通信问题。实现容器跨主机通信的最简单方式就是直接使用host

老焦专栏 | 用 RACI 模式梳理业务流程,提高业务发布的效率

转载本文需注明出处:微信公众号EAWorld,违者必究。最近经常在不同场合说,技术发展已经进入深水区。IT技术发展已经越来越成熟了,尤其在金融行业,以前是解决从无到有的问题,现在该有的系统都有了,是解

AI:我们能否让“宕机”这个词消失?

今天,即便是许多非从业者也至少对人工智能(AI)有一个大致的了解。从自动驾驶汽车到个人语音助手,人工智能已成为我们日常生活的前沿和中心。但是这些直观展现在我们面前的AI应用并不是现在大多数IT专业人士

陆天炜: GoldenDB事务一致性处理机制优化历程

前言:GoldenDB是中兴通讯推出的一款自研的金融级交易型分布式数据。针对金融行业关注的数据库事务一致性问题,中兴通讯GoldenDB分布式数据库架构师陆天炜,在DTCC2019数据库大会上做了干货

走近科学,探究阿里闲鱼团队通过数据提升Flutter体验的真相

背景闲鱼客户端的Flutter页面已经服务上亿级用户,因此用户体验尤其重要,完善Flutter性能稳定性监控体系,以便及早发现线上性能问题,也可以作为用户体验提升的衡量标准。那么Flutter的性能到

nodejs都能做什么?

Node对一些特殊用例进行优化,提供替代的API,使得V8在非浏览器环境下运行得更好。V8引擎执行Javascript的速度非常快,性能非常好。Node是一个基于ChromeJavaScript运行时

jQuery能做什么?

目的是让开发者更容易地在网站上使用JavaScript。它不是一种单独的编程语言,与JavaScript一起工作。使用jQuery,将用更少的资源做更多的事情。使用jQuery能做什么?jQuery库

爬虫python能做什么?

课程推荐:Python开发工程师--学习猿地精品课--点击进入 python爬虫可以用来做什么? 1、收集数据 python爬虫程序可用于收集数据。这也是最直接和最常用的方法。由于爬虫程序是一个程序,

深入理解 MySQL—锁、事务与并发控制

本文对MySQL数据库中有关锁、事务及并发控制的知识及其原理做了系统化的介绍和总结,希望帮助读者能更加深刻地理解MySQL中的锁和事务,从而在业务系统开发过程中可以更好地优化与数据库的交互。1.MyS

我们走访了900名微软员工,为你揭秘全球最大软件公司的代码评审机制

大数据文摘出品来源:michaelagreiler编译:倪倪、钱天培、毅航全球最大的软件公司之一微软拥有约140,000名员工,其中大约44%,即超过60,000名员工,是工程师。Office、Vis

SpringBoot整合RabbitMQ

SpringBoot整合RabbitMQSpringBoot框架已经提供了RabbitMQ的使用jar包,开发人员在使用RabbitMQ的时候只需要引用jar包简单的配置一下就可以使用RabbitMQ

SpringBoot连接多RabbitMQ源

在实际开发中,很多场景需要异步处理,这时就需要用到RabbitMQ,而且随着场景的增多程序可能需要连接多个RabbitMQ。SpringBoot本身提供了默认的配置可以快速配置连接RabbitMQ,但

自己撸一个 LaraDock(使用 Docker LNMP 部署 PHP 开发环境)

项目简介 DockerLNMP是基于docker-compose开发的运行在Docker上的LNMP开发环境,包含PHP、MySQL、Redis等镜像并支持多版本切换,满足您的学习、开发和测试需求。

Python入门教程_4. 深入 Python 流程控制

除了刚刚介绍的while语句,Python还有一些在其他语言中常见的控制流语句,并做了一些改动。 4.1.if语句 也许最著名的语句是if语句了。 例如: >>>x=int(input("Please

老司机带你用 PHP 实现 Websocket 协议

我为什么会写这篇文章? 当初作为编程小白的我,刚刚从事后台工作,觉得http是个很牛逼的东西,然而后面随着自己深入学习并实践之后,觉得原来和我所想的天壤之别,没大家想象的那么复杂,仅仅是个协议嘛!。后

TypeScript是什么,为什么要使用它?

课程推荐:前端开发工程师--学习猿地精品课程 从历史上看,JavaScript已经成为了在Internet上编写网页和应用程序脚本语言的主要语言。但是否能通过JavaScript创建大型复杂Web应用

使用html-webpack-plugin对HTML文件进行预处理

一、前言先整理一波之前和webpack相关的文章: 使用Webpack对CSS文件进行后处理 基于Webpack的CSSSprites实现方案 Stylus系列——webpack-spritesmit

workerman源码-workerman启动流程

前面我们跟着代码看了一遍workerman的初始化流程.但对于如何监听端口.等操作还没有具体的实现.我们这次就来看一下.workerman是如何监听端口并运行的.runAll在前面我们初始化方法过后,

Java异常日志堆栈丢失原因

推荐课程:Java开发工程师--学习猿地--送7个上线商业项目 查日志是我们排查问题的重要手段之一,直接又方便。其中异常日志堆栈信息可以让我们快速的发现问题所在,但稍微有点经验的开发应该会遇到过日志堆