头图

今天,我将那些大厂必问的消息队列的场景问题为大家整理出来,本文将跟大家一起来探讨如何回答这些问题。

为什么要使用消息队列?
保证消息有序,一个topic只能有一个partition吗?(消息顺序)
业务突然增长,导致消息消费不过来怎么办?(消息积压)
生产者收到写入成功响应后消息一定不会丢失吗?(消息丢失)
高并发场景下怎么保证消息不会重复消费?(重复消费)
如何保证消息的可靠性?
各大消息队列中间件对比及使用场景

为什么要使用消息队列?

总结一下,主要三点原因:解耦、异步、削峰。面试的时候,用自己的语言将这三点讲述出来就可以了。
  1. 解耦:比如,用户下单后,订单系统需要通知库存系统,假如库存系统无法访问,则订单减库存将失败,从而导致订单操作失败。订单系统与库存系统耦合,这个时候如果使用消息队列,可以返回给用户成功,先把消息持久化,等库存系统恢复后,就可以正常消费减去库存了。
  2. 异步:将消息写入消息队列,非必要的业务逻辑以异步的方式运行,不影响主流程业务。
  3. 削峰:消费端慢慢的按照数据库能处理的并发量,从消息队列中慢慢拉取消息。在生产中,这个短暂的高峰期积压是允许的。比如秒杀活动,一般会因为流量过大,从而导致流量暴增,应用挂掉。这个时候加上消息队列,服务器接收到用户的请求后,首先写入消息队列,如果消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面。

保证消息有序,一个topic只能有一个partition吗?(消息顺序)

这个场景是针对 kafka 的,一个 Topic,一个 Partition,一个 Consumer,但是这样会导致内部单线程消费,单线程吞吐量太低,一般不会用这个。

针对保证消息有序性的问题,解决方法就是保证生产者入队的顺序是有序的,出队后的顺序消费则交给消费者去保证。

方法一:

拆分 queue,使得一个 queue 只对应一个消费者。

由于 MQ 一般都能保证内部队列是先进先出的,所以把需要保持先后顺序的一组消息使用某种算法都分配到同一个消息队列中。然后只用一个消费者单线程去消费该队列,这样就能保证消费者是按照顺序进行消费的了。

但是消费者的吞吐量会出现瓶颈。如果多个消费者同时消费一个队列,还是可能会出现顺序错乱的情况,这就相当于是多线程消费了

方法二:

对于多线程的消费同一个队列的情况,可以使用重试机制:比如有一个微博业务场景的操作,发微博、写评论、删除微博,这三个异步操作。

如果一个消费者先执行了写评论的操作,但是这时微博都还没发,写评论一定是失败的,等一段时间。等另一个消费者,先执行发微博的操作后,再执行,就可以成功。

业务突然增长,导致消息消费不过来怎么办?(消息积压)

消息堆积往往是生产者的生产速度与消费者的消费速度不匹配导致的。

有可能就是消费者消费能力弱,渐渐地消息就积压了,也有可能是因为消息消费失败反复复重试造成的,也有可能是消费端出了问题,导致不消费了或者消费极其慢。

一般这个时候,只能临时紧急扩容了,具体操作步骤和思路如下:

  1. 先修复 consumer 的问题,确保其恢复消费速度,然后将现有 consumer 都停掉;
  2. 新建一个 topic,partition 是原来的 10 倍,临时建立好原先 10 倍的 queue 数量;
  3. 然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的 10 倍数量的 queue;
  4. 接着临时用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据。这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常的 10 倍速度来消费数据;
  5. 等快速消费完积压数据之后,得恢复原先部署的架构,重新用原先的 consumer 机器来消费消息。

生产者收到写入成功响应后消息一定不会丢失吗?(消息丢失)

一个消息从生产者产生,到被消费者消费,主要经过这 3 个过程:


因此如何保证 MQ 不丢失消息,可以从这三个阶段阐述:

生产者保证不丢消息,存储端不丢消息,消费者不丢消息

生产者保证不丢消息

生产端如何保证不丢消息呢?确保生产的消息能到达存储端。

如果是 RocketMQ 消息中间件,生产者要想发消息时保证消息不丢失,可以:

采用同步方式发送,send 消息方法返回成功状态,就表示消息正常到达了存储端 Broker。

如果 send 消息异常或者返回非成功状态,可以重试。

可以使用事务消息,RocketMQ 的事务消息机制就是为了保证零丢失来设计的。

存储端不丢消息

如何保证存储端的消息不丢失呢? 确保消息持久化到磁盘。大家很容易想到就是刷盘机制。

刷盘机制分同步刷盘和异步刷盘

生产者消息发过来时,只有持久化到磁盘,RocketMQ 的存储端 Broker 才返回一个成功的 ACK 响应,这就是同步刷盘。它保证消息不丢失,但是影响了性能。

异步刷盘的话,只要消息写入 PageCache 缓存,就返回一个成功的 ACK 响应。这样提高了 MQ 的性能,但是如果这时候机器断电了,就会丢失消息。

消费者不丢消息

消费者执行完业务逻辑,再反馈会 Broker 说消费成功,这样才可以保证消费阶段不丢消息。

高并发场景下怎么保证消息不会重复消费?(重复消费)

首先,对于正常业务而言消息重复是不可避免的。

正常情况下,消费者在消费消息后,会给消息队列发送一个确认,消息队列接收后就知道消息已经被成功消费了,然后就从队列中删除该消息,也就不会将该消息再发送给其他消费者了。

不同消息队列发出的确认消息形式不同,RabbitMQ 是通过发送一个 ACK 确认消息。

但是因为网络故障,消费者发出的确认并没有传到消息队列,导致消息队列不知道该消息已经被消费,然后就再次消息发送给了其他消费者,从而造成重复消费的情况。

重复消费问题的解决思路是:保证消息的唯一性,即使多次传输,也不让消息的多次消费带来影响,也就是保证消息等幂性。

具体办法:

在消息生产时,MQ 内部针对每条生产者发送的消息生成一个唯一 id,作为去重和幂等的依据(消息投递失败并重传),避免重复的消息进入队列。

在消息消费时,要求消息体中也要有一全局唯一 id 作为去重和幂等的依据,避免同一条消息被重复消费

如何保证消息的可靠性?

关于消息丢失的情况也就是这三种情况,消息到 MQ 的过程中搞丢,MQ 自己搞丢,MQ 到消费过程中搞丢。针对不同的情况,用不同的解决方法,用自己的语言叙述即可。

生产者到 RabbitMQ:事务机制和 Confirm 机制,注意:事务机制和 Confirm 机制是互斥的,两者不能共存,会导致 RabbitMQ 报错。

RabbitMQ 自身:持久化、集群、普通模式、镜像模式。

RabbitMQ 到消费者:basicAck 机制、死信队列、消息补偿机制。

各大消息队列中间件对比

ActiveMQRabbitMQRocketMQKafkaZeroMQ
单机吞吐量比 RabbitMQ 低2.6w/s11.6w/s17.3w/s29w/s
开发语言JavaErlangJavaScala/JavaC
成熟度成熟成熟开源版本不够成熟比较成熟只有 C、PHP 版本成熟
订阅模式点对点(p2p)、广播(发布-订阅)direct、topic、Headers、fanout基于 topic/messageTag 以及按照消息类型,属性进行正则匹配的发布订阅模式基于 topic 以及按照 topic 进行正则匹配的发布订阅模式点对点(p2p)
持久化支持少量堆积支持少量堆积支持大量堆积支持大量堆积不支持
顺序消息不支持不支持支持支持不支持
性能稳定性一般较差很好
集群模式支持简单集群模式,比如‘主-备’,对高级集群模式支持不好支持简单集群,‘复制’模式,对高级集群模式支持不好常用多对‘Master-Slave’模式,开源版本需手动切换 Slave 变成 Master天然的‘Leader-Slave’无状态集群,每台服务器既是 Master 也是 Slave不支持
管理界面一般较好一般

各个消息中间件对比适用场景

ActiveMQ:

  • 特点:可靠性、持久化、多种消息模式(点对点、发布-订阅、请求-回复)。
  • 适用场景:适合任务队列、工作流、异步通信、RPC、事件驱动架构等场景。

RabbitMQ:

  • 特点:易用性、灵活性、可靠性、多种消息模式(点对点、发布-订阅、请求-回复)。
  • 适用场景:适合任务队列、工作流、异步通信、RPC、事件驱动架构等场景。

RocketMQ:

  • 特点:高吞吐量、分布式、强一致性、消息顺序保证、支持消息队列和广播消息、多种消息协议(例如:HTTP、MQTT)。
  • 适用场景:适合大规模分布式系统、金融行业消息中间件、实时流处理、消息通知、事件驱动架构等场景。

Apache Kafka:

  • 特点:高吞吐量、持久化、可伸缩性、分布式、多副本复制、消息顺序保证。
  • 适用场景:适合大规模数据流处理、实时流式处理、日志收集、事件驱动架构等场景。

ZeroMQ:

  • 特点:轻量级、低延迟、高性能、无服务器架构、支持多种消息传输协议(如:发布-订阅、请求-回复、推送-拉取)。
  • 适用场景:适合高频交易、低延迟通信、大规模分布式应用、实时数据传输、微服务通信等场景。

就业陪跑训练营学员投稿

欢迎关注 ❤

我们搞了一个免费的面试真题共享群,互通有无,一起刷题进步。

没准能让你能刷到自己意向公司的最新面试题呢。

感兴趣的朋友们可以加我微信:wangzhongyang1993,备注:面试群。


王中阳讲编程
822 声望304 粉丝