面试知识点梳理及相关面试题(十)-- rabbitmq

1. 什么是消息队列?

队列中存放的内容是 message ,是一种跨进程的通信机制,用于上下游传递消息。

在互联网架构中,MQ 是一种非常常见的上下游 “逻辑解耦 + 物理解耦” 的消息通信服务。

2. 为什么使用消息队列?

  1. 在分布式系统下具备异步,削峰,负载均衡等一系列高级功能;
  2. 拥有持久化的机制,进程消息,队列中的信息也可以保存下来。
  3. 实现消费者和生产者之间的解耦。
  4. 对于高并发场景下,利用消息队列可以使得同步访问变为串行访问达到一定量的限流,利于数据库的操作。
  5. 可以使用消息队列达到异步下单的效果,排队中,后台进行逻辑下单。

2.1 流量削峰

比如某一时刻开放购买的场景,消费者不能一下子消费那么多订单,可以让订单在队列中排队,过几秒钟再处理,总比不能处理的体验要好。

2.2 应用解耦

可以实现消费者和生产者之间的解耦。比如说下单的动作需要多个服务配合,如果一个服务出现了问题,下单都无法完成,但是如果是基于消息队列,前一个服务可以将请求发送到队列,继续去做自己的事情。
面试知识点梳理及相关面试题(十)-- rabbitmq_第1张图片

2.3 异步处理

有些服务间调用是异步的,例如 A 调用 B,B 需要花费很长时间执行,但是 A 需要知道 B 什么时候可以执行完。

这时候只需要在A调用完B,A去做自己的事情,B处理完成后,发送一条消息到MQ,MQ再将次消息转发给A。
面试知识点梳理及相关面试题(十)-- rabbitmq_第2张图片

3. Kafka、ActiveMQ、RabbitMQ、RocketMQ 有什么优缺点?

面试知识点梳理及相关面试题(十)-- rabbitmq_第3张图片

4. 你们公司用的什么消息队列?为什么选择rabbitmq作为你们的消息队列

4.1 Kafka

Kafka 主要特点是基于 Pull 的模式来处理消息消费,追求高吞吐量,一开始的目的就是用于日志收集和传输,适合产生大量数据的互联网服务的数据收集业务。大型公司建议可以选用,如果有日志采集功能,肯定是首选 kafka 了。

4.2 RocketMQ

天生为金融互联网领域而生,对于可靠性要求很高的场景,尤其是电商里面的订单扣款,以及业务削峰,在大量交易涌入时,后端可能无法及时处理的情况。RoketMQ 在稳定性上可能更值得信赖,这些业务场景在阿里双 11 已经经历了多次考验,如果你的业务有上述并发场景,建议可以选择 RocketMQ。

4.3 RabbitMQ

结合 erlang 语言本身的并发优势,性能好时效性微秒级,社区活跃度也比较高,管理界面用起来十分方便,如果你的数据量没有那么大,中小型公司优先选择功能比较完备的 RabbitMQ

对于我的业务来说就是

  1. 数据量没有那么大,并且使用场景比较简单
  2. 同时rabbitmq本身可以支撑高并发、高吞吐、性能很高
  3. 同时有非常完善便捷的后台管理界面可以使用,
  4. 还可以支持集群
  5. 社区活跃,有问题能够及时得到解决

所以rabbitMQ就足够了。

5. 使用rabbitmq的场景。

  1. 服务间异步通信
  2. 顺序消费
  3. 定时任务
  4. 请求削峰
  5. 消息推送

6. rabbitmq四大核心概念

  1. 生产者:产生数据发送消息的程序。
  2. 交换机:是 RabbitMQ 非常重要的一个部件,一方面它接收来自生产者的消息,另一方面它将消息推送到队列中。交换机必须确切知道如何处理它接收到的消息,是将这些消息推送到特定队列还是推送到多个队列,亦或者是把消息丢弃,这个得有交换机类型决定。
  3. 队列:队列是 RabbitMQ 内部使用的一种数据结构,尽管消息流经 RabbitMQ 和应用程序,但它们只能存储在队列中。队列仅受主机的内存和磁盘限制的约束,本质上是一个大的消息缓冲区。许多生产者可以将消息发送到一个队列,许多消费者可以尝试从一个队列接收数据。
  4. 消费者:大多时候是一个等待接收消息的程序。请注意生产者,消费者和消息中间件很多时候并不在同一机器上。同一个应用程序既可以是生产者又是可以是消费者。

7. rabbitmq相关名词介绍

面试知识点梳理及相关面试题(十)-- rabbitmq_第4张图片

  • Producer: 消息生产者,就是投递消息的程序

  • Connection:producer/consumer 和 broker 之间的 TCP 连接。

  • Channel信道:如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP Connection 的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线程,通常每个线程创建单独的 channel 进行通讯,AMQP method 包含了 channel id 帮助客户端和 message broker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的 Connection 极大减少了操作系统建立 TCP connection 的开销。每个channel代表一个会话任务,由Exchange、Queue、RoutingKey三个才能决定一个从Exchange到Queue的唯一的线路。

  • Broker:接收和分发消息的应用,RabbitMQ Server 就是 Message Broker。简单来说就是消息队列服务器实体

  • Virtual host:出于多用户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出多个 vhost,每个用户在自己的 vhost 创建 exchange/queue 等。可以理解为虚拟broker ,即mini-RabbitMQ server。其内部均含有独立的queue、exchange和binding等,但最最重要的是,其拥有独立的权限系统,可以做到vhost范围的用户控制。当然,从RabbitMQ的全局角度,vhost可以作为不同权限隔离的手段(一个典型的例子就是不同的应用可以跑在不同的 vhost 中)。

  • Exchange:消息交换机。message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发消息到 queue 中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout (multicast)

  • Queue:消息队列载体,每个消息都会被投入到一个或多个队列。消息最终被送到这里等待 consumer 取走。

  • Binding:绑定,exchange 和 queue 之间的虚拟连接,即将exchange和queue按照路由规则绑定起来,binding 中可以包含 routing key,Binding 信息被保存到 exchange 中的查询表中,用于 message 的分发依据

  • Routing Key: 路由关键字,exchange根据这个关键字进行消息投递

8. 消息的分发方式有哪些:

  1. 轮询:同一个队列的多个消费者按顺序消费队列中的消息
  2. 不公平分发:通过设置 channel.basicQos(1);来设置不公平分发(0是公平分发),只要有消费者空闲,并且队列中有消息,就接收队列中的消息并执行。
  3. 预取值分发:队列中有7个消息,A消费者设置了预取值为2,B为5,则会以轮询的方式,直到A的信道上有2条消息,B有5条,接下来再有第8条第9条就以不公平方式分发。通过channel.basicQos(prefetchCount);设置

9. rabbitmq如何保证消息不丢失?

详见:https://blog.csdn.net/weixin_39724194/article/details/130050301

消息不可靠的情况可能是消息丢失,劫持等原因;

丢失又分为:

  1. 生产者丢失消息:通过发布确认解决
  2. 消息列表丢失消息:消息持久化解决
  3. 消费者丢失消息:消息应答机制解决

9.1 生产者丢失消息:即blackhold问题

生产者向 exchange 投递了 message ,而由于各种原因导致该message 丢失,但发送者却不知道。

可导致 blackholed 的情况:

  1. 向未绑定 queue 的exchange 发送 message;
  2. exchange 以 binding_key key_A 绑定了 queue queue_A,但向该 exchange 发送 message 使用的 routing_key 却是 key_B

从生产者弄丢数据这个角度来看,RabbitMQ提供transaction和confirm模式来确保生产者不丢消息;

9.1.1 transaction模式:几乎不用

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

9.1.2 confirm模式(发布确认):主要用

一旦channel进入confirm模式,所有在该信道上发布的消息都将会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后:

  • rabbitMQ就会发送一个ACK给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了;
  • 如果rabbitMQ没能处理该消息,则会发送一个Nack消息给你,你可以进行重试操作。
channel.queueDeclare(MqConnectUtil.QUEUE_NAME, true, false, false, null);
// 开启发布确认
channel.confirmSelect();
// 开始时间
long begin = System.currentTimeMillis();

// 准备消息的监听器,监听哪些消息成功了,哪些消息失败了
// 参数一:确认成功的回调函数
// 参数二:确认失败的回调函数
// deliveryTag:消息的标记
// multiple:    是否为批量确认
channel.addConfirmListener((deliveryTag, multiple) -> {},
        (deliveryTag, multiple) -> {log.debug("未确认的消息:{}", deliveryTag);});

// 批量发消息
for (int i = 0; i < MESSAGE_COUNT; i++) {
    channel.basicPublish("", UUID.randomUUID().toString(), null, String.valueOf(i).getBytes());
}
9.1.3 如何处理confirm模式中未处理(未确认)的消息?

最好的解决方案就是把未确认的消息放到一个基于内存的能被发布线程访问的队列, 比如说用 ConcurrentSkipListMap在 confirm callbacks 与发布线程之间进行消息的传递。

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

处理消息队列丢数据的情况,一般是开启持久化磁盘的配置。

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

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

如果消息还没来得及消费,但是rabbitmq挂了,但是因为持久化数据了,所以在重启时,会从日志中读取未消费的消息

9.2.1 关于消息队列持久化步骤:
  1. 持久化队列:
    之前创建的队列都是非持久化的,rabbitmq 如果重启的话,该队列就会被删除掉,如果要队列实现持久化需要在声明队列的时候把 durable 参数设置为持久化。将queueDeclare的第二个参数设为true:channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);
  2. 持久化消息:
    虽然持久化了队列,如果消息不进行持久化,消息还是会丢失,所以我们同样需要持久化消息
    在这里插入图片描述

9.3 消费者丢失消息:消息应答机制

9.3.1 消息应答有两种方式:

选用规则是高吞吐量和数据传输安全性方面做权衡

  1. 自动应答:
  2. 手动应答

消费者丢数据一般是因为采用了自动确认消息模式,消费者在收到消息之后,处理消息之前,会自动回复RabbitMQ已收到消息;如果这时处理消息失败,就会丢失该消息;

9.3.2 解决方案:

改为手动确认消息即可!处理消息成功后,手动回复确认消息。

9.3.3 手动应答的三个方法:
  • Channel.basicAck (用于肯定确认):RabbitMQ 已知道该消息成功被处理,可以将其丢弃了。
  • Channel.basicNack (用于否定确认)
  • Channel.basicReject (用于否定确认):与 Channel.basicNack 相比少一个参数,不处理该消息了直接拒绝,可以将其丢弃了。

否定确认后如果设置了requeue=true,则消息会重新回到队列头的位置,等待被消费,如果设置了requeue为false,则会被丢弃

9.3.4 批量应答

手动应答可以通过设置批量应答来减少网络拥堵。

通过设置:
面试知识点梳理及相关面试题(十)-- rabbitmq_第5张图片

  • true 代表批量应答 channel 上未应答的消息:比如说队列往信道中放了4个消息 5、6、7、8, 当前消息是 8 那么此时 5-8 的这些还未应答的消息都会被确认收到消息应答。
  • false 同上面相比只会应答8的消息, 5、6、7 这三个消息依然不会被确认收到消息应答。

10. 什么是交换机?有哪些类型?

生产者生产的消息从不会直接发送到队列, 生产者只能将消息发送到交换机 (exchange),然后交换机将消息推入到设置的队列中。
面试知识点梳理及相关面试题(十)-- rabbitmq_第6张图片
如果我们没有指定交换机,用的空字符串的话,表示是默认或无名称交换机。消息从路由发送到队列是由routingKey(bindingKey)绑定key指定的。

交换机的类型包括:

  • 直接 (direct)
  • 主题 (topic)
  • 标题 (headers)
  • 扇出 (fanout)
  • 延迟交换机(需要安装插件,不是默认拥有)

可以通过如下代码创建exchange并设置名称和类型:

/**
 * 声明一个 exchange
 * 1.exchange 的名称
 * 2.exchange 的类型
 */
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");

10.1 扇出交换机:fanout

Fanout 这种类型非常简单。正如从名称中猜到的那样,它是将接收到的所有消息广播到它知道的所有队列中。哪怕交换机和队列关联的key是不同的也不影响

10.2 直接交换机:direct

消息只去到它绑定的 routingKey 队列中去
面试知识点梳理及相关面试题(十)-- rabbitmq_第7张图片
如上图的绑定关系下,生产者发布消息到 exchange 上,绑定键为 orange 的消息会被发布到队列 Q1。绑定键为 black或green的消息会被发布到队列 Q2,其他消息类型的消息将被丢弃

10.2.1 多重绑定

如果 exchange 的绑定类型是 direct,但是它绑定的多个队列的 key 如果都相同,在这种情况下虽然绑定类型是 direct 但是它表现的就和 fanout 有点类似了,就跟广播差不多
面试知识点梳理及相关面试题(十)-- rabbitmq_第8张图片

10.3 主题交换机:Topic

简单说就是routingkey模糊匹配的交换机

当我们的队列想要接收某个类型的消息的时候,比如现在的消息有桔子苹果汽水、桔子橙子汽水、西瓜苹果汽水,我的某个队列只想要有桔子的汽水,这时候使用direct类型的交换机就比较难做到了,这时候我们可以使用主题交换机。

发送到类型是 topic 交换机的消息的 routing_key 不能随意写,必须满足一定的要求,它必须是一个单词列表,以点号分隔开。这些单词可以是任意单词。比如说:”stock.usd.nyse”, “nyse.vmw”, “quick.orange.rabbit”. 这种类型的。当然这个单词列表最多不能超过 255 个字节。

在这个规则列表中,其中有两个替换符是大家需要注意的:

  • *(星号) 可以代替一个单词
  • #(井号) 可以替代零个或多个单词

例:
面试知识点梳理及相关面试题(十)-- rabbitmq_第9张图片

  • Q1–> 绑定的是:
    • 中间带 orange 带 3 个单词的字符串 (.orange.)
  • Q2–> 绑定的是:
    • 最后一个单词是 rabbit 的 3 个单词 (..rabbit)
    • 第一个单词是 lazy 的多个单词 (lazy.#)

面试知识点梳理及相关面试题(十)-- rabbitmq_第10张图片

10.4 延迟交换机

需要通过安装延迟队列插件来实现延迟交换机,默认是不自带的。
面试知识点梳理及相关面试题(十)-- rabbitmq_第11张图片

功能:生产者将消息直接分给延迟交换机,延迟交换机在经过延迟时间后将消息分发

10.5 标题交换机

标题交换机和扇形交换机都不需要路由键routingKey,标题交换机是通过Headers头部来将消息映射到队列的,有点像HTTP的Headers,Hash结构中要求携带一个键“x-match”,这个键的Value可以是any或者all,这代表消息携带的Hash是需要全部匹配(all),还是仅匹配一个键(any)就可以了。相比直连交换机,首部交换机的优势是匹配的规则不被限定为字符串(string)而是Object类型。

  • any: 只要在发布消息时携带的有一对键值对headers满足队列定义的多个参数arguments的其中一个就能匹配上,注意这里是键值对的完全匹配,只匹配到键了,值却不一样是不行的;
  • all:在发布消息时携带的所有Entry必须和绑定在队列上的所有Entry完全匹配

面试知识点梳理及相关面试题(十)-- rabbitmq_第12张图片

11. 交换机和队列是如何关联的?bindings是什么?

交换机是如何跟队列进行关联的呢?,就是通过bingdings,我们可以在bindings中设置routingkey,并关联一个队列,将交换机和队列进行绑定。

  • 一个交换机可以和多个队列绑定
  • 一个交换机可和一个队列建立多个绑定关系(routingKey不同)

面试知识点梳理及相关面试题(十)-- rabbitmq_第13张图片

11.1 如何进行绑定

  1. 可以在rabbitmq的操作界面对交换机进行bindings设置(设置routingkey)从而绑定队列。
    面试知识点梳理及相关面试题(十)-- rabbitmq_第14张图片
  2. 也可以在代码中设置:(‘01’是routingkey)
    channel.queueBind(queueName, EXCHANGE_NAME, "01")

12. 什么是临时队列?

没有持久化的队列就是临时队列。每当我们连接到mq时,我们都需要一个全新的空队列,为此我们可以创建一个具有随机名称的队列,一旦我们断开了消费者的连接,临时队列将被自动删除。

看Features是否有D的标识,如果没有就说明是临时队列。
在这里插入图片描述

13. 什么是死信队列?

producer 将消息投递到 broker 或者直接到 queue 里了,consumer 从 queue 取出消息进行消费,但某些时候由于特定的原因导致 queue 中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信,有死信自然就有了死信队列。

死信进入到死信队列后,可以被专门用于处理死信队列消息的消费者消费。

13.1 死信产生的原因

  1. 消息TTL过期
  2. 队列达到最大长度(队列满了,无法再添加数据到mq中)
  3. 消息被拒绝(basci.reject或者basic.nack)并且requeue = false(如果设置requeue为true那就是在被nack后会重新回到原队列等待被消费)

面试知识点梳理及相关面试题(十)-- rabbitmq_第15张图片

13.2 死信队列也满了怎么办?

如果出现死信队列和普通队列都满的情况,此时考虑消费者消费能力不足,可以对消费者开多线程进行处理。

13.3 死信的应用:

应用场景:

  • 为了保证订单业务的消息数据不丢失,需要使用到 RabbitMQ 的死信队列机制,当消息发生异常时,将消息投入死信队列中。
    • 后续等到环境好了之后,再消费死信队列中的消息
  • 用户在商城下单成功并点击去支付后在指定时间未支付时自动失效
    • 当做延迟队列来处理,死信可以在指定时间内被消费者消费

14. 延迟队列

概念:延时队列就是用来存放需要在指定时间被处理的元素的队列

14.1 应用场景

  1. 订单在十分钟之内未支付则自动取消;
  2. 新创建的店铺,如果在十天内都没有上传过商品,则自动发送消息提醒;
  3. 用户注册成功后,如果三天内没有登陆则进行短信提醒;
  4. 用户发起退款,如果三天内没有得到处理则通知相关运营人员;
  5. 预定会议后,需要在预定的时间点前十分钟通知各个与会人员参加会议

这些场景都有一个特点:需要在某个事件发生之后或者之前的指定时间点完成某一项任务。

14.2 实现方式:延迟交换机

需要通过安装延迟队列插件来实现延迟交换机,默认是不自带的。
面试知识点梳理及相关面试题(十)-- rabbitmq_第16张图片

功能:生产者将消息直接分给延迟交换机,延迟交换机在经过延迟时间后将消息分发

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

详见:https://blog.csdn.net/weixin_39724194/article/details/130050301

首先谈谈什么是幂等性:用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用

15.1 什么是重复消费?

消费者在消费 MQ 中的消息时,MQ 已把消息发送给消费者,消费者在给 MQ 返回 ack 时网络中断, 故 MQ 未收到确认信息,该条消息会重新发给其他的消费者,或者在网络重连后再次发送给该消费者,但实际上该消费者已成功消费了该条消息,造成消费者消费了重复的消息。

15.2 什么是消息重复投递?

在海量订单生成的业务高峰期,生产端有可能就会重复发生了消息,这时候消费端就要实现幂等性, 这就意味着我们的消息永远不会被消费多次,即使我们收到了一样的消息。

15.3 如何解决?

MQ 消费者的幂等性的解决一般使用全局 ID 或者写个唯一标识,比如时间戳或者 UUID ,订单消费者消费 MQ 中的消息也可利用 MQ 的该 id 来判断,或者可按自己的规则生成一个全局唯一 id,每次消费消息时用该 id 先判断该消息是否已消费过。

业界主流的幂等性有两种操作:

  1. 唯一 ID + 指纹码机制,用数据库主键去重;
    指纹码:我们的一些规则或者时间戳加别的服务给到的唯一信息码,它并不一定是我们系统生成的,基本都是由我们的业务规则拼接而来,但是一定要保证唯一性,然后就利用查询语句进行判断这个 id 是否存在数据库中,优势就是实现简单就一个拼接,然后查询判断是否重复;劣势就是在高并发时,如果是单个数据库就会有写入性能瓶颈当然也可以采用分库分表提升性能,但也不是我们最推荐的方式
  2. 利用 redis 的原子性去实现。
    利用 redis 执行 setnx 命令,天然具有幂等性,从而实现不重复消费。

当然也可以根据业务不同去进行操作:

  1. 比如,你拿到这个消息做数据库的insert操作。那就容易了,给这个消息做一个唯一主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据。
  2. 再比如,你拿到这个消息做redis的set的操作,那就容易了,不用解决,因为你无论set几次结果都是一样的,set操作本来就算幂等操作。

16. 什么是优先级队列?

简单说:就是根据队列权重,优先消费权重高的消息

16.1 使用场景

在我们系统中有一个订单催付的场景,我们的客户在天猫下的订单,淘宝会及时将订单推送给我们,如果在用户设定的时间内未付款那么就会给用户推送一条短信提醒,很简单的一个功能对吧。

但是,天猫商家对我们来说,肯定是要分大客户和小客户的对吧,比如像苹果、小米这样大商家一年起码能给我们创造很大的利润,所以理应当然,他们的订单必须得到优先处理。

所以苹果小米等大客户的催付订单我们可以放到优先级高的队列中,其他的催付订单可以放到优先级较低的队列中。

17. 什么是惰性队列?你怎么理解?

惰性队列会尽可能的将消息存入磁盘中,而在消费者消费到相应的消息时才会被加载到内存中,它的一个重要的设计目标是能够支持更长的队列,即支持更多的消息存储。当消费者由于各种各样的原因 (比如消费者下线、宕机亦或者是由于维护而关闭等) 而致使长时间内不能消费消息造成堆积时,惰性队列就很有必要了。

18. 消息是基于什么传输的?

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

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

19.1 系统可用性降低

系统引入的外部依赖越多,越容易挂掉,本来你就是A系统调用BCD三个系统的接口就好了,人 ABCD四个系统好好的,没啥问题,你偏加个MQ进来,万一MQ挂了咋整?MQ挂了,整套系统崩溃了,你不就完了么。

系统复杂性提高硬生生加个MQ进来,你怎么保证消息没有重复消费?怎么处理消息丢失的情况?怎么保证消息传递的顺序性?头大头大,问题一大堆,痛苦不已。

19.2 一致性问题

A系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是BCD三个系统那里,BD两个系统写库成功了,结果C系统写库失败了,咋整?你这数据就不一致了。

所以消息队列实际是一种非常复杂的架构,你引入它有很多好处,但是也得针对它带来的坏处做各种额外的技术方案和架构来规避掉,最好之后,你会发现,妈呀,系统复杂度提升了一个数量级,也许是复杂了10倍。但是关键时刻,用,还是得用的

19.3 系统复杂度提高

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

20. rabbitmq的工作模式有哪些

20.1 简单模式:无需交换机

面试知识点梳理及相关面试题(十)-- rabbitmq_第17张图片

  1. 消息产生消息,将消息放入队列
  2. 消息的消费者(consumer) 监听 消息队列,如果队列中有消息,就消费掉,消息被拿走后,自动从队列中删除(隐患 消息可能没有被消费者正确处理,已经从队列中消失了,造成消息的丢失,这里可以设置成手动的ack,但如果设置成手动ack,处理完后要及时发送ack消息给队列,否则会造成内存溢出)。

20.2 工作模式(竞争模式):无需交换机

面试知识点梳理及相关面试题(十)-- rabbitmq_第18张图片

  1. 消息产生者将消息放入队列,消费者可以有多个,消费者1和消费者2同时监听同一个队列,消息被消费。
  2. C1 C2共同争抢当前的消息队列内容,谁先拿到谁负责消费消息(隐患:高并发情况下,默认会产生某一个消息被多个消费者共同使用,可以设置一个开关(syncronize) 保证一条消息只能被一个消费者使用)。

20.3 发布订阅模式(三种):通过交换机实现

面试知识点梳理及相关面试题(十)-- rabbitmq_第19张图片

  1. 1个生产者,多个消费者
  2. 每一个消费者都有自己的一个队列
  3. 生产者没有将消息直接发送到队列,而是发送到了交换机
  4. 每个队列都要绑定到交换机
  5. 生产者发送的消息,经过交换机到达队列,实现一个消息被多个消费者获取的目的
  6. 交换机一方面:接收生产者发送的消息。另一方面:知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。
    • Fanout:广播,将消息交给所有绑定到交换机的队列
    • Direct:定向,把消息交给符合指定routing key 的队列
    • Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列
  7. Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失
20.3.1 广播模型(fanout交换机)
  1. 多个消费者,每个消费者绑定自己的队列
  2. 每个队列都需要绑定交换机
  3. 生产者生产消息,交给交换机,生产者不能决定消息被交换机分发给哪个队列
  4. 交换机把消息发送给绑定过的所有队列
  5. 订阅该队列的消费者都能消费消息,实现一个消息被多个消费者消费
20.3.2 direct模型(direct交换机)

面试知识点梳理及相关面试题(十)-- rabbitmq_第20张图片
在Direct模型下,队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key),消息的发送方在向Exchange发送消息时,也必须指定消息的routing key。

  • P:生产者,向Exchange发送消息,发送消息时,会指定一个routing key。
  • X:Exchange(交换机),接收生产者的消息,然后把消息递交给 与routing key完全匹配的队列
  • C1:消费者,其所在队列指定了需要routing key 为 error 的消息
  • C2:消费者,其所在队列指定了需要routing key 为 info、error、warning 的消息
20.3.3 主题模型(topic交换机)

面试知识点梳理及相关面试题(十)-- rabbitmq_第21张图片
Topic 类型的 Exchange 与 Direct 相比,都是可以根据 RoutingKey 把消息路由到不同的队列。只不过 Topic 类型 Exchange 可以让队列在绑定 Routing key 的时候使用通配符!

  • 星号井号代表通配符
  • 星号代表1个单词,井号代表1到多个单词

21. 如何保证rabbitmq的消息的顺序性

拆分多个queue(消息队列),每个queue(消息队列) 一个consumer(消费者),就是多一些queue(消息队列)而已,确实是麻烦点;

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

22. 为什么不对所有的消息进行持久化?

  1. 必然导致性能的下降,因为写磁盘比写RAM慢的多,message的吞吐量可能有10倍的差距
  2. message的持久化机制用在RabbitMQ的内置cluster方案时会出现“坑爹”问题。矛盾点在于,若message设置了persistent属性,但queue未设置durable属性,那么当该queue的owner node出现异常后,在未重建该queue前,发往该queue 的message将被 blackholed;若 message 设置了 persistent属性,同时queue也设置了durable属性,那么当queue的owner node异常且无法重启的情况下,则该queue无法在其他node上重建,只能等待其owner node重启后,才能恢复该 queue的使用,而在这段时间内发送给该queue的message将被 blackholed 。
    所以,是否要对message进行持久化,需要综合考虑性能需要,以及可能遇到的问题。若想达到100,000 条/秒以上的消息吞吐量(单RabbitMQ服务器),则要么使用其他的方式来确保message的可靠delivery ,要么使用非常快速的存储系统以支持全持久化(例如使用SSD)。
    另外一种处理原则是:仅对关键消息作持久化处理(根据业务重要程度),且应该保证关键消息的量不会导致性能瓶颈

23. rabbitmq是如何保证高可用的?集群如何搭建?

RabbitMQ是比较有代表性的,因为是基于主从(非分布式)做高可用性的,我们就以RabbitMQ为例子讲解第一种MQ的高可用性怎么实现。RabbitMQ有三种模式:单机模式、普通集群模式、镜像集群模式。

23.1 单机模式

就是Demo级别的,一般就是你本地启动了玩玩儿的?,没人生产用单机模式

23.2 普通集群模式:

意思就是在多台机器上启动多个RabbitMQ实例,每个机器启动一个。

你创建的queue,只会放在一个RabbitMQ实例上,但是每个实例都同步queue的元数据(元数据可以认为是queue的一些配置信息,通过元数据,可以找到queue所在实例)。

你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从queue所在实例上拉取数据过来。这方案主要是提高吞吐量的,就是说让集群中多个节点来服务某个queue的读写操作。

23.3 镜像集群模式:

这种模式,才是所谓的RabbitMQ的高可用模式。跟普通集群模式不一样的是,在镜像集群模式下,你创建的queue,无论元数据还是 queue 里的消息都会存在于多个实例上,就是说,每个RabbitMQ节点都有这个queue的一个完整镜像,包含queue的全部数据的意思。然后每次你写消息到queue的时候,都会自动把消息同步到多个实例的queue上。

RabbitMQ有很好的管理控制台,就是在后台新增一个策略,这个策略是镜像集群模式的策略,指定的时候是可以要求数据同步到所有节点的,也可以要求同步到指定数量的节点,再次创建queue的时候,应用这个策略,就会自动将数据同步到其他的节点上去了。

这样的好处在于,你任何一个机器宕机了,没事儿,其它机器(节点)还包含了这个queue的完整数据,别的consumer都可以到其它节点上去消费数据。坏处在于,第一,这个性能开销也太大了吧,消息需要同步到所有机器上,导致网络带宽压力和消耗很重!RabbitMQ一个queue的数据都是放在一个节点里的,镜像集群下,也是每个节点都放这个queue的完整数据。

24. 有几百万消息持续积压几小时,怎么办?

临时紧急扩容:

  1. 先修复consumer的问题,确保其恢复消费速度,然后将现有consumer都停掉。
  2. 新建一个topic,partition是原来的10倍,临时建立好原先10倍的queue数量。
  3. 然后写一个临时的分发数据的consumer程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的10倍数量的queue。
  4. 接着临时征用10倍的机器来部署consumer,每一批consumer消费一个临时queue的数据。这种做法相当于是临时将 queue 资源和consumer资源扩大10倍,以正常的10倍速度来消费数据。
  5. 等快速消费完积压数据之后,得恢复原先部署的架构,重新用原先的consumer机器来消费消息。

25. 如何解决消息队列的延时以及过期失效问题?

假设你用的是RabbitMQ,RabbtiMQ是可以设置过期时间的,也就是 TTL。如果消息在queue中积压超过一定的时间就会被RabbitMQ给清理掉,这个数据就没了。那这就是第二个坑了。

这就不是说数据会大量积压在mq里,而是大量的数据会直接搞丢。我们可以采取一个方案,就是批量重导,这个我们之前线上也有类似的场景干过。就是大量积压的时候,我们当时就直接丢弃数据了,然后等过了高峰期以后,比如大家一起喝咖啡熬夜到晚上12点以后,用户都睡觉了。这个时候我们就开始写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,
然后重新灌入mq里面去,把白天丢的数据给他补回来。也只能是这样了。假设1万个订单积压在mq里面,没有处理,其中 1000个订单都丢了,你只能手动写程序把那1000个订单给查出来,手动发到mq里去再补一次。

26. 消息队列满了以后该怎么处理?

27. 可以在地理上分开的不同数据中心使用 RabbitMQ cluster 么?

不能。第一,你无法控制所创建的 queue 实际分布在 cluster 里的哪个 node 上(一般使用 HAProxy + cluster 模型时都是这样),这可能会导致各种跨地域访问时的常见问题;第二,Erlang 的 OTP 通信框架对延迟的容忍度有限,这可能会触发各种超时,导致业务疲于处理;第三,在广域网上的连接失效问题将导致经典的“脑裂”问题,而RabbitMQ 目前无法处理(该问题主要是说 Mnesia)

28. 设计MQ思路,如何保证高可用。

比如说这个消息队列系统,我们从以下几个角度来考虑一下:

首先这个mq得支持可伸缩性吧,就是需要的时候快速扩容,就可以增加吞吐量和容量,那怎么搞?设计个分布式的系统呗,参照一下kafka的设计理念,broker->topic->partition,每个partition放一个机器,就存一部分数据。如果现在资源不够了,简单啊,给topic增加partition,然后做数据迁移,增加机器,不就可以存放更多数据,提供更高的吞吐量了?

其次你得考虑一下这个mq的数据要不要落地磁盘吧?那肯定要了,落磁盘才能保证别进程挂了数据就丢了。那落磁盘的时候怎么落啊?顺序写,这样就没有磁盘随机读写的寻址开销,磁盘顺序读写的性能是很高的,这就是 kafka 的思路。

其次你考虑一下你的mq的可用性啊?这个事儿,具体参考之前可用性那个环节讲解的kafka的高可用保障机制。多副本 -> leader&follower->broker挂了重新选举 leader 即可对外服务。能不能支持数据0丢失啊?可以呀,有点复杂的。

29. 如何保证数据一致性问题?

比如订单系统通过mq将不同任务分给不同系统如库存、支付、短信,如果在某一个服务出现异常后,保证各个服务的数据一致性问题?

根据不同的业务需求做具体的业务分析,可以使用rocketmq来实现事务消息,也可以通过DB来实现消息状态处理;

看看这个分析:
https://blog.csdn.net/weixin_43466542/article/details/101676944

30. AMQP是什么?

RabbitMQ就是 AMQP 协议的 Erlang 的实现(当然 RabbitMQ 还支持 STOMP2、 MQTT3 等协议 ) AMQP 的模型架构 和 RabbitMQ 的模型架构是一样的,生产者将消息发送给交换器,交换器和队列绑定 。

RabbitMQ 中的交换器、交换器类型、队列、绑定、路由键等都是遵循的 AMQP 协议中相 应的概念。目前 RabbitMQ 最新版本默认支持的是 AMQP 0-9-1。

31. AMQP协议3层?

  • Module Layer:协议最高层,主要定义了一些客户端调用的命令,客户端可以用这些命令实现自己的业务逻辑。
  • Session Layer:中间层,主要负责客户端命令发送给服务器,再将服务端应答返回客户端,提供可靠性同步机制和错误处理。
  • TransportLayer:最底层,主要传输二进制数据流,提供帧的处理、信道服用、错误检测和数据表示等。

你可能感兴趣的:(面试知识点整理,java-rabbitmq,rabbitmq,面试)