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



转载请注明 AIQ - 最专业的机器学习大数据社区  http://www.6aiq.com

AIQ 机器学习大数据 知乎专栏 点击关注

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

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 在生产实践中非常重要的一环,也就是架构设计必须要考虑的。

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


更多高质资源 尽在AIQ 机器学习大数据 知乎专栏 点击关注

转载请注明 AIQ - 最专业的机器学习大数据社区  http://www.6aiq.com