MQ专题

5/17/2023 面试

# MQ专题

# 为什么要用MQ

面试官:为什么要使用MQ呢,他的作用是什么

我:MQ的作用主要有三个解耦,异步,削峰

# 1.1 MQ的作用

# 1.1.1 解耦

​ 如果多个模块或者系统中,互相调用很复杂,维护起来比较麻烦,但是这个调用又不是同步调用,就可 以运用MQ到这个业务中。

# 1.1.2 异步

​ 这个很好理解,比如用户的操作日志的维护,可以不用同步处理,节约响应时间。

# 1.1.3 削峰

​ 在高峰期的时候,系统每秒的请求量达到 5000,那么调用MySQL的请求也是5000,一般情况下 MySQL 的请求大概在2000左右,那么在高峰期的时候,数据库就被打垮了,那系统就不可用了。此时引入MQ,在系统A前面加个MQ,用户请求先到MQ,系统A从MQ中每秒消费2000条数据,这样就把本来5000的请求变为 MySQL 可以接受的请求数量了,可以保证系统不挂掉,可以继续提供服务。MQ 里的数据可以慢慢的把它消费掉。

# 1.2 使用了MQ会有什么问题

面试官:MQ使用后可能会出现什么问题呢?

我:

  • 增加了系统的复杂度:因为一个系统引入了MQ之后会造成系统的复杂性的提升,复杂性提升后, 增加MQ的维护成本
  • 降低的系统的可用性:复杂性的提升意味这系统可用性的降低,因为MQ一旦出现问题就会造成系统出现问题。
  • 一致性问题:因为MQ是异步处理消息,需要处理类似于消息丢失以及重复消费的问题,一旦处理不好就会造成重复消费问题。

# 1.3 如何避免MQ消息堆积

面试官:如何避免MQ消息堆积

# 1.3.1 产生MQ消息堆积的原因
  1. 生产者投递消息的速率与我们消费者消费的速率完全不匹配。
  2. 生产者投递消息的速率>消费者消费的速率 导致我们消息会堆积在我们 mq 服务器端中,没有及时的被消费者消费,所以就会产生消息堆积的问题
  3. 注意的是:rabbitmq消费者我们的消息消费如果成功的话消息会被立即删除。 kafka或者rocketmq消息消费如果成功的话,消息是不会立即被删除。
# 1.3.2 解决办法
  1. 提高消费者消费的速率;(对我们的消费者实现集群)

  2. 消费者应该批量形式获取消息 减少网络传输的次数

# 1.4 为什么会出现重复消费?

面试官:为什么MQ会出现重复消费,以及如何来解决重复消费问题

我:MQ的消息流程主要有两个阶段来完成,发送消息到消息队列以及消息队列将消息投递到消费者,因为各种网络原因会造成发送方与以及接收方消息重试,就会造成重复消费

# 1.4.1 发送方消息重试

​ 因为网络原因以及MQ自身原因会导致发送的消息未成功投递到MQ,如果出现未成功投递就会重复投 递消息,这个时候是正常的,但是可能因为网络原因导致消息确认消息延时,实际上已经投递到了MQ 但是确认消息没有己实被发送方接收到,就会重新发起消息重试,这样就会造成消息重复,这种是发送 方消息重试,但是发送方重试次数是有限制的,如果达到一定次数后还是失败如果没有做其他处理就会 造成消息丢失。

# 1.4.2 消费方消息重试

​ 当消息正常投递到MQ后,就需要消费消息了,正常情况下消息会被发送到消费方进行消费,消费方一 般需要开启手动确认来确定消息一定会被消费掉,但是可能网络原因导致消息未被消费,这个时候消费 方就会重试消费消息,如果是出现消费超时或者异常就会进行消息重复消费,如果异常没有处理好导致 消息重复消费可能造成业务出现幂等性问题,但是如果做了幂等性问题,多次消费还是失败就会造成消 息丢失。

# 1.5 如果出现重复消费如何解决

面试官:如何解决重复消息问题

# 唯一主键

手动给每一条消息增加一个唯一的消息ID,使用消息ID作为唯一主键,如果消息重复,消息插入不进 去,用这种方式来解决消息重复

# 使用幂等

使用幂等方式来解决重复消费问题,手动给每一条消息增加一个唯一的消息ID,不要使用系统生成的消息ID,如果生成消息后就将消息保存到Redis中,如果消息消费成功后删除Redis中的消息,如果失败了不需要管Redis中的消息,这样就完来实现消息的幂等。

# 1.6 如何保证消息顺序性

面试官:MQ如何保证消息的有序消费

# 1.6.1 问题解析

​ 一个queue,有多个consumer去消费,这样就会造成顺序的错误,consumer从MQ里面读取数据是有 序的,但是每个consumer的执行时间是不固定的,无法保证先读到消息的consumer一定先完成操 作,这样就会出现消息并没有按照顺序执行,造成数据顺序错误,并且执行的时候是多线程执行的,并 不能保证执行的顺序性。

# 1.6.2 解决办法

# 1.7 如何实现延时消息

面试官:说下你所知道的实现消息延时发送的方法

我:延时消息就是一个消息需要延时多长时间才能够发送,比如用户支付订单,如果多长时间未支付就会取消订单,这个就属于延时消息

# 延时消息实现方式
# 数据库轮询

通过一个线程定时的去扫描数据库,通过订单时间来判断是否有超时的订单,然后进行update或delete等操作

# JDK的延迟队列

​ 利用JDK自带的DelayQueue来实现,这是一个无界阻塞队列,该队列只有在延迟期满的时候才能从中 获取元素,放入DelayQueue中的对象,是必须实现Delayed接口的。

# netty时间轮算法

# RabbitMQ

​ 可以采用RabbitMQ的死信队列来实现延时队列,RabbitMQ可以针对Queue和Message设置x-message-tt,来控制消息的生存时间,如果超时,则消息变为dead letter,dead letter会被投递到死信交换器,然后通过死信队列将消息发送出去

# RocketMQ

RocketMQ可以实现18个等级的消息延时,但是不可以实现任意时间的消息延时,使用RocketMQ的延 时消息只需要按照正常消息发送,并指定延时等级即可,简单高效,并且这个延时时间可以在 RocketMQ的配置参数中进行配置。


# 怎样选型MQ

面试官:你是如何选型MQ的能说下RocketMQ,RabbitMQ,Kafka三个MQ的特点吗

# 2.1 需求分析

# 2.1.1 功能需求

​ 除了最基本生产消费模型,还需要MQ能支持REQUEST-REPLY模型,以提供对同步调用的支持。 此 外,如果MQ能提供PUBLISH-SUBSCRIBE模型,则事件代理的实现可以更加简单。

# 2.1.2 性能需求

考虑未来一到两年内产品的发展,消息队列的呑吐量预计不会超过 1W qps,但由单条消息延迟要求较高,希望尽量的短。

# 2.1.3 可用性需求

因为是在线服务,因此需要较高的可用性,但充许有少量消息丢失。

# 2.1.4 易用性需求

包括学习成本、初期的开发部署成本、日常的运维成本等。

# 2.2 横向对比

我:几个MQ的主要对比如下如

特性 ActiveMQ RabbitMQ Kafka RocketMQ
PRODUCER- COMSUMER 支持 支持 支持 支持
PUBLISH-SUBSCRIBE 支持 支持 支持 支持
REQUEST-REPLY 支持 支持 - 支持
API完备性 低(静态配置)
多语言支持 支持,Java优先 语言无关 支持,Java优先 支持
单机呑吐量 万级 万级 十万级 单机万级
消息延迟 毫秒级 微秒级 毫秒级 毫秒级
可用性 高(主从) 高(主从) 很高(分布式) 非常高(分布式)
消息丢失 理论上不会丢失 理论上不会丢失
消息重复 - 可控制 理论上会有重复 允许重复
文档的完备性
提供快速入门
首次部署难度 -

# 2.3 选型参考

MQ 描述
RabbitMQ erlang开发,对消息堆积的支持并不好,当大量消息积压的时候,会导致 RabbitMQ 的性能急剧下降。每秒钟可以处理几万到十几万条消息
RocketMQ java开发,面向互联网集群化功能丰富,对在线业务的响应时延做了很多的优化, 大多数情况下可以做到毫秒级的响应,每秒钟大概能处理几十万条消息
Kafka Scala开发,面向日志功能丰富,性能最高。当你的业务场景中,每秒钟消息数量 没有那么多的时候,Kafka 的时延反而会比较高。所以,Kafka 不太适合在线业务场景。
ActiveMQ java开发,简单,稳定,性能不如前面三个。小型系统用也ok,但是不推荐。推荐 用互联网主流的

# 2.4 业务为什么使用rocketmq 不用kafka

面试官:为什么公司一般使用RocketMQ而使用kafka的不是很多呢

我:因为kafka的诞生是作为大数据的一个中间件来使用的,MQ只是kafka的一个功能,相对于 RocketMQ,Kafka的功能相对于简单,RocketMQ设计的时候借鉴了很多kafka的设计,有着后发优 势,支持分布式、集群、副本等机制,消息延时也比较少,但是RocketMQ也有很多kafka不具备的功 能,比如:严格顺序消息,延时消息,服务端tag过滤,能够至少保证消费一个的可靠性策略,消息失 败后重试时间随着次数递增等都是很多业务所需要的,并且RocketMQ是经过了双十一的验证,所以很 多人说RocketMQ是专门为业务而生的。

# 2.5 为什么kafka不能支持大量的topic

面试官:为什么kafka对于大量的topic支持不好呢,能详细的说下吗

我:这句话如果严格意义上说的话应该是kafka在topic-partition过多而不是单纯的topic过多

这个要从kafka的存储结构来说起,kafka的最小存储单元是一个partition,一个partition由一个数据文 件以及一个索引文件组成,partition的存储文件是通过顺序写的方式来保证写入磁盘的效率的,但是如 果非常多的partition需要磁盘写入,那么就可能造成顺序写变成随机写,因为磁盘的IO流量就那麽大, 同时由很多个partition需要写入,就会变成随机写,性能反而会急剧下降,并且partition过多还是导致 元数据管理,副本同步的困难,这些都是导致kafka不能支持大量topic的原因。

# 2.6 为什么RcoketMQ可以支持大量的Topic

面试官:为什么RocketMQ可以支持大量的Topic呢

我:这个需要从RocketMQ的存储结构来说,RocketMQ的所有的数据存储在一个commitlog,然后则会 通过异步线程将commitlog的数据转移到其他对应的消费者的队列中,因为不管多少个队列也只有一个 commitlog文件,所有写入以及读取性能不会随着队列的增加而有显著的变化

2.6.1 下面是RocketMQ和kafka的topic的测试报告


# RabbitMQ交换器有哪些特点是什么

主要有以下4种

1.fanout

把所有发送到该交换器的消息路由到所有与该交换器绑定的队列中。

2.direct

把消息路由到BindingKey和RoutingKey完全匹配的队列中。

3.topic

Topic交换器能够根据路由键匹配根据规则匹配相关的交换器,主要的表达式有#和*

4.headers

不依赖路由键匹配规则路由消息。是根据发送消息内容中的 headers 属性进行匹配。性能差,基本用不到。

# 3.1 Virtual Host是什么

每一个RabbitMQ服务器都能创建虚拟的消息服务器,也叫虚拟主机(virtual host),简称vhost。

# 3.2 RabbitMQ集群中的节点类型有哪些

面试官:RabbitMQ的集群模式有哪些方案,以及节点类型有哪些

我:集群模式有以下几种

  • 单机版RabbitMQ

  • 集群模式,普通集群、高可用集群

  • 镜像队列

节点类型有以下几种

  • 内存节点ram,将变更写入内存。

  • 磁盘节点disc,磁盘写入操作。

  • RabbitMQ要求最少有一个磁盘节点。

# 3.3 导致的死信的几种原因

  • 消息被拒( Basic.Reject /Basic.Nack ) 且 requeue = false 。

  • 消息TTL过期。

  • 队列满了,无法再添加。

# 3.4 RabbitMQ如何保证消息的可靠性

面试官:如何保证RabbitMQ的消息可靠性。

1.生产者到RabbitMQ

失败通知,发布者确认,备用交换器。

2.RabbitMQ自身

持久化、高可用集群模式,镜像模式。

3.RabbitMQ到消费者

basicAck机制、死信队列、消息补偿机制。


# 什么是RocketMQ的事务消息

面试官:什么是RocketMQ的事务消息

事务消息就是MQ提供的类似XA的分布式事务能力,通过事务消息可以达到分布式事务的最终一致性。

半事务消息就是MQ收到了生产者的消息,但是没有收到二次确认,不能投递的消息。

# 4.1 实现原理

  • 生产者先发送一条半事务消息到MQ

  • MQ收到消息后返回ack确认

  • 生产者开始执行本地事务

  • 如果事务执行成功发送commit到MQ,失败发送rollback

  • 如果MQ长时间未收到生产者的二次确认commit或者rollback,MQ对生产者发起消息回查

  • 生产者查询事务执行最终状态

  • 根据查询事务状态再次提交二次确认

最终,如果MQ收到二次确认commit,就可以把消息投递给消费者,反之如果是rollback,消息会保存下来并且在3天后被删除。

# 4.2 Broker是怎么保存数据的呢

面试官:RocketMQ的存储结构是什么样的。

RocketMQ主要的存储文件包括commitlog文件、consumequeue文件、indexfile文件。

Broker在收到消息之后,会把消息保存到commitlog的文件当中,而同时在分布式的存储当中,每个 broker都会保存一部分topic的数据,同时,每个topic对应的messagequeue下都会生成 consumequeue文件用于保存commitlog的物理位置偏移量offset,indexfile中会保存key和offset的对 应关系。

# 4.3 RocketMQ如何保证消息的可靠性

# 4.3.1 生产者丢失

生产者可能因为网络原因,broker故障等原因丢失消息,使用同步发送不会出现这种问题,因为只要开 启master的同步刷盘,异步复制,基本上能够保证消息不会丢失,如果消息不能落盘到master中,这 消息就不会发送成功,并且发送端有发送方重试,如果消息发送失败会给出消息发送失败,我们在做处 理就可以的。

# 4.3.2 MQ丢失

如果生产者保证消息发送到MQ,而MQ收到消息后还在内存中,这时候宕机了又没来得及同步给从节点,就有可能导致消息丢失。

RocketMQ分为同步刷盘和异步刷盘两种方式,默认的是异步刷盘,就有可能导致消息还未刷到硬盘上 就丢失了,可以通过设置为同步刷盘的方式来保证消息可靠性,这样即使MQ挂了,恢复的时候也可以从磁盘中去恢复消息。

# 4.3.3 消费者丢失

==消费者丢失消息的场景==

消费者刚收到消息,此时服务器宕机,MQ认为消费者已经消费,不会重复发送消息,消息丢失。

RocketMQ默认是需要消费者回复ack确认,消费方不返回ack确认,重发的机制根据MQ类型的不同发 送时间间隔、次数都不尽相同,如果重试超过次数之后会进入死信队列,需要手工来处理了,消息在死 信队列的时间一般在72小时,如果不及时处理可能造成消息丢失。

# 4.4 RocketMQ如何保证的可靠性的

面试官:RocketMQ是如何保证集群的可靠性

我:RocketMQ在设计之出就保证了消息至少被消费一次,我们只需要保证消息能够正常投递到MQ中就可以的

# 4.4.1 发送方保证消息的可靠性

发送的的时候由三种方式,同步,异步,单向,其中同步异步都有发送发确认机制,如果发送失败会进行重试,而单向发送则只管发送不管结果

# 4.4.2 RocketMQ保证消息可靠性

  • RocketMQ由nameserver和broker组成,nameserver之间是不互相通讯的,这样当一个 nameserver挂掉后会找到另一个nameserver重试消息

  • RocketMQ的broker是一个主从架构,可以配置一主一从或者多主多从,MQ的主节点收到消息后 通过同步策略会将消息复制到从节点中。

  • RocketMQ的消息同步策略有同步复制和异步复制,而如果要保证消息不丢失可以选择同步复制, 只有消息同步复制到了从节点后才会确认消息发送到MQ成功

  • Broker 故障规避,默认情况下延迟规避策略只在重试时生效,例如在一次消息发送过程中如果遇 到消息发送失败,规避 broekr-a,但是在下一次消息发送时,即再次调用 DefaultMQProducer 的 send 方法发送消息时,还是会选择 broker-a 的消息进行发送,只要继续发送失败后,重试时再次规避 broker-a。

  • RocketMQ的存储方式有两种同步刷盘以及异步刷盘,同步刷盘是指消息必须写入磁盘后才会成 功,而异步刷盘是指消息必须只要写入内存就是成功,但是在还没有写入磁盘的过程中如果断电可 能造成部分消息丢失

# 4.4.3 消费端保证消息不丢失

  • RocketMQ为了保证消费进度不丢失使用了只记录最小的消费进度,比如消费端消费了100-200之 间的消息,如果第101条消息没有消费成功101后面的所以消息消费成功,如果这个时候消费者重 启也会导致消息从101-200之间的所有消息进行重试

  • 为了防止消息一直重试的问题,引入了死信队列,当消息一直消费不了的时候就会进入死信队列交 给死信队列处理

  • 如果消费成功后的消息因为删库跑步导致数据丢失,还可以通过RocketMQ的消息回溯功能将以前 消费过的消息在消费一遍

# 4.5 为什么RocketMQ不使用Zookeeper作为注册中心呢

  • 根据CAP理论,同时最多只能满足两个点,而zookeeper满足的是CP,也就是说zookeeper并不能 保证服务的可用性,zookeeper在进行选举的时候,整个选举的时间太长,期间整个集群都处于不可用的状态,而这对于一个注册中心来说肯定是不能接受的,作为服务发现来说就应该是为可用性而设计

  • 基于性能的考虑,NameServer本身的实现非常轻量,而且可以通过增加机器的方式水平扩展,增 加集群的抗压能力,而zookeeper的写是不可扩展的,而zookeeper要解决这个问题只能通过划分 领域,划分多个zookeeper集群来解决,首先操作起来太复杂,其次这样还是又违反了CAP中的A 的设计,导致服务之间是不连通的。

  • 持久化的机制来带的问题,ZooKeeper 的 ZAB 协议对每一个写请求,会在每个 ZooKeeper 节点 上保持写一个事务日志,同时再加上定期的将内存数据镜像(Snapshot)到磁盘来保证数据的一 致性和持久性,而对于一个简单的服务发现的场景来说,这其实没有太大的必要,这个实现方案太重了。而且本身存储的数据应该是高度定制化的。

  • 消息发送应该弱依赖注册中心,而RocketMQ的设计理念也正是基于此,生产者在第一次发送消息的时候从NameServer获取到Broker地址后缓存到本地,如果NameServer整个集群不可用,短时间内对于生产者和消费者并不会产生太大影响。

Last Updated: 7/24/2025, 4:36:33 PM