这篇文章我们会把消息中间件这块高频的面试问题来给大家说一下,也会涵盖一些 MQ 中间件常见的技术问题。
假如说面试官看你简历里写了 MQ 中间件的使用经验,很可能会有如下的问题:
好,下面我们一个个的来分析!
这个首先你可以说下你们公司选用的是什么消息中间件,比如用的是 RabbitMQ,然后可以初步给一些你对不同 MQ 中间件技术的选型分析。
举个例子:比如说 ActiveMQ 是老牌的消息中间件,国内很多公司过去运用的还是非常广泛的,功能很强大。
但是问题在于没法确认 ActiveMQ 可以支撑互联网公司的高并发、高负载以及高吞吐的复杂场景,在国内互联网公司落地较少。而且使用较多的是一些传统企业,用 ActiveMQ 做异步调用和系统解耦。
然后你可以说说 RabbitMQ,他的好处在于可以支撑高并发、高吞吐、性能很高,同时有非常完善便捷的后台管理界面可以使用。
另外,他还支持集群化、高可用部署架构、消息高可靠支持,功能较为完善。
而且经过调研,国内各大互联网公司落地大规模 RabbitMQ 集群支撑自身业务的 case 较多,国内各种中小型互联网公司使用 RabbitMQ 的实践也比较多。
除此之外,RabbitMQ 的开源社区很活跃,较高频率的迭代版本,来修复发现的 Bug 以及进行各种优化,因此综合考虑过后,公司采取了 RabbitMQ。
但是 RabbitMQ 也有一点缺陷,就是他自身是基于 Erlang 语言开发的,所以导致较为难以分析里面的源码,也较难进行深层次的源码定制和改造,毕竟需要较为扎实的 Erlang 语言功底才可以。
然后可以聊聊 RocketMQ,是阿里开源的,经过阿里的生产环境的超高并发、高吞吐的考验,性能卓越,同时还支持分布式事务等特殊场景。
而且 RocketMQ 是基于 Java 语言开发的,适合深入阅读源码,有需要可以站在源码层面解决线上生产问题,包括源码的二次开发和改造。
另外就是 Kafka。Kafka 提供的消息中间件的功能明显较少一些,相对上述几款 MQ 中间件要少很多。
但是 Kafka 的优势在于专为超高吞吐量的实时日志采集、实时数据同步、实时数据计算等场景来设计。
因此 Kafka 在大数据领域中配合实时计算技术(比如 Spark Streaming、Storm、Flink)使用的较多。但是在传统的 MQ 中间件使用场景中较少采用。
PS:如果大家对上述一些 MQ 技术还没在自己电脑部署过,没写几个 helloworld 体验一下的话,建议先上各个技术的官网找到 helloworld demo,自己跑一遍玩玩。
回答这个问题,其实就是让你先说说消息中间件的常见使用场景。然后结合你们自身系统对应的使用场景,说一下在你们系统中引入消息中间件解决了什么问题。
假设你有个系统 A,这个系统 A 会产出一个核心数据,现在下游有系统 B 和系统 C 需要这个数据。那简单,系统 A 就是直接调用系统 B 和系统 C 的接口发送数据给他们就好了。
整个过程,如下图所示:
但是现在要是来了系统 D、系统 E、系统 F、系统 G,等等,十来个其他系统慢慢的都需要这份核心数据呢?如下图所示:
大家可别以为这是开玩笑,一个大规模系统,往往会拆分为几十个甚至上百个子系统,每个子系统又对应 N 多个服务,这些系统与系统之间有着错综复杂的关系网络。
如果某个系统产出一份核心数据,可能下游无数的其他系统都需要这份数据来实现各种业务逻辑。
此时如果你要是采取上面那种模式来设计系统架构,那么绝对你负责系统 A 的同学要被烦死了。
先是来一个人找他要求发送数据给一个新的系统 H,系统 A 的同学要修改代码,然后在那个代码里加入调用新系统 H 的流程。
一会那个系统 B 是个陈旧老系统要下线了,告诉系统 A 的同学:别给我发送数据了,接着系统 A 再次修改代码不再给这个系统 B。
然后如果要是某个下游系统突然宕机了呢?系统 A 的调用代码里是不是会抛异常?
那系统A的同学会收到报警说异常了,结果他还要去 care 是下游哪个系统宕机了。
所以在实际的系统架构设计中,如果全部采取这种系统耦合的方式,在某些场景下绝对是不合适的,系统耦合度太严重。
并且互相耦合起来并不是核心链路的调用,而是一些非核心的场景(比如上述的数据消费)导致了系统耦合,这样会严重的影响上下游系统的开发和维护效率。
因此在上述系统架构中,就可以采用 MQ 中间件来实现系统解耦。系统 A 就把自己的一份核心数据发到 MQ 里,下游哪个系统感兴趣自己去消费即可,不需要了就取消数据的消费,如下图所示:
假设你有一个系统调用链路,是系统 A 调用系统 B,一般耗时 20ms;系统 B 调用系统 C,一般耗时 200ms;系统 C 调用系统 D,一般耗时 2s,如下图所示:
现在最大的问题就是:用户一个请求过来巨慢无比,因为走完一个链路,需要耗费 20ms + 200ms + 2000ms(2s) = 2220ms,也就是 2 秒多的时间。
但是实际上,链路中的系统 A 调用系统 B,系统 B 调用系统 C,这两个步骤起来也就 220ms。
就因为引入了系统 C 调用系统 D 这个步骤,导致最终链路执行时间是 2 秒多,直接将链路调用性能降低了 10 倍,这就是导致链路执行过慢的罪魁祸首。
那此时我们可以思考一下,是不是可以将系统 D 从链路中抽离出去做成异步调用呢?其实很多的业务场景是可以允许异步调用的。
举个例子,你平时点个外卖,咔嚓一下子下订单然后付款了,此时账户扣款、创建订单、通知商家给你准备菜品。
接着,是不是需要找个骑手给你送餐?那这个找骑手的过程,是需要一套复杂算法来实现调度的,比较耗时。
但是其实稍微晚个几十秒完成骑手的调度都是 ok 的,因为实际并不需要在你支付的一瞬间立马给你找好骑手,也没那个必要。
那么我们是不是就可以把找骑手给你送餐的这个步骤从链路中抽离出去,做成异步化的,哪怕延迟个几十秒,但是只要在一定时间范围内给你找到一个骑手去送餐就可以了。
这样是不是就可以让你下订单点外卖的速度变得超快?支付成功之后,直接创建好订单、账户扣款、通知商家立马给你准备做菜就 ok 了,这个过程可能就几百毫秒。
然后后台异步化的耗费可能几十秒通过调度算法给你找到一个骑手去送餐,但是这个步骤不影响我们快速下订单。
当然我们不是说那些大家熟悉的外卖平台的技术架构就一定是这么实现的,只不过是用一个生活中常见的例子给大家举例说明而已。
所以上面的链路也是同理,如果业务流程支持异步化的话,是不是就可以考虑把系统 C 对系统 D 的调用抽离出去做成异步化的,不要放在链路中同步依次调用。
这样,实现思路就是系统 A→系统 B→系统 C,直接就耗费 220ms 后直接成功了。
然后系统 C 就是发送个消息到 MQ 中间件里,由系统 D 消费到消息之后慢慢的异步来执行这个耗时 2s 的业务处理。通过这种方式直接将核心链路的执行性能提升了 10 倍。
整个过程,如下图所示:
假设你有一个系统,平时正常的时候每秒可能就几百个请求,系统部署在 8 核 16G 的机器的上,正常处理都是 ok 的,每秒几百请求是可以轻松抗住的。
但是如下图所示,在高峰期一下子来了每秒钟几千请求,瞬时出现了流量高峰,此时你的选择是要搞 10 台机器,抗住每秒几千请求的瞬时高峰吗?
那如果瞬时高峰每天就那么半个小时,接着直接就降低为了每秒就几百请求,如果你线上部署了很多台机器,那么每台机器就处理每秒几十个请求就可以了,这不是有点浪费机器资源吗?
大部分时候,每秒几百请求,一台机器就足够了,但是为了抗那每天瞬时的高峰,硬是部署了 10 台机器,每天就那半个小时有用,别的时候都是浪费资源的。
但是如果你就部署一台机器,那会导致瞬时高峰时,一下子压垮你的系统,因为绝对无法抗住每秒几千的请求高峰。
此时我们就可以用 MQ 中间件来进行流量削峰。所有机器前面部署一层 MQ,平时每秒几百请求大家都可以轻松接收消息。
一旦到了瞬时高峰期,一下涌入每秒几千的请求,就可以积压在 MQ 里面,然后那一台机器慢慢的处理和消费。
等高峰期过了,再消费一段时间,MQ 里积压的数据就消费完毕了。
这个就是很典型的一个 MQ 的用法,用有限的机器资源承载高并发请求。
如果业务场景允许异步削峰,高峰期积压一些请求在 MQ 里,然后高峰期过了,后台系统在一定时间内消费完毕不再积压的话,那就很适合用这种技术方案。
如果你在系统架构里引入了消息中间件之后,会有哪些缺点?
首先是你的系统整体可用性绝对会降低,给你举个例子,我们就拿之前的一幅图来说明。
比如说一个核心链路里面,系统 A→系统 B→系统 C,然后系统 C 是通过 MQ 异步调用系统 D 的。
看起来很好,你用这个 MQ 异步化的手段解决了一个核心链路执行性能过差的问题。
但是你有没有考虑另外一个问题,就是万一你依赖的那个 MQ 中间件突然挂掉了怎么办?
这个还真的不是异想天开,MQ、Redis、MySQL 这些组件都有可能会挂掉。一旦你的 MQ 挂了,就导致你的系统的核心业务流程中断了。
本来你要是不引入 MQ 中间件,那其实就是一些系统之间的调用,但是现在你引入了 MQ,就导致你多了一个依赖。一旦多了一个依赖,就会导致你的可用性降低。
因此,一旦引入了 MQ 中间件,你就必须去考虑这个 MQ 是如何部署的,如何保证高可用性。
甚至在复杂的高可用的场景下,你还要考虑如果 MQ 一旦挂了以后,你的系统有没有备用兜底的技术方案,可以保证系统继续运行下去。
还是上面那张图,大家再来看一下:
不知道大家有没有发现一个问题,这个链路除了 MQ 中间件挂掉这个可能存在的隐患之外,可能还有一些其他的技术问题。
比如说,莫名其妙的,系统 C 发了一个消息到 MQ,结果那个消息因为网络故障等问题,就丢失了。这就导致系统 D 没有收到那条消息。
这可就惨了,这样会导致系统 D 没完成自己该做的任务,此时可能整个系统会出现业务错乱,数据丢失,严重的 Bug,用户体验很差等各种问题。
这还只是其中之一,万一说系统 C 给 MQ 发送消息,不小心一抽风重复发了一条一模一样的,导致消息重复了,这个时候该怎么办?
可能会导致系统 D 一下子把一条数据插入了两次,导致数据错误,脏数据的产生,最后一样会导致各种问题。
或者说如果系统 D 突然宕机了几个小时,导致无法消费消息,结果大量的消息在 MQ 中间件里积压了很久,这个时候怎么办?
即使系统 D 恢复了,也需要慢慢的消费数据来进行处理。所以这就是引入 MQ 中间件的第二个大问题,系统稳定性可能会下降,故障会增多,各种各样乱七八糟的问题都可能产生。
而且一旦产生了一个问题,就会导致系统整体出问题。你就需要为了解决各种 MQ 引发的技术问题,采取很多的技术方案。
关于这个,我们后面会用专门的文章聊聊 MQ 中间件的这些问题的解决方案,包括:
引入消息中间件,还有分布式一致性的问题。举个例子,比如说系统 C 现在处理自己本地数据库成功了,然后发送了一个消息给 MQ,系统 D 也确实是消费到了。
但是结果不幸的是,系统 D 操作自己本地数据库失败了,那这个时候咋办?
系统 C 成功了,系统 D 失败了,会导致系统整体数据不一致了啊。所以此时又需要使用可靠消息最终一致性的分布式事务方案来保障。
关于这个,可以参考之前的一篇文章:最终一致性分布式事务如何保障实际生产中 99.99% 高可用?
我们在里面详细阐述了系统之间异步调用场景下,如何采用分布式事务方案保证其数据一致性。
最后,我们来做一个简单的小结。在面试中要答好这个问题,首先一定要熟悉 MQ 这个技术的优缺点。了解清楚把他引入系统是为了解决哪些问题的,但是他自身又会带来哪些问题。
此外,对于引入 MQ 以后,是否对他自身可能引发的问题有一些方案的设计,来保证你的系统高可用、高可靠的运行,保证数据的一致性。这个也要做好相应的准备。
中华石杉:十余年 BAT 架构经验,一线互联网公司技术总监。带领上百人团队开发过多个亿级流量高并发系统。现将多年工作中积累下的研究手稿、经验总结整理成文,倾囊相授。微信公众号:石杉的架构笔记(ID:shishan100)。