面试准备:MQ消息队列常见面试题汇总

1.为什么要使用消息队列?

简单来说就是:解耦、异步、削峰。
消息队列可以对系统异步功能进行剥离,减少功能耦合,提供开发效率;
消息队列可以削峰限流,确保下游消费者稳定运行。
参考:Java架构直通车——MQ应用场景与JMS规范
在这里插入图片描述

2.消息队列推、拉模式区别?

  • push模式
    推模式指的是客户端与服务端建立好网络长连接,服务方有相关数据,直接通过长连接通道推送到客户端。其优点是 及时 ,一旦有数据变更,客户端立马能感知到;另外对客户端来说逻辑简单,不需要关心有无数据这些逻辑处理。缺点是不知道客户端的数据消费能力,可能导致数据积压在客户端,来不及处理。另外,服务端保存push状态(哪些客户端发送成功,哪些没有发送成功),push状态是集中保存在服务端的,负载均衡也由服务端进行统一处理。
  • pull模式
    客户端主动从服务端轮询拉取数据,其优点是不存在推模式中数据积压的问题。故缺点是可能不够及时,对客户端来说需要考虑数据拉取相关逻辑,这就需要客户端保存pull状态(以便在故障重启的时候恢复),pull状态是分散保存在客户端的,负载均衡由客户端之间做调配(比如使用zookeeper)。

kafka是通过一个提交日志记录的方式来存储消息记录,采用拉模式,而RabbitMQ有推模式和拉模式,其中推模式采用socket长连接来做通知。

3.消息队列有什么缺点?

系统可靠性降低,解耦后,多个系统通过消息中间件交互,消息中间件挂了整个系统就挂了;
系统开发复杂度提升,需要考虑消息的处理,包括消息幂等性(重复消费问题),消息保序性(一个订单多条消息问题),以及消息中间件本身的持久化和稳定性可靠性;
消息一致性问题,如果一个功能发给多个系统,要所有系统都执行成功才算成功时,需要确保一个功能多个消息的完整一致性;

4.消息如何保证幂等性?

参考:MQ 生产端可靠性投递和消费端幂等性保障方案
参考:Java架构直通车——幂等性接口设计

5.消息队列为什么会出现重复消费?

  1. 客户端宕机

kafka实际上有个offset的概念,就是每个消息写进去,都有一个offset,代表他的序号,然后consumer消费了数据之后,每隔一段时间,会把自己消费过的消息的offset提交一下,代表我已经消费过了,下次我要是重启啥的,你就让我继续从上次消费到的offset来继续消费吧。

但是凡事总有意外,比如我们之前生产经常遇到的,就是你有时候重启系统,看你怎么重启了,如果碰到点着急的,直接kill进程了,再重启。这会导致consumer有些消息处理了,但是没来得及提交offset,尴尬了。重启之后,少数消息会再次消费一次。

  1. 网络问题

另外,只要通过网络交换数据,就无法避免重复消费。AMQP 定义了消费者确认机制(message ack),如果服务端投递消息到消费端,然而消费端虽然收到这个消息,但是消费端发送的ACK消息丢失,那服务端Broker就无法获得 ACK确认,那么消息会被重新放入队列。所以 AMQP 提供的是“至少一次交付”(at-least-once delivery),异常情况下,消息会被重复消费,此时业务要实现幂等性(重复消息处理)。

6.消息如何保证可靠性?

参考:MQ 生产端可靠性投递和消费端幂等性保障方案

7.如何保证消息顺序?

  • 对于activeMQ,可以通过exclusive方式让一个queue始终被一个consumer消费;或者messageGroup方式;
  • 对于rabbitMQ, 一个queue对应一个consumer才行,多个consumer对应一个queue就容易错乱;
  • 对于kafka,partition的消息是保序的。然后它强制要求一个partition只能投递给同组内的一个consume(即partition出只能有一个consumer,不能投递给同组内两个consumer,只是同组内的consumer却可以消费多个partition),所以不存在多个消费者错乱的问题。然后生产者可以设定一个key,同一个key的可以发送到同一个partition中,这样同一个key的消息在partition中是保序;
    如果kafka在消费端开启多线程,也会出现乱序。可以在消费端加队列,按照业务保序增加内存队列,这样队列中的消息与partition中顺序是一致的,然后多线程从队列中取数据,每次取一个完整顺序的消息进行处理即可。

8.消息队列积压怎么办?

消息发送主要涉及三方:producer/consumer/broker,所以发生消息积压要从这三方来看。

  • 发生消息积压后,producer端服务降级,关闭一些非核心业务,减少消息的产生。
  • 扩容consumer实例,注意扩容后必须同步扩容主题中的分区(也叫队列)数量,确保 Consumer 的实例数和分区数量是相等的,因为在队列中的消费是单线程的,如果只扩容了consumer实例,没有扩容队列数量,则不会有效果的。
  • 其实不用太关注消息队列,因为消息队列本身的处理能力要远远大于业务系统的处理能力。主流消息队列的单个节点,消息收发的性能可以达到每秒钟处理几万至几十万条消息的水平,还可以通过水平扩展 Broker 的实例数成倍地提升处理能力。

9.简单介绍Kafka?Kafka架构?

Kafka是一个分布式流式处理平台,提供超高的吞吐量,ms 级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。一般用于大数据实时计算领域中以及日志采集中。因为Kafka提供了发布和订阅消息流,这个功能类似于消息队列,这也是 Kafka 也被归类为消息队列的原因。kafka 唯一的一点劣势是有可能消息重复消费,但是如果Kafka应用于大数据、日志采集的话,对数据准确性会造成极其轻微的影响。

Kafka本身的结构遵从JMS的发布订阅模型,为了提高效率,消息被分批次写入Kafka ,这些消息属于同一个主题(topic)和分区(partition)。不过消息分批需要在时间延迟和吞吐量之前做一个权衡(单位时间内批次越大,单位时间内处理的消息就越多,但是单个消息的传输时间就会越长)。主题可以被分为若干个分区,消息以追加的方式写入分区,然后以先入先出FIFO的顺序读取。
一个独立的Kafka 服务器被称为broker。broker 接收来自生产者的消息,为消息设置偏移量,并提交消息到磁盘保存。broker 为消费者提供服务,对读取分区的请求作出响应,返回已经提交到磁盘上的消息。broker 是集群的组成部分,Kafka 集群为分区(Partition)引入了多副本(Replica)机制来提供冗余。

参考:Kafka概述

10.简单介绍RabbitMQ?RabbitMQ架构?

参考:Java架构直通车——RabbitMQ核心概念和消息流转方式

参考:Java架构直通车——RabbitMQ集群架构模式

RabbitMQ 在吞吐量方面虽然稍逊于 Kafka 和 RocketMQ ,但是由于它基于 erlang 开发,所以并发能力很强,性能极其好,延时很低,达到微秒级。

11. JMS是什么?AMPQ又是什么?

JMS(JAVA Message Service,java消息服务),JMS的客户端之间可以通过JMS服务进行异步的消息传输。

JMS提供了两种消息模型,peer-2-peer(点对点)以及publish-subscribe(发布订阅)模型。当采用点对点模型时,消息将发送到一个队列(queue),该队列的消息只能被一个消费者消费。而采用发布订阅模型时,消息将发送到一个**主题(topic)**上,消息可以被多个消费者消费。在发布订阅模型中,生产者和消费者完全独立,不需要感知对方的存在。

  • 点到点(P2P)模型
    在这里插入图片描述

  • 发布/订阅(Pub/Sub)模型   
    在这里插入图片描述


AMQP是一种应用层协议的开放标准,最早用于解决金融领不同平台之间的消息传递交互,这种标准使得实现了AMQP协议天然性就是跨平台的。与JMS本质上不同的是,AMQP不从API层进行限定,而是直接定义网络交换的数据格式。意味着我们可以使用Java的AMQP provider,同时使用一个python的producer加一个rubby的consumer。

另外,在AMQP中,消息路由(messagerouting)和JMS存在一些差别,在AMQP中增加了Exchange和binding的角色。producer将消息发送给Exchange,binding决定Exchange的消息应该发送到那个queue。

12. Kafka 如何保证消息的消费顺序?

  1. 很简单的保证消息消费顺序的方法:1 个 Topic 只对应一个 Partition。这样当然可以解决问题,但是破坏了 Kafka 的设计初衷。

  2. Kafka 中发送 1 条消息的时候,可以指定 topic, partition, key,data(数据) 4 个参数。如果你发送消息的时候指定了 Partition 的话,所有消息都会被发送到指定的 Partition。并且,同一个 key 的消息可以保证只发送到同一个 partition,这个我们可以采用表/对象的 id 来作为 key 。

13. Kafka 如何保证消息不丢失?

生产者弄丢消息:

  1. Kafka 生产者(Producer) 使用 send 方法发送消息实际上是异步的操作,我们可以通过 get()方法获取调用结果,但是这样也让它变为了同步操作。
  2. 采用为其添加回调函数的形式,如果消息发送失败的话,我们检查失败的原因之后可以设定一个retries次数并重新发送。比如:
ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send(topic, o);
future.addCallback(result -> logger.info("生产者成功发送消息到topic:{} partition:{}的消息", result.getRecordMetadata().topic(), result.getRecordMetadata().partition()),
                ex -> logger.error("生产者发送消失败,原因:{}", ex.getMessage()));

消费者弄丢消息:
我们知道消息在被追加到 Partition(分区)的时候都会分配一个特定的偏移量(offset)。偏移量(offset)表示 Consumer 当前消费到的 Partition(分区)的所在的位置。Kafka 通过偏移量(offset)可以保证消息在分区内的顺序性。
当消费者拉取到了分区的某个消息之后,消费者会自动提交了 offset。自动提交的话会有一个问题,试想一下,当消费者刚拿到这个消息准备进行真正消费的时候,突然挂掉了,消息实际上并没有被消费,但是 offset 却被自动提交了。
解决办法也比较粗暴,我们手动关闭闭自动提交 offset,每次在真正消费完消息之后之后再自己手动提交 offset 。 但是,细心的朋友一定会发现,这样会带来消息被重新消费的问题。比如你刚刚消费完消息之后,还没提交 offset,结果自己挂掉了,那么这个消息理论上就会被消费两次。

Kafka 弄丢了消息:
Kafka 为分区(Partition)引入了多副本(Replica)机制。假如 leader 副本所在的 broker 突然挂掉,那么就要从 follower 副本重新选出一个 leader ,但是 leader 的数据还有一些没有被 follower 副本的同步的话,就会造成消息丢失。
解决办法是可以设置消息被多个或者全部副本同步后才算消息被成功接收。

14. Kafka 为什么吞吐量大?

  1. 顺序读写

磁盘的顺序读写是磁盘使用模式中最有规律的,并且操作系统也对这种模式做了大量优化,Kafka就是使用了磁盘顺序读写来提升的性能。Kafka的message是不断追加到本地磁盘文件末尾的,而不是随机的写入,这使得Kafka写入吞吐量得到了显著提升 。

物理上存储的其实是 Partition,每一个 Partition 最终对应一个目录,里面存储所有的消息和索引文件。任何发布到 Partition 的消息都会被追加到 Partition 数据文件的尾部,这样的顺序写磁盘操作让 Kafka 的效率非常高(经验证,顺序写磁盘效率比随机写内存还要高,这是 Kafka 高吞吐率的一个很重要的保证)。

  1. Page Cache

为了优化读写性能,Kafka利用了操作系统本身的Page Cache(在内存里),就是利用操作系统自身的内存而不是JVM空间内存。这样做的好处有:

  • 避免Object消耗:如果是使用 Java 堆,Java对象的内存消耗比较大,通常是所存储数据的两倍甚至更多。
  • 避免GC问题:随着JVM中数据不断增多,垃圾回收将会变得复杂与缓慢,使用系统缓存就不会存在GC问题

相比于使用JVM或in-memory cache等数据结构,利用操作系统的Page Cache更加简单可靠。首先,操作系统层面的缓存利用率会更高,因为存储的都是紧凑的字节结构而不是独立的对象。其次,操作系统本身也对于Page Cache做了大量优化,提供了 write-behind、read-ahead以及flush等多种机制。再者,即使服务进程重启,系统缓存依然不会消失,避免了in-process cache重建缓存的过程。

  1. Zero-Copy

linux操作系统 “零拷贝” 机制使用了sendfile方法, 允许操作系统将数据从Page Cache 直接发送到网络,只需要最后一步的copy操作将数据复制到 NIC 缓冲区, 这样避免重新复制数据。
在这里插入图片描述

  1. 分区分段+索引

Kafka的message是按topic分类存储的,topic中的数据又是按照一个一个的partition即分区存储到不同broker节点。每个partition对应了操作系统上的一个文件夹,partition实际上又是按照segment分段存储的。这也非常符合分布式系统分区分桶的设计思想。

通过这种分区分段的设计,Kafka的message消息实际上是分布式存储在一个一个小的segment中的,每次文件操作也是直接操作的segment。为了进一步的查询优化,Kafka又默认为分段后的数据文件建立了索引文件,就是文件系统上的.index文件。这种分区分段+索引的设计,不仅提升了数据读取的效率,同时也提高了数据操作的并行度

  1. 批量读写

Kafka数据读写也是批量的而不是单条的。
除了利用底层的技术外,Kafka还在应用程序层面提供了一些手段来提升性能。最明显的就是使用批次。在向Kafka写入数据时,可以启用批次写入,这样可以避免在网络上频繁传输单个消息带来的延迟和带宽开销。假设网络带宽为10MB/S,一次性传输10MB的消息比传输1KB的消息10000万次显然要快得多。

  1. 批量压缩

在很多情况下,系统的瓶颈不是CPU或磁盘,而是网络IO,对于需要在广域网上的数据中心之间发送消息的数据流水线尤其如此。进行数据压缩会消耗少量的CPU资源,不过对于kafka而言,网络IO更应该需要考虑。

如果每个消息都压缩,但是压缩率相对很低,所以Kafka使用了批量压缩,即将多个消息一起压缩而不是单个消息压缩
Kafka允许使用递归的消息集合,批量的消息可以通过压缩的形式传输并且在日志中也可以保持压缩格式,直到被消费者解压缩
Kafka支持多种压缩协议,包括Gzip和Snappy压缩协议

总结:
Kafka速度的秘诀在于,它把所有的消息都变成一个批量的文件,并且进行合理的批量压缩,减少网络IO损耗,通过mmap提高I/O速度,写入数据的时候由于单个Partion是末尾添加所以速度最优;读取数据的时候配合sendfile直接暴力输出。

15. Kafka和RabbitMQ对比?

  1. 在应用场景方面
    • RabbitMQ遵循AMQP协议,由内在高并发的erlanng语言开发,用在实时的对可靠性要求比较高的消息传递上,适合企业级的消息发送订阅,也是比较受到大家欢迎的。
    • kafka是Linkedin于2010年12月份开源的消息发布订阅系统,它主要用于处理活跃的流式数据,大数据量的数据处理上。常用日志采集,数据采集上。
  2. 在架构模型方面
    • RabbitMQ遵循AMQP协议,RabbitMQ的broker由Exchange,Binding,queue组成,其中exchange和binding组成了消息的路由键;客户端Producer通过连接channel和server进行通信,Consumer从queue获取消息进行消费(长连接,queue有消息会推送到consumer端,consumer循环从输入流读取数据)。rabbitMQ以broker为中心;有消息的确认机制
    • kafka遵从一般的MQ结构,producer,broker,consumer,以consumer为中心,消息的消费信息保存的客户端consumer上,consumer根据消费的点,从broker上批量pull数据;无消息确认机制
  3. 在吞吐量
    • kafka具有高的吞吐量,内部采用消息的批量处理,zero-copy机制,数据的存储和获取是本地磁盘顺序批量操作,具有O(1)的复杂度,消息处理的效率很高。
    • rabbitMQ在吞吐量方面稍逊于kafka,他们的出发点不一样,rabbitMQ支持对消息的可靠的传递,支持事务,不支持批量的操作;基于存储的可靠性的要求存储可以采用内存或者硬盘。
  4. 在可用性方面
    • rabbitMQ支持miror的queue,主queue失效,miror queue接管。
    • kafka kafka的broker支持主备模式。
  5. 在集群负载均衡方面
    • kafka采用zookeeper对集群中的broker、consumer进行管理,可以注册topic到zookeeper上;通过zookeeper的协调机制,producer保存对应topic的broker信息,可以随机或者轮询发送到broker上;并且producer可以基于语义指定分片,消息发送到broker的某分片上。
    • rabbitMQ的负载均衡需要单独的loadbalancer进行支持。
  • 2
    点赞
  • 97
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值