开篇介绍
大家好,我是Java最全面试题库
的提裤姐,今天这篇是中间件面试题系列的第一篇,主要总结了RabbitMQ相关的面试题;在后续,会沿着第一篇开篇的知识线路一直总结下去,做到日更!如果我能做到百日百更,希望你也可以跟着百日百刷,一百天养成一个好习惯。
什么是RabbitMQ?为什么使用RabbitMQ?有什么好处?
RabbitMQ是一款开源的,Erlang编写的,基于AMQP协议的,消息中间件;
可以用它来:解耦
、异步
、削峰
。
- 优点:解耦、异步、削峰;
- 缺点:降低了系统的稳定性:系统中使用了消息队列,如果消息队列挂了,那么系统也会挂掉。因此,系统可用性会降低;
加入了消息队列,要多考虑很多方面的问题,比如:一致性问题、如何保证消息不被重复消费、如何保证消息可靠性传输等。因此,需要考虑的东西更多,复杂性增大。
RabbitMQ基本概念有哪些?
Broker
: 消息队列服务器实体Exchange
: 消息交换机,它指定消息按特定规则,路由到哪个队列Queue
: 消息队列载体,每个消息都会被投入到一个或多个队列Binding
: 绑定,它的作用就是把exchange和queue按照路由规则绑定起来Routing Key
: 路由关键字,exchange根据这个关键字进行消息投递VHost
: vhost 可以理解为虚拟 broker ,即 mini-RabbitMQ server。其内部均含有独立的 queue、exchange 和 binding 等,拥有独立的权限系统,可以做到 vhost 范围的用户控制。Producer
: 消息生产者Consumer
: 消息消费者Channel
: 消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务
如何保证RabbitMQ不被重复消费?
正常情况下,消费者在消费消息的时候,消费完毕后,会发送一个确认消息给消息队列,消息队列就知道该消息被消费了,就会将该消息从消息队列中删除;
但是因为网络传输等等故障,确认信息没有传送到消息队列,导致消息队列不知道自己已经消费过该消息了,再次将消息分发给其他的消费者。
解决思路:
保证消息的唯一性,就算是多次传输,不要让消息的多次消费带来影响;保证消息等幂性;
比如:在写入消息队列的数据做唯一标识,消费消息时,根据唯一标识判断是否消费过;
RabbitMQ 概念里的 channel、exchange 和 queue 是逻辑概念,还是对应着进程实体?分别起什么作用?
queue 具有自己的 erlang
进程;
exchange 内部实现为保存 binding
关系的查找表;
channel 是实际进行路由工作的实体,即负责按照 routing_key 将 message 投递给 queue 。
由 AMQP 协议描述可知,channel
是真实 TCP 连接之上的虚拟连接
,所有 AMQP 命令都是通过 channel 发送的,且每一个 channel 有唯一的 ID
。一个 channel 只能被单独一个操作系统线程使用,故投递到特定 channel 上的 message 是有顺序的。但一个操作系统线程上允许使用多个 channel 。
rabbitmq 消息是如何路由的?
从概念上来说,消息路由必须有三部分:交换器、路由、绑定。
生产者把消息发布到交换器上;绑定决定了消息如何从路由器路由到特定的队列;消息最终到达队列,并被消费者接收。
消息发布到交换器时,消息将拥有一个路由键(routing key)
,在消息创建时设定。
通过队列路由键,可以把队列绑定到交换器上。
消息到达交换器后,RabbitMQ会将消息的路由键与队列的路由键进行匹配(针对不同的交换器有不同的路由规则)。如果能够匹配到队列,则消息会投递到相应队列中;如果不能匹配到任何队列,消息将进入 “黑洞”。
常用的交换器主要分为一下三种:
direct
:如果路由键完全匹配,消息就被投递到相应的队列fanout
:如果交换器收到消息,将会广播到所有绑定的队列上topic
:可以使来自不同源头的消息能够到达同一个队列。 使用topic交换器时,可以使用通配符,比如:“*”
匹配特定位置的任意文本,“.”
把路由键分为了几部分,“#”
匹配所有规则等。
特别注意:发往topic交换器的消息不能随意的设置选择键(routing_key),必须是由"."隔开的一系列的标识符组成。
如何保证RabbitMQ消息的可靠传输?
消息不可靠的情况可能是消息丢失,劫持等原因
丢失又分为:
- 生产者丢失消息
- 消息列表丢失消息
- 消费者丢失消息
生产者丢失消息:
从生产者弄丢数据这个角度来看,RabbitMQ提供transaction机制
和confirm模式
来确保生产者不丢消息;
- transaction机制:发送消息前,开启事务(channel.txSelect()),然后发送消息,如果发送过程中出现什么异常,事务就会回滚(channel.txRollback()),如果发送成功则提交事务(channel.txCommit())。
- confirm模式(用的居多):一旦channel进入confirm模式,所有在该信道上发布的消息都将会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后; RabbitMQ就会发送一个ACK给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了;
如果RabbitMQ没能处理该消息,则会发送一个Nack消息给你,你可以进行重试操作。
消息队列丢数据:
消息持久化,处理消息队列丢数据的情况,一般是开启持久化磁盘的配置:
将队列的持久化标识durable
设置为true
,则代表是一个持久的队列 发送消息的时候将 deliveryMode=2
这样设置以后,即使RabbitMQ挂了,重启后也能恢复数据
消费者丢失消息:
消费者丢数据一般是因为采用了自动确认消息模式,改为手动确认消息
消费者在收到消息之后,处理消息之前,会自动回复RabbitMQ已收到消息; 如果这时处理消息失败,就会丢失该消息;
解决方案:处理消息成功后,手动回复确认消息。
如何保证RabbitMQ消息的顺序性?
1、单线程
消费保证消息的顺序性;
2、对消息进行编号
,消费者处理消息是根据编号处理消息;
死信队列和延迟队如何使用?
死信队列:
死信,顾名思义就是无法被消费的消息,字面意思可以这样理解,一般来说,producer将消息投递到broker或者直接到queue里了,consumer从queue取出消息进行消费,但某些时候由于特定的原因导致queue中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信,有死信,自然就有了死信队列;
死信消息:
1、消息被拒绝(Basic.Reject
或Basic.Nack
)并且设置 requeue 参数的值为 false
2、消息过期了
3、队列达到最大的长度
过期消息:
在 rabbitmq 中存在2种方可设置消息的过期时间,
- 第一种通过对队列进行设置,这种设置后,该队列中所有的消息都存在相同的过期时间,
- 第二种通过对消息本身进行设置,那么每条消息的过期时间都不一样。
如果同时使用这2种方法,那么以过期时间小的那个数值为准。当消息达到过期时间还没有被消费,那么那个消息就成为了一个 死信 消息。
队列设置:在队列申明的时候使用 x-message-ttl
参数,单位为 毫秒
单个消息设置:是设置消息属性的 expiration
参数的值,单位为 毫秒
延时队列:在rabbitmq中不存在延时队列,但是我们可以通过设置消息的过期时间和死信队列来模拟出延时队列。消费者监听死信交换器绑定的队列,而不要监听消息发送的队列。