Fork me on GitHub

【真实生产案例】消息中间件如何处理消费失败的消息?

爱钓鱼的桌子哥,资深架构师
先后工作于滴滴、百度、字节跳动等国内一线互联网大厂,从事基础架构相关工作。带领团队设计与构建了大规模的分布式存储系统、分布式消息中间件、分布式数据库,对分布式架构设计、系统高可用体系构建、基础中间件架构都有丰富的经验。

1、消息中间件在生产系统中的使用

下图是一个非常典型的生产环境的问题,很多公司都会在生产系统里使用MQ,即消息队列。

也就是说,一个系统跟另外一个系统之间进行通信的时候,假如系统A希望发送一个消息给系统B,让他去处理。

但是系统A不关注系统B到底怎么处理或者有没有处理好,所以系统A把消息发送给MQ,然后就不管这条消息的“死活”了,接着系统B从MQ里消费出来处理即可。

至于怎么处理,是否处理完毕,什么时候处理,都是系统B的事儿,与系统A无关。

上述过程,可以通过下图看的很清晰:

这样的一种通信方式,就是所谓的“异步”通信方式

对于系统A来说,只要把消息发给MQ,然后系统B就会异步的去进行处理了,系统A不需要“同步”的等待系统B处理完。

这样的好处是什么呢?

两个字:解耦

系统A要跟系统B通信,但是他不需要关注系统B如何处理的一些细节。我们来举几个例子说明:

比如,A不需要关注B什么时候处理完,这样假如系统B处理一个消息要耗费10分钟也不关系统A的事儿。

否则,系统A直接调用系统B的接口,系统B一下子处理了10分钟怎么办?难不成系统A也阻塞等待10分钟?

再比如,系统A不需要关注系统B处理成功与否,即使系统B处理失败了,也是系统B自己去考虑这个场景和重新尝试处理。

否则如果系统调用系统B的接口,万一处理失败了报错了,系统A受到一个调用异常该怎么处理?

还有,系统A不需要关注系统B是否存活。万一要是系统B挂掉了,系统A通过MQ来通信也不需要管系统B的“死活”,系统B自己恢复了之后就可以从MQ消费消息再次处理即可。

否则系统A直接调用系统B的接口,万一系统B挂了,难道系统A还要把消息暂存到数据库?等待系统B恢复了再给他发过去吗?

这就是通过MQ进行异步通信,让两个系统解耦之后的好处,可以大幅度提升整个大系统的容错性,增加系统的弹性,而不是处处耦合,一个系统出错连带导致其他系统全部出错。

解耦之后,即使出错也只是大系统中的一个系统B出错而已,不影响别人。

2、生产案例:早教盒子APP的发货

接下来用一个经典的生产案例给大家说说MQ在生产的使用。现在很多早教类的APP,都会提供早教盒子,什么意思呢?

早教APP提供的核心服务就是三块:

  1. APP里的早教视频课程

  2. 线上微信群的助教答疑指导

  3. 线下送你早教盒子,里面有很多上课道具

这样一个妈妈陪伴孩子上早教的过程可能是这样的:

  • 首先在APP里看早教视频课程,孩子看着很感兴趣。

  • 接着妈妈从早教盒子里取出来道具,陪孩子把视频里的游戏和任务都做一遍,让孩子加深印象

  • 最后每天妈妈会打卡,有助教会来给妈妈进行答疑。

所以说,假设现在我们要在一个早教APP里购买一个早教课程,他的流程大致如下:

  • 选择购买早教课程

  • 直接支付

  • 创建订单

  • 给用户增加课程权限

  • 通知仓库准备发早教盒子

  • 通知物流公司去仓库取早教盒子进行配送。

我们来分析一下每个环节。首先你要是购买一个早教课程,那么点击“购买”的按钮之后,一般直接会跳入一个支付界面。

这个时候,你就可以直接选择支付了。此时后台系统一定会通过支付系统跟第三方支付系统进行通信,比如说支付宝、微信之类的,然后等待支付完成。

一旦支付完成,就会在自己内部系统干两个事:

  • 第一,给这个用户id创建一个订单;

  • 第二,给这个用户id增加看某个早教视频课程的权限。

此时用户其实在“我的订单”界面就可以看到自己的订单了,而且在“我的课程”界面,就可以开始看早教课程的视频了。

下图展示了这个过程:

但是现在问题主要在后面两个步骤,现在你的订单系统作为核心入口,他要通知仓库系统去扣减一个早教盒子的库存。

同时,还得准备好早教盒子的发货(比如说提前打包装箱,准备一些给快递公司使用的发货单之类的,需要帖子箱子上)。然后通知第三方物流公司的系统,可以去自己的仓库取早教盒子发货了。

这两个步骤需要涉及到对仓库系统以及第三方物流公司系统的调用,那么是采用订单系统直接同步调用那两个系统的方式吗?

恐怕不妥,因为这里最大的问题就是性能问题可用性问题

举个例子,假如现在仓库系统部署在其他地方,因为网络问题导致性能很差,访问速度很慢,那么是不是可能会导致用户支付之后,等待了几分钟都看不到整个流程的完成?

或者要是说第三方物流公司的系统现在要是故障了,暂时无法访问,那么会不会导致用户支付了之后,一直没有给用户发货早教盒子?

所以说,在这里就应该引入MQ,订单系统在完成订单的创建以及课程的分配之后,就可以发送一个消息到MQ,然后有一个专门的仓储系统负责消费这个消息,接着尝试去调用独立仓库系统通知发货,以及通知第三方物流系统去配送。

整个过程,如下图所示:

这么做有什么好处呢?

好处是显而易见的,假如现在独立仓库系统和第三方物流系统的访问性能突然变得很差,大不了就是仓储系统在后面慢慢的跟人家通信等着人家处理完毕好了,对订单系统是没影响的。

对于订单系统而言,创建订单和分配课程都是速度很快的,然后发送个消息到MQ速度也很快。

这样一来,用户看到的就是一两秒的时间支付就成功了,然后可以查到订单,看到自己的课程,然后订单的物流显示的是“待配送”的状态。

那么如果独立仓库系统或者第三方物流系统故障了,导致仓储系统消费到一条订单消息之后,尝试进行发货失败,也就是对这条消费到的消息处理失败。这种情况,怎么处理?

这就是本文最核心的地方了!!!

3、死信队列的使用:处理失败的消息

一般生产环境中,如果你有丰富的架构设计经验,都会在使用MQ的时候设计两个队列:一个是核心业务队列,一个是死信队列

核心业务队列,就是比如上面专门用来让订单系统发送订单消息的,然后另外一个死信队列就是用来处理异常情况的。

之所以我们这篇文章抛出一个面试题,结果先长篇大论说一个生产实践案例和业务场景,就是因为面试被问到这个问题时,必须要结合你自己的业务实践经验来说。

你需要先给面试官说有血有肉的业务系统场景,然后再结合这个场景回答他的问题,因为面试官想听的就是你真实的实践经验。

比如说要是第三方物流系统故障了,此时无法请求,那么仓储系统每次消费到一条订单消息,尝试通知发货和配送,都会遇到对方的接口报错。

此时仓储系统就可以把这条消息拒绝访问,或者标志位处理失败!注意,这个步骤很重要。

一旦标志这条消息处理失败了之后,MQ就会把这条消息转入提前设置好的一个死信队列中。

然后你会看到的就是,在第三方物流系统故障期间,所有订单消息全部处理失败,全部会转入死信队列。

然后你的仓储系统得专门有一个后台线程,监控第三方物流系统是否正常,能否请求的,不停的监视。

一旦发现对方恢复正常,这个后台线程就从死信队列消费出来处理失败的订单,重新执行发货和配送的通知逻辑。

死信队列的使用,其实就是MQ在生产实践中非常重要的一环,也就是架构设计必须要考虑的。

最终的架构图,如下所示:


本文地址:https://www.6aiq.com/article/1560187859658
本文版权归作者和AIQ共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出