MQ 经典面试题汇总

262 阅读9分钟

1、为什么选择使用MQ?

对比常用消息队列ActiveMQ,RocketMQ,Kafka,RabbitMQ 具备如下优点:

  1. RabbitMQ相对成熟稳定,这是我们选择它最主要的原因。
  2. Rabbitmq的吞吐量可以达到万级,完全满足业务系统的要求。
  3. 社区比较活跃,有完善的资料可以参考。
  4. 对接友好,支持常用的主流开发语言,比如java、golang、php、python 等等。
  5. 有完善的可视化界面,方便查看。
  6. RabbitMQ是Erlang语言开发的,性能比较好。

2、 简述 MQ 的优缺点?

使用MQ的原因及其优点:

MQ可实现:

  • 异步处理 - 相比于传统的串行、并行方式,提高了系统吞吐量。
  • 应用解耦 - 系统间通过消息通信,不用关心其他系统的处理。
  • 流量削锋 - 可以通过消息队列长度控制请求量;可以缓解短时间内的高并发请求。
  • 日志处理 - 解决大量日志传输。
  • 消息通讯 - 消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等。

MQ 的缺点:

  • 系统可用性降低

    • 如果消息队列出现问题,会导致服务可用性降低。
  • 系统复杂度提高

    • 加入了消息队列,要多考虑很多方面的问题,比如:一致性问题、如何保证消息不被重复消费、如何保证消息可靠性传输等。因此,需要考虑的东西更多,复杂性增大。
  • 引发一致性问题

    • 在未引入消息队列时,系统处理完成就成功结束了,但如果引入了消息队列,就需要考虑原系统A成功了,那消费消息的系统B、C 是否也成功完成,如果某个系统比如B失败了,则会引发数据不一致,这是需要如何处理这个不一致问题。

3、消息基于什么传输?

由于 TCP 连接的创建和销毁开销较大,且并发数受系统资源限制,会造成性能瓶颈。RabbitMQ 使用信道的方式来传输数据。信道是建立在真实的 TCP 连接内的虚拟连接,且每条 TCP 连接上的信道数量没有限制。

4、如何保证RabbitMQ消息的顺序性?

  • 拆分多个 queue(消息队列),每个 queue(消息队列) 一个 consumer(消费者);

  • 或者就一个 queue (消息队列)但是对应一个 consumer(消费者),然后这个 consumer(消费者)内部用内存队列做排队,然后分发给底层不同的 worker 来处理。

5.如何避免消息重复投递或重复消费?

  • 首先分析问题产生原因:为什么会出现重复投递或消费?

    • 正常情况下,生产者生产完消息就会进行消息确认,从而避免出现重复投递;同样正常消费情况下,消费者在消费完消息后,会发送一个确认消息给消息队列,确认消息已被正常消费,可将该消息从消息队列中删除;

    • 但是因为网络传输等故障,确认信息没有传送到消息队列,导致生产者不知道自己已经成功投递或者消息队列成功消费过该消息,再次将消息进行投递或分发给其他的消费者。

  • 针对问题给出解决方案:

    • 解决思路:保证消息的唯一性,提供有效去重依据,从而保证消息等幂性;
    • 比如在写消息时,为每一条消息生成一个唯一标识,作为去重的依据(消息投递失败并重传),避免重复的消息进入队列;
    • 消息消费时,根据此唯一标识进行是否消费二次确认,即增加业务逻辑去重,避免同一条消息被重复消费。这样即使一条消息多次传输,也只会消费一次,保证了消息等幂性;

6、如何确保消息不丢失?

6、如何保证MQ消息的可靠传输?

这是一个问题的两种常见表述,主要考察的还是消费传输的可靠性,即避免消失丢失。

  • 消息丢失可分为:生产者丢失消息、消息列表丢失消息、消费者丢失消息 3种常见情况;
  1. 生产者丢失消息:从生产者弄丢数据这个角度来看,RabbitMQ提供transaction和confirm模式来确保生产者不丢消息;其中confirm模式使用居多。

    • transaction机制:发送消息前,开启事务(channel.txSelect()),然后发送消息,如果发送过程中出现什么异常,事务就会回滚(channel.txRollback()),如果发送成功则提交事务(channel.txCommit())。然而,这种方式有个缺点:吞吐量下降;

    • confirm模式:一旦channel进入confirm模式,所有在该信道上发布的消息都将会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后;rabbitMQ就会发送一个ACK给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了;如果rabbitMQ没能处理该消息,则会发送一个Nack消息,你可以进行重试操作。

  2. 消息队列丢数据:消息持久化。

    RabbitMQ 确保持久性消息能从服务器重启中恢复的方式是,将它们写入磁盘上的一个持久化日志文件,当发布一条持久性消息到持久交换器上时,Rabbit 会在消息提交到日志文件后才发送响应。

    一旦消费者从持久队列中消费了一条持久化消息,RabbitMQ 会在持久化日志中把这条消息标记为等待垃圾收集。如果持久化消息在被消费之前 RabbitMQ 重启,那么 Rabbit 会自动重建交换器和队列(以及绑定),并重新发布持久化日志文件中的消息到合适的队列。

    这个持久化配置可以和confirm机制配合使用,你可以在消息持久化磁盘后,再给生产者发送一个Ack信号。

    这样,如果消息持久化磁盘之前,rabbitMQ阵亡了,那么生产者收不到Ack信号,生产者会自动重发。

    那如何进行持久化配置呢?

    ​ 1. 将queue的持久化标识durable设置为true,则代表是一个持久的队列

    ​ 2. 发送消息的时候将deliveryMode=2

    这样设置以后,即使rabbitMQ挂了,重启后也能恢复数据。

  3. 消费者丢失消息:消费者丢数据一般是因为采用了自动确认消息模式,改为手动确认消息即可。

    消费者在收到消息之后,处理消息之前,会自动回复RabbitMQ已收到消息;

    如果这时处理消息失败,就会丢失该消息;

    解决方案:处理消息成功后,手动回复确认消息。

7. RabbitMQ如何保证高可用 ?

RabbitMQ 使用 镜像集群保证系统的高可用,镜像集群最大特点是可实现多节点的消息相互通信。

假如有三个节点,rabbitmq1、rabbitmq2、rabbitmq3,每个实例之间都可以相互通信,每次生产者写消息到queue的时候,每个rabbitmq节点上都有queue的消息数据和元数据。这样即使一个节点挂掉,也不会造成数据的丢失。 这种模式适用于可靠性要求较高的场景。

8. 消息大量堆积应该怎么处理

消息堆积的原因有两个

  1. 网络故障,消费者无法正常消费
  2. 消费方消费后未进行ack确认

解决方案如下:

  1. 检查并修复消费者故障,使其正常消费
  2. 编写临时程序将堆积的消息发送到容量更大的MQ集群,增加消费者快速消费
  3. 堆积消息消费完毕后,停止临时程序,恢复正常消费

9. 如果我有一笔订单,30分钟未支付则关闭订单,使用RabbitMQ如何来实现?

RabbitMQ可以使用死信队列来实现延时消费,用户下单之后,将订单信息投递到消息队列中,并且设置消息过期时常为30分钟。如果用户支付则正常关闭订单,如果用户未支付,消息达到过期时间,消息会进入死信交换,由消费者进行消费死信队列来关闭订单。

10、如何确保消息正确地发送至RabbitMQ? 如何确保消息接收方消费了消息?

发送方确认模式

将信道设置成 confirm 模式(发送方确认模式),则所有在信道上发布的消息都会被指派一个唯一的 ID。

一旦消息被投递到目的队列后,或者消息被写入磁盘后(可持久化的消息),信道会发送一个确认给生产者(包含消息唯一 ID)。

如果 RabbitMQ 发生内部错误从而导致消息丢失,会发送一条 nack(notacknowledged,未确认)消息。

发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消息。当确认消息到达生产者应用程序,生产者应用程序的回调方法就会被触发来处理确认消息。

接收方确认机制

  • 消费者接收每一条消息后都必须进行确认(消息接收和消息确认是两个不同操作)。只有消费者确认了消息,RabbitMQ 才能安全地把消息从队列中删除。

  • 这里并没有用到超时机制,RabbitMQ 仅通过 Consumer 的连接中断来确认是否需要重新发送消息。也就是说,只要连接不中断,RabbitMQ 给了 Consumer 足够长的时间来处理消息。保证数据的最终一致性;

    下面罗列几种特殊情况:

    • 如果消费者接收到消息,在确认之前断开了连接或取消订阅,RabbitMQ 会认为消息没有被分发,然后重新分发给下一个订阅的消费者。(可能存在消息重复消费的隐患,需要去重)
    • 如果消费者接收到消息却没有确认消息,连接也未断开,则 RabbitMQ 认为该消费者繁忙,将不会给该消费者分发更多的消息。

11.如果让你设计一个消息队列,你应该怎么设计?

设计一个消息队列(Message Queue, MQ)需要综合考虑 核心功能可靠性扩展性性能优化高可用性。 具体设计方案参见文章: # 如何设计一个消息队列?