消息队列面试题

2020-04-26  本文已影响0人  AlbenXie

一. 为什么要使用消息队列

  • 消息队列可以对系统进行解耦,提高响应速度,系统功能向内聚合,对外开放;
  • 消息队列可以对系统异步功能进行剥离,减少功能耦合,提供开发效率;
  • 消息队列可以削峰限流,确保下游消费者稳定运行;

二. 消息队列有什么缺点

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

三. 各种MQ的比较

综上,总结如下:


【activeMQ】

  • 优点:技术成熟,功能齐全,历史悠久,有大量公司在使用
  • 缺点:偶尔会有较低概率丢失数据,而且社区已经不怎么维护5.15.X版本
  • 使用场景:主要用于系统解耦和异步处理,不适用与大数据量吞吐情况。互联网公司很少适用

【rabitMQ】

  • 优点:吞吐量高,功能齐全,管理界面易用,社区活跃,性能极好,;
  • 缺点:吞吐量只是万级,erlang难以二次开发和掌控;集群动态扩展非常麻烦;
  • 使用场景:吞吐量不高而要求低延迟,并且不会频繁调整和扩展的场景。非常适合国内中小型互联网公司适用,因为管理界面非常友好,可以在界面进行配置和优化/集群监控。

【rocketMQ】

  • 优点:支持百千级大规模topic。吞吐量高(十万级,日处理上百亿)。接口易用。,分布式易扩展,阿里支持。java开发易于掌控。
  • 缺点:与阿里(社区)存在绑定。不兼容JMS规范。
  • 使用场景:高吞吐量

【kafka】

  • 优点:超高吞吐量,超高可用性和可靠性,分布式易扩展
  • 缺点:topic支持少,MQ功能简单,消息可能会重复消费影响数据精确度
  • 使用场景:超高吞吐量场景而数据精确度没那么高,天然适合大数据实时计算和日志采集场景

三. activeMQ和rabbitMQ/kafka的架构图

四. 消息如何保证幂等性

例如kafka的offset可能是消费者批量处理后才提交到zk,重启后再消费时就可能会收到重复消息,需要消费者在处理消息时做幂等性设计,即先判断是否消费过,把已消费的放到本地缓存或者redis中,每次消费时先做个判断即可。

五 消息如何保证不丢失

生产者投递消息到MQ,MQ存储消息,消费者从MQ消费消息:
要分别确保上述三个过程都是成功的,有如下做法:


【以rabbitMQ举例】

  1. 发送时启用消息事务,channel发送失败就回滚。但是不推荐使用,因为事务时同步的,性能会下降。
  2. 发送时使用confirm模式(就是回调模式)。推荐使用,因为是异步的,吞吐量高。消息发送后,回调消息发送成功或者失败的接口。那么业务层面也就可以根据是否发送成功和失败做处理,比如发送前缓存到redis,发送成功后从redis中移除,对于在redis中一直没有处理的,再进行重发操作。
  1. 对queue进行持久化
  2. 对queue的数据进行持久化

一般出现在autoACK模式下,即收到消息就反馈给MQ已处理,但消费者本身还没有真正完成处理就挂了,那么这个消息就会丢失(重启后也找不到了)。
解决方案:业务逻辑处理完后再手动对消息进行ACK。

image

【以kafka举例】

主要是因为kafka的多分区机制,当数据从leader的partition同步到其他follower的partition时,刚好leader挂了,此时选举某个同步慢的follower为leader,此时未同步的数据就丢失。需要从如下三个方面进行配置:

  1. 每个topic的partition副本应该大于1;以确保数据有备份;
  2. leader能够感知到至少有一个follower与自己是快速同步不掉队的;以确保切换leader是可行的;
  3. 数据在所有follower的partition中存储完成后,才给生成者发送ack消息;以确保所有partition的消息保持一致;
  4. 生产者可设置发送失败后无限重试(也就会卡住,消息不会丢);

与上述rabbit情况相同,主要是autoACK模式造成offset自动提交,建议都做成手动提交offset。

六 如何保证消息顺序

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


    image

七 消息队列积压怎么办

  • 当消费者出现异常,很容易引起队列积压,如果一秒钟1000个消息,那么一个小时就是几千万的消息积压,是非常可怕的事情,但是生产线上又有可能会出现;
  • 当消息积压来不及处理,rabbitMQ如果设置了消息过期时间,那么就有可能由于积压无法及时处理而过期,这消息就被丢失了;

解决方法如下:

  • 不建议在生产环境使用数据过期策略,一是数据是否丢失无法控制,二是一旦积压就很有可能丢失;建议数据的处理都有代码来控制;
  • 当出现消息积压时,做法就是临时扩大consumer个数,让消息快速消费,一般都是通过业务逻辑的手段来完成:如下:

【rabbitmq解决积压范例】

  1. 修复consumer代码故障,确保consumer逻辑正确可以消费;
  2. 停止consumer,开启10倍20倍的queue个数;
    • 创建一个临时的consumer程序,消费积压的queue,并把消息写入到扩建的10倍queue中;
    • 再开启10倍20倍的consumer对新的扩充后队列进行消费;
    • 这种做法相当于通过物理资源扩充了10倍来快速消费;
  1. 当消费完成后,需要恢复原有架构,开启原来的consumer进行正常消费;

【kafka解决范例】

  1. 修复consumer代码故障,确保consumer逻辑正确可以消费;
  2. 停止consumer,新建topic,新建10倍20倍的partition个数;
    • 创建对应原topic的partition个数的临时的consumer程序,消费原来的topic,并把消息写入到扩建的新topic中;
    • 再开启对应新partition个数的consumer对新的topic进行消费;
    • 这种做法相当于通过物理资源扩充了10倍来快速消费;
  1. 当消费完成后,需要恢复原有架构,开启原来的consumer进行正常消费;

作者:华木公子
链接:https://www.jianshu.com/p/88a4da652e23
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

上一篇下一篇

猜你喜欢

热点阅读