RabbitMQ面试题集锦(精选)(另附思惟导图)

RabbitMQ面试题集锦(精选)

1.使用RabbitMQ有什么好处?

1.解耦,系统A在代码中直接调用系统B和系统C的代码,若是未来D系统接入,系统A还须要修改代码,过于麻烦!
2.异步,将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快响应速度
3.削峰,并发量大的时候,全部的请求直接怼到数据库,形成数据库链接异常java

2.RabbitMQ 中的 broker 是指什么?cluster 又是指什么?

broker 是指一个或多个 erlang node 的逻辑分组,且 node 上运行着 RabbitMQ 应用程序。
cluster 是在 broker 的基础之上,增长了 node 之间共享元数据的约束。node

3. RabbitMQ 概念里的 channel、exchange 和 queue 是逻辑概念,仍是对应着进程实体?分别起什么做用?

Queue 具备本身的 erlang 进程;exchange 内部实现为保存 binding 关系的查找表;channel 是实际进行路由工做的实体,即负责按照 routing_key 将 message 投递给 queue 。由 AMQP 协议描述可知,channel 是真实 TCP 链接之上的虚拟链接,全部 AMQP 命令都是经过 channel 发送的,且每个 channel 有惟一的 ID。一个 channel 只能被单独一个操做系统线程使用,故投递到特定 channel 上的 message 是有顺序的。但一个操做系统线程上容许使用多个 channel 。git

4. vhost 是什么?起什么做用?

vhost 能够理解为虚拟 broker ,即 mini-RabbitMQ server。其内部均含有独立的 queueexchangebinding 等,但最最重要的是,其拥有独立的权限系统,能够作到 vhost 范围的用户控制。固然,从 RabbitMQ 的全局角度,vhost 能够做为不一样权限隔离的手段(一个典型的例子就是不一样的应用能够跑在不一样的 vhost 中)。github

5. 消息基于什么传输?

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

6. 消息如何分发?

若该队列至少有一个消费者订阅,消息将以循环(round-robin)的方式发送给消费者。每条消息只会分发给一个订阅的消费者(前提是消费者可以正常处理消息并进行确认)。redis

7. 消息怎么路由?

从概念上来讲,消息路由必须有三部分:交换器路由绑定。生产者把消息发布到交换器上;绑定决定了消息如何从路由器路由到特定的队列;消息最终到达队列,并被消费者接收。算法

消息发布到交换器时,消息将拥有一个路由键(routing key),在消息建立时设定。
经过队列路由键,能够把队列绑定到交换器上。
消息到达交换器后,RabbitMQ会将消息的路由键与队列的路由键进行匹配(针对不一样的交换器有不一样的路由规则)。若是可以匹配到队列,则消息会投递到相应队列中;若是不能匹配到任何队列,消息将进入 “黑洞”。数据库

经常使用的交换器主要分为一下三种:编程

  • direct:若是路由键彻底匹配,消息就被投递到相应的队列
  • fanout:若是交换器收到消息,将会广播到全部绑定的队列上
  • topic:可使来自不一样源头的消息可以到达同一个队列。使用topic交换器时,可使用通配符。
    好比:“*” 匹配特定位置的任意文本, “.” 把路由键分为了几部分,“#” 匹配全部规则等。
    特别注意:发往topic交换器的消息不能随意的设置选择键(routing_key),必须是由"."隔开的一系列的标识符组成。

8. 什么是元数据?元数据分为哪些类型?包括哪些内容?与 cluster 相关的元数据有哪些?元数据是如何保存的?元数据在 cluster 中是如何分布的?

在非 cluster 模式下,元数据主要分为 Queue 元数据(queue 名字和属性等)、Exchange 元数据(exchange 名字、类型和属性等)、Binding 元数据(存放路由关系的查找表)、Vhost 元数据(vhost 范围内针对前三者的名字空间约束和安全属性设置)。
cluster 模式下,还包括 cluster 中 node 位置信息和 node 关系信息。元数据按照 erlang node 的类型肯定是仅保存于 RAM 中,仍是同时保存在 RAM 和 disk 上。元数据在 cluster 中是全 node 分布的。安全

9. 在单node 系统和多 node 构成的 cluster 系统中声明 queue、exchange ,以及进行 binding 会有什么不一样?

答:当你在单 node 上声明 queue 时,只要该 node 上相关元数据进行了变动,你就会获得 Queue.Declare-ok 回应;而在 cluster 上声明 queue ,则要求 cluster 上的所有 node 都要进行元数据成功更新,才会获得 Queue.Declare-ok 回应。另外,若 node 类型为 RAM node 则变动的数据仅保存在内存中,若类型为 disk node 则还要变动保存在磁盘上的数据。

死信队列&死信交换器:DLX 全称(Dead-Letter-Exchange),称之为死信交换器,当消息变成一个死信以后,若是这个消息所在的队列存在x-dead-letter-exchange参数,那么它会被发送到x-dead-letter-exchange对应值的交换器上,这个交换器就称之为死信交换器,与这个死信交换器绑定的队列就是死信队列。

10. 如何确保消息正确地发送至RabbitMQ?

RabbitMQ使用发送方确认模式,确保消息正确地发送到RabbitMQ
发送方确认模式:将信道设置成confirm模式(发送方确认模式),则全部在信道上发布的消息都会被指派一个惟一的ID。一旦消息被投递到目的队列后,或者消息被写入磁盘后(可持久化的消息),信道会发送一个确认给生产者(包含消息惟一ID)。若是RabbitMQ发生内部错误从而致使消息丢失,会发送一条nack(not acknowledged,未确认)消息。发送方确认模式是异步的,生产者应用程序在等待确认的同时,能够继续发送消息。当确认消息到达生产者应用程序,生产者应用程序的回调方法就会被触发来处理确认消息。

11. 如何确保消息接收方消费了消息?

接收方消息确认机制:消费者接收每一条消息后都必须进行确认(消息接收和消息确认是两个不一样操做)。只有消费者确认了消息,RabbitMQ才能安全地把消息从队列中删除。这里并无用到超时机制,RabbitMQ仅经过Consumer的链接中断来确认是否须要从新发送消息。也就是说,只要链接不中断,RabbitMQ给了Consumer足够长的时间来处理消息。

下面罗列几种特殊状况:

  • 若是消费者接收到消息,在确认以前断开了链接或取消订阅,RabbitMQ会认为消息没有被分发,而后从新分发给下一个订阅的消费者。(可能存在消息重复消费的隐患,须要根据bizId去重)
  • 若是消费者接收到消息却没有确认消息,链接也未断开,则RabbitMQ认为该消费者繁忙,将不会给该消费者分发更多的消息。

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

在消息生产时,MQ内部针对每条生产者发送的消息生成一个inner-msg-id,做为去重和幂等的依据(消息投递失败并重传),避免重复的消息进入队列;在消息消费时,要求消息体中必需要有一个bizId(对于同一业务全局惟一,如支付ID、订单ID、帖子ID等)做为去重和幂等的依据,避免同一条消息被重复消费。

这个问题针对业务场景来答分如下几点:

1.好比,你拿到这个消息作数据库的insert操做。那就容易了,给这个消息作一个惟一主键,那么就算出现重复消费的状况,就会致使主键冲突,避免数据库出现脏数据。

2.再好比,你拿到这个消息作redis的set的操做,那就容易了,不用解决,由于你不管set几回结果都是同样的,set操做原本就算幂等操做。

3.若是上面两种状况还不行,上大招。准备一个第三方介质,来作消费记录。以redis为例,给消息分配一个全局id,只要消费过该消息,将<id,message>以K-V形式写入redis。那消费者开始消费前,先去redis中查询有没消费记录便可。

13. 如何解决丢数据的问题?

1.生产者丢数据

生产者的消息没有投递到MQ中怎么办?从生产者弄丢数据这个角度来看,RabbitMQ提供transaction和confirm模式来确保生产者不丢消息。

transaction机制就是说,发送消息前,开启事物(channel.txSelect()),而后发送消息,若是发送过程当中出现什么异常,事物就会回滚(channel.txRollback()),若是发送成功则提交事物(channel.txCommit())。

然而缺点就是吞吐量降低了。所以,按照博主的经验,生产上用confirm模式的居多。一旦channel进入confirm模式,全部在该信道上面发布的消息都将会被指派一个惟一的ID(从1开始),一旦消息被投递到全部匹配的队列以后,rabbitMQ就会发送一个Ack给生产者(包含消息的惟一ID),这就使得生产者知道消息已经正确到达目的队列了.若是rabiitMQ没能处理该消息,则会发送一个Nack消息给你,你能够进行重试操做。

2.消息队列丢数据

处理消息队列丢数据的状况,通常是开启持久化磁盘的配置。这个持久化配置能够和confirm机制配合使用,你能够在消息持久化磁盘后,再给生产者发送一个Ack信号。这样,若是消息持久化磁盘以前,rabbitMQ阵亡了,那么生产者收不到Ack信号,生产者会自动重发。

那么如何持久化呢,这里顺便说一下吧,其实也很容易,就下面两步

①、将queue的持久化标识durable设置为true,则表明是一个持久的队列

②、发送消息的时候将deliveryMode=2

这样设置之后,rabbitMQ就算挂了,重启后也能恢复数据。在消息尚未持久化到硬盘时,可能服务已经死掉,这种状况能够经过引入mirrored-queue即镜像队列,但也不能保证消息百分百不丢失(整个集群都挂掉)

3.消费者丢数据

启用手动确认模式能够解决这个问题

①自动确认模式,消费者挂掉,待ack的消息回归到队列中。消费者抛出异常,消息会不断的被重发,直处处理成功。不会丢失消息,即使服务挂掉,没有处理完成的消息会重回队列,可是异常会让消息不断重试。

②手动确认模式,若是消费者来不及处理就死掉时,没有响应ack时会重复发送一条信息给其余消费者;若是监听程序处理异常了,且未对异常进行捕获,会一直重复接收消息,而后一直抛异常;若是对异常进行了捕获,可是没有在finally里ack,也会一直重复发送消息(重试机制)。

③不确认模式,acknowledge="none" 不使用确认机制,只要消息发送完成会当即在队列移除,不管客户端异常仍是断开,只要发送完就移除,不会重发。

14. 死信队列和延迟队列的使用?

死信消息:

消息被拒绝(Basic.Reject或Basic.Nack)而且设置 requeue 参数的值为 false
消息过时了
队列达到最大的长度

过时消息:

在 rabbitmq 中存在2种方可设置消息的过时时间,第一种经过对队列进行设置,这种设置后,该队列中全部的消息都存在相同的过时时间,第二种经过对消息自己进行设置,那么每条消息的过时时间都不同。若是同时使用这2种方法,那么以过时时间小的那个数值为准。当消息达到过时时间尚未被消费,那么那个消息就成为了一个 死信 消息。

队列设置:在队列申明的时候使用 x-message-ttl 参数,单位为 毫秒

单个消息设置:是设置消息属性的 expiration 参数的值,单位为 毫秒

延时队列:在rabbitmq中不存在延时队列,可是咱们能够经过设置消息的过时时间和死信队列来模拟出延时队列。消费者监听死信交换器绑定的队列,而不要监听消息发送的队列。


有了以上的基础知识,咱们完成如下需求:

需求:用户在系统中建立一个订单,若是超过期间用户没有进行支付,那么自动取消订单。

分析:

一、上面这个状况,咱们就适合使用延时队列来实现,那么延时队列如何建立
二、延时队列能够由 过时消息+死信队列 来时间
三、过时消息经过队列中设置 x-message-ttl 参数实现
四、死信队列经过在队列申明时,给队列设置 x-dead-letter-exchange 参数,而后另外申明一个队列绑定x-dead-letter-exchange对应的交换器。

ConnectionFactory factory = new ConnectionFactory(); 
    factory.setHost("127.0.0.1"); 
    factory.setPort(AMQP.PROTOCOL.PORT); 
    factory.setUsername("guest"); 
    factory.setPassword("guest"); 
    Connection connection = factory.newConnection(); 
    Channel channel = connection.createChannel();
     
    // 声明一个接收被删除的消息的交换机和队列 
    String EXCHANGE_DEAD_NAME = "exchange.dead"; 
    String QUEUE_DEAD_NAME = "queue_dead"; 
    channel.exchangeDeclare(EXCHANGE_DEAD_NAME, BuiltinExchangeType.DIRECT); 
    channel.queueDeclare(QUEUE_DEAD_NAME, false, false, false, null); 
    channel.queueBind(QUEUE_DEAD_NAME, EXCHANGE_DEAD_NAME, "routingkey.dead"); 
     
    String EXCHANGE_NAME = "exchange.fanout"; 
    String QUEUE_NAME = "queue_name"; 
    channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT); 
    Map<String, Object> arguments = new HashMap<String, Object>(); 
    // 统一设置队列中的全部消息的过时时间 
    arguments.put("x-message-ttl", 30000); 
    // 设置超过多少毫秒没有消费者来访问队列,就删除队列的时间 
    arguments.put("x-expires", 20000); 
    // 设置队列的最新的N条消息,若是超过N条,前面的消息将从队列中移除掉 
    arguments.put("x-max-length", 4); 
    // 设置队列的内容的最大空间,超过该阈值就删除以前的消息
    arguments.put("x-max-length-bytes", 1024); 
    // 将删除的消息推送到指定的交换机,通常x-dead-letter-exchange和x-dead-letter-routing-key须要同时设置
    arguments.put("x-dead-letter-exchange", "exchange.dead"); 
    // 将删除的消息推送到指定的交换机对应的路由键 
    arguments.put("x-dead-letter-routing-key", "routingkey.dead"); 
    // 设置消息的优先级,优先级大的优先被消费 
    arguments.put("x-max-priority", 10); 
    channel.queueDeclare(QUEUE_NAME, false, false, false, arguments); 
    channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ""); 
    String message = "Hello RabbitMQ: "; 
     
    for(int i = 1; i <= 5; i++) { 
    	// expiration: 设置单条消息的过时时间 
    	AMQP.BasicProperties.Builder properties = new AMQP.BasicProperties().builder()
    			.priority(i).expiration( i * 1000 + ""); 
    	channel.basicPublish(EXCHANGE_NAME, "", properties.build(), (message + i).getBytes("UTF-8")); 
    } 
    channel.close(); 
    connection.close();

15. 使用了消息队列会有什么缺点?

1.系统可用性下降:你想啊,原本其余系统只要运行好好的,那你的系统就是正常的。如今你非要加个消息队列进去,那消息队列挂了,你的系统不是呵呵了。所以,系统可用性下降

2.系统复杂性增长:要多考虑不少方面的问题,好比一致性问题、如何保证消息不被重复消费,如何保证保证消息可靠传输。所以,须要考虑的东西更多,系统复杂性增大。

16. 消息队列的做用与使用场景

异步:批量数据异步处理(批量上传文件)
削峰:高负载任务负载均衡(电商秒杀抢购)
解耦:串行任务并行化(退货流程解耦)
广播:基于Pub/Sub实现一对多通讯

17. 多个消费者监听一个队列时,消息如何分发?

  • 轮询: 默认的策略,消费者轮流,平均地接收消息
  • 公平分发: 根据消费者的能力来分发消息,给空闲的消费者发送更多消息

//当消费者有x条消息没有响应ACK时,再也不给这个消费者发送消息

channel.basicQos(int x)

18. 没法被路由的消息去了哪里?

无设置的状况下,没法路由(Routing key错误)的消息会被直接丢弃
解决方案:
mandatory设置为true,并配合ReturnListener,实现消息的回发

声明交换机时,指定备份的交换机

Map<String,Object> arguments = new HashMap<String,Object>();
	arguments.put("alternate-exchange","备份交换机名");

19. 消息在何时会变成死信?

  • 消息拒绝而且没有设置从新入队
  • 消息过时
  • 消息堆积,而且队列达到最大长度,先入队的消息会变成DL

20. RabbitMQ如何实现延时队列?

利用TTL(队列的消息存活时间或者消息存活时间),加上死信交换机

// 设置属性,消息10秒钟过时
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
		.expiration("10000") // TTL

 // 指定队列的死信交换机
Map<String,Object> arguments = new HashMap<String,Object>();
arguments.put("x-dead-letter-exchange","DLX_EXCHANGE");

21. 如何保证消息的可靠性投递

发送方确认模式:
将信道设置成confirm模式(发送方确认模式),则全部在信道上发布的消息都会被指派一个惟一的ID。
一旦消息被投递到目的队列后,或者消息被写入磁盘后(可持久化的消息),信道会发送一个确认给生产者(包含消息惟一ID)。
若是RabbitMQ发生内部错误从而致使消息丢失,会发送一条nack(not acknowledged,未确认)消息。
发送方确认模式是异步的,生产者应用程序在等待确认的同时,能够继续发送消息。当确认消息到达生产者应用程序,生产者应用程序的回调方法就会被触发来处理确认消息。

接收方确认机制
接收方消息确认机制:消费者接收每一条消息后都必须进行确认(消息接收和消息确认是两个不一样操做)。只有消费者确认了消息,RabbitMQ才能安全地把消息从队列中删除。
这里并无用到超时机制,RabbitMQ仅经过Consumer的链接中断来确认是否须要从新发送消息。也就是说,只要链接不中断,RabbitMQ给了Consumer足够长的时间来处理消息。保证数据的最终一致性;
下面罗列几种特殊状况:
若是消费者接收到消息,在确认以前断开了链接或取消订阅,RabbitMQ会认为消息没有被分发,而后从新分发给下一个订阅的消费者。(可能存在消息重复消费的隐患,须要去重)
若是消费者接收到消息却没有确认消息,链接也未断开,则RabbitMQ认为该消费者繁忙,将不会给该消费者分发更多的消息。

22. 消息幂等性

生产者方面:能够对每条消息生成一个msgID,以控制消息重复投递

AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
	porperties.messageId(String.valueOF(UUID.randomUUID()))

消费者方面:消息体中必须携带一个业务ID,如银行流水号,消费者能够根据业务ID去重,避免重复消费

23. 消息如何被优先消费

//生产者
 Map<String, Object> argss = new HashMap<String, Object>();
        argss.put("x-max-priority",10);

//消费者
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
                .priority(5) // 优先级,默认为5,配合队列的 x-max-priority 属性使用

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

一个队列只有一个消费者的状况下才能保证顺序,不然只能经过全局ID实现(每条消息都一个msgId,关联的消息拥有一个parentMsgId。能够在消费端实现前一条消息未消费,不处理下一条消息;也能够在生产端实现前一条消息未处理完毕,不发布下一条消息)

25. RabbitMQ的集群模式和集群节点类型

普通模式:默认模式,以两个节点(rabbit01,rabbit02)为例来进行说明,对于Queue来讲,消息实体只存在于其中一个节点rabbit01(或者rabbit02),rabbit01和rabbit02两个节点仅有相同的元数据,即队列结构。当消息进入rabbit01节点的Queue后,consumer从rabbit02节点消费时,RabbitMQ会临时在rabbit01,rabbit02间进行消息传输,把A中的消息实体取出并通过B发送给consumer,因此consumer应尽可能链接每个节点,从中取消息。即对于同一个逻辑队列,要在多个节点创建物理Queue。不然不管consumer连rabbit01或rabbit02,出口总在rabbit01,会产生瓶颈。当rabbit01节点故障后,rabbit02节点没法取到rabbit01节点中还未消费的消息实体。若是作了消息持久化,那么等到rabbit01节点恢复,而后才可被消费。若是没有消息持久化,就会产生消息丢失的现象。

镜像模式:把须要的队列作成镜像队列,存在与多个节点属于RabibitMQ的HA方案,该模式解决了普通模式中的问题,其实质和普通模式不一样之处在于,消息体会主动在镜像节点间同步,而不是在客户端取数据时临时拉取,该模式带来的反作用也很明显,除了下降系统性能外,若是镜像队列数量过多,加之大量的消息进入,集群内部的网络带宽将会被这种同步通信大大消耗掉,因此在对可靠性要求比较高的场合中适用

节点分为内存节点(保存状态到内存,但持久化的队列和消息仍是会保存到磁盘),磁盘节点(保存状态到内存和磁盘),一个集群中至少须要一个磁盘节点

26.如何自动删除长时间没有消费的消息

// 经过队列属性设置消息过时时间
        Map<String, Object> argss = new HashMap<String, Object>();
        argss.put("x-message-ttl",6000);

 // 对每条消息设置过时时间
        AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
                .expiration("10000") // TTL

27.消息基于什么传输

RabbitMQ使用信道的方式来传输数据。信道是创建在真实的TCP链接内的虚拟链接,且每条TCP链接上的信道数量没有限制

28.如何确保消息不丢失

消息持久化,固然前提是队列必须持久化
RabbitMQ确保持久性消息能从服务器重启中恢复的方式是,将它们写入磁盘上的一个持久化日志文件,当发布一条持久性消息到持久交换器上时,Rabbit会在消息提交到日志文件后才发送响应。
一旦消费者从持久队列中消费了一条持久化消息,RabbitMQ会在持久化日志中把这条消息标记为等待垃圾收集。若是持久化消息在被消费以前RabbitMQ重启,那么Rabbit会自动重建交换器和队列(以及绑定),并从新发布持久化日志文件中的消息到合适的队列。

RabbitMQ思惟导图

RabbitMQ知识集锦

更多思惟导图,关注公众号下载~

推荐

Redis面试题集锦(精选)

Spring面试题集锦(精选)

SpringMVC面试题集锦(精选)

ProcessOn是一个在线做图工具的聚合平台~

文末

欢迎关注我的微信公众号:Coder编程
欢迎关注Coder编程公众号,主要分享数据结构与算法、Java相关知识体系、框架知识及原理、Spring全家桶、微服务项目实战、DevOps实践之路、每日一篇互联网大厂面试或笔试题以及PMP项目管理知识等。更多精彩内容正在路上~

文章收录至
Github: https://github.com/CoderMerlin/coder-programming
Gitee: https://gitee.com/573059382/coder-programming
欢迎关注并star~
微信公众号

相关文章
相关标签/搜索