消息队列面试题

1. 项目中为什么使用mq

(1) 解耦

  • 一个模块, 调用其他多个模块的接口, 调用过程很复杂, 但又不是必须同步调用的时候, 就可以将这个调用最为一条消息放在mq里, 让被调用者取订阅相关的消息, 此时被调用的那几个模块就可以从mq里得知有一个模块调用我了
  • 不用考虑对方调用是否成功, 超时, 失败重试等
  • 主要是应用了消息队列的pub-sub模型

(2) 异步

  • 不用mq的同步高延时场景:
    还是以上场景, 模块A要分别调用模块B, 模块C, 模块D; 假设BCD三个模块分别处理3条sql, 5条sql, 4条sql, 且分别耗时200ms, 250ms, 700ms, 则模块A要想正常返回, 需要1150ms, 这在用户体验上十分不可接受(一般可接受范围在200ms以内)
  • 消峰
2. 引入消息队列后, 有什么坏处
  • 系统可用性降低
  • 系统与MQ之间的交互可能会产生问题
    • 消息重复: 比如系统A本来只应该调用系统B一次, 但是由于系统A和MQ交互发生问题 ,使得A存了2此请求在mq中, 则B会被请求2次
    • 消息丢失: 少了几次请求
    • 消息乱序: 调用次序变了
    • 消费者挂掉了: 系统BCD挂掉了, 系统A狂往队列中加数据
  • 一致性问题
    本来系统ABCD都成在能返回给用户成功, 加入mq后, 调用变成异步的了, 恰恰BCD中某个模块失败了, 但A却已经给用户正确的返回了
3. 如何保证mq的高可用性

(1) Rabbit mq高可用的做法

  • 普通集群模式
    数据只保存在某1个节点上, 而其它节点只负责响应request请求, 然后查找该请求的数据在那个节点上, 把数据从其他节点传输到本节点再返回给用户端
    这种方式没有任何高可用性
  • 镜像集群模式(数据副本, n台机器保存n分完整数据, 不存在partition, 只存在replication)
    每个机器都是完整的queue的数据, 这几个节点的数据互为备份, 但是仍然不是分布式的queue, 只能用一台机器存储数据
    (通过管理平台 增加一条镜像管理策略, 配置所有机器同部数据/某几台机器同步数据)

(2) kafka高可用性的做法

  • 数据分区(partition)
  • 数据备份(每个partition分为leader和follower)
4. mq consumer如何保证消息不被重复消费or如何保证消息的幂等性

(1) kafka
每条消息有一个offset, 可理解为每个消息的一个编号. consumer消费后返回offset(基于zookeeper里记录消费到了哪个offset) 或记录到kafka的一个特定队列中(__consumer_offsets下面)

如何保证消息重复消费的幂等性
(1) 为什么会出现数据的重复消费
比如kafka中, 如果consumer在消费后就宕机了, 而且还没来得及到zookeeper中记录自己消费的offset
(2) 如何保证幂等性
比如consumer消费数据时要写入databse, 可以记录一个消息主键, 重复消费时会报错但不会重复保存

5. 如何处理使用mq后消息存在丢失的问题
  • rabbitmq
    (1) 生产者把数据发往mq的时候丢了

    1. rabbitmq可以通过开启事务实现避免生产者这端丢数据. 这种模式是同步机制, 只等这条消息发送完毕再发送下一条消息
      channel.txSelect()
      try{
        // 发送消息
      }catch (异常){
        channel.txrollback;
        // 再次重试发送数据
      }
      
    2. 或者开启confirm机制.
      • 先把channel设置为confirm机制,
      • 然后发送消息,
      • 发送成功后, rabbitmq接收消息后会回调生产者本地的回调接口, 如果mq保存消息成功回调ack()方法, 如果保存失败则回调unack()方法
    这种方法是异步机制, 发送完不用等待回调就可以立刻发送下一条. 一般选用confirm机制
    

    (2) mq存储的数据丢了

    • rabbitmq开启持久化
      • a. 先把queue的元数据开启持久化
      • b. 把diliverymode设置为2, 让queue里的数据持久化
        但此时仍存在一个风险就是, 数据存到内存后, 还没来得及存到磁盘, mq就挂了, 导致数据丢失

    (3) 消费者处理失败导致消息丢失
    消费者从mq收到消息后就挂掉了, 但还没来得及消费, 而mq却认为消费者取走就代表消费完毕
    这种一般是开启了消费者的auto ack机制, 这样会让rabbitmq认为收到消息就是处理完毕, 此时若在处理过程中consumer宕机, 就会出现数据丢失问题
    所以应该关闭autoack机制, 在consumer处理完消息后在手动进行ack

  • kafka
    (1) kafka存储的数据丢失
    因为kafka的partition通过leader和follower进行数据高可用, 有时数据只写到了leader的内存, 还没复制到follower时, leader就宕机了, 此后将follower选举成leader的时候数据就丢失了
    解决办法: 设置4个参数

    1. kafka服务期设置
      • 为topic设置replication.factors大于1,
        确保数据至少有2个副本
      • min.insync.replicats:
        kafka的leader会对follower进行心跳, 如果超时认为该follower宕机. 该参数必须大于等于2, 表示数据至少有1个follower可以和leader正常通信
    2. producer段设置
      • ack=all:
        每条数据必须写入所有的follower才认为是写入成功了
      • retries=MAX:
        一旦失败, 就自动无限重试的重发数据. 该参数设置后配合ack=all, 使得当leader未写成功所有follower就宕机后leader切换, producer可以再次把数据发送给新leader

    (2) kafka消费端丢数据
    原理一样. kafka的自动提交offset机制, 导致consumer收到消息成功但处理过程中失败时造成数据丢失. 解决办法也是关闭自动提交offset, 来手动提交offset

6. 如何保证消息的顺序性

(1) 什么时候rabbitMq的数据会出现顺序性问题
当producer只有1个, consumer有多个, 且mq以队列模式存在(每个consumer依次从mq中拿数据), 这种时候, 即使mq中的消息不乱序, 但由于consumer处理消息的速度不同, 最终产生结果的顺序会不同(比如consumer是插入数据到数据库的话)
解决办法: 需要保证顺序的消息都放到1个queue里, 同时只被1个consumer消费

(2) 什么时候kafka出现顺序问题

  1. kafka可以保证写入一个partition的数据时有序的, 所以可以在写入数据时指定一个key
producer.send(new ProducerRecord<>(topic,partition,key, msg));

比如: 设置订单id为key, 这个key相关的数据全都会发送到1个partition中
2. kafka的另一个原则:
1个partition只能被1个消费者消费. 即如果partition有3个, 而开启了4个consumer, 那有1个consumer会闲置
3. 但是, 如果1个consumer内开启了多个线程处理数据, 则还会出现数据顺序的问题.
解决办法:
如果确实1个consumer需要多个线程并发加速处理数据, 那么可以为每个线程开启一个内存queue, 从kafka中得到数据时, 把key按照hash分发到不同的queue. 例如: 1个订单id的消息, 既可以保证生产者生产的数据在partition中有序, 也可以保证消费者在多线程下消费的顺序性

7. 有几百万消息积压在kafka消息队列怎么办

    消息积压是因为consumer出问题或下线了, 重启恢复consumer后, 首先创建1个新的topic, 让这个新的topic拥有的partition数量是原来的10倍, 修改原来的consumer代码逻辑为: 把老topic的数据消费后就存入新的topic. 然后再启动10倍的consumer消费这个新topic下的消息.
为什么不能直接启动10倍的consumer消费老topic?
    因为kafka里1个partition只能被1个consumer消费, 老topic的partition数量不变的话, 即使新增consumer, 那些consumer也会被闲置

8. 如果让你来开发一个mq中间件, 如何设计这个架构

面试官心理分析:
    其实聊到这个问题,一般面试官要考察两块:
你有没有对某一个消息队列做过较为深入的原理的了解,或者从整体了解把握住一个消息队列的架构原理。
看看你的设计能力,给你一个常见的系统,就是消息队列系统,看看你能不能从全局把握一下整体架构设计,给出一些关键点出来。
说实话,问类似问题的时候,大部分人基本都会蒙,因为平时从来没有思考过类似的问题,大多数人就是平时埋头用,从来不去思考背后的一些东西。类似的问题,比如,如果让你来设计一个 Spring 框架你会怎么做?如果让你来设计一个 Dubbo 框架你会怎么做?如果让你来设计一个 MyBatis 框架你会怎么做?
回答思路:
     首先, 让mq支持数据的分布式存储, 其次支持高可用(数据副本), 支持数据的零丢失(数据副本的一致性问题)

rabbitMQ基本概念
  • 概念:
    • Brocker:消息队列服务器实体。
    • Exchange:消息交换机,指定消息按什么规则,路由到哪个队列。
    • Queue:消息队列,每个消息都会被投入到一个或者多个队列里。
    • Binding:绑定,它的作用是把exchange和queue按照路由规则binding起来。
    • Routing Key:路由关键字,exchange根据这个关键字进行消息投递。
    • Vhost:虚拟主机,一个broker里可以开设多个vhost,用作不同用户的权限分离。
    • Producer:消息生产者,就是投递消息的程序。
    • Consumer:消息消费者,就是接受消息的程序。
    • Channel:消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务。
  • 消息队列的使用过程大概如下:
    • 消息接收
      • 客户端连接到消息队列服务器,打开一个channel。
      • 客户端声明一个exchange,并设置相关属性。
      • 客户端声明一个queue,并设置相关属性。
      • 客户端使用routing key,在exchange和queue之间建立好绑定关系。
    • 消息发布
      • 客户端投递消息到exchange。
      • xchange接收到消息后,就根据消息的key和已经设置的binding,进行消息路由,将消息投递到一个或多个队列里。
  • AMQP 里主要要说两个组件:
    Exchange 和 Queue
    绿色的 X 就是 Exchange ,红色的是 Queue ,这两者都在 Server 端,又称作 Broker
    这部分是 RabbitMQ 实现的,而蓝色的则是客户端,通常有 Producer 和 Consumer 两种类型。
  • Exchange通常分为四种:
    • fanout:该类型路由规则非常简单,会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中,相当于广播功能
    • direct:该类型路由规则会将消息路由到binding key与routing key完全匹配的Queue中
    • topic:与direct类型相似,只是规则没有那么严格,可以模糊匹配和多条件匹配
    • headers:该类型不依赖于routing key与binding key的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配
kafka基本概念:

topic: 队列, 相当于rabbitmq的queue
partition: 数据的分片(分布式存储数据), rabbitmq中没有分布式存储的概念

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值