2021最新 RabbitMQ面试题精选(附刷题小程序)

推荐使用小程序阅读

为了能让您更加方便的阅读
本文所有的面试题目均已整理至小程序《面试手册
可以通过微信扫描(或长按)下图的二维码享受更好的阅读体验!


最近梳理汇总了Java面试常遇到的面试题;并将其开发成小程序《面试手册》,方便大家阅读,可微信扫描文章开头的二维码使用;包含了Java基础并发JVM数据库SpringSpringMVCSpringBootSpringCloud设计模式MQLinuxDocker等多个类型,资料持续更新中;侵权联删。

本人不才,如出现错误或者不准确的地方,望各位大神指正。

分类地址
2021最新 Java基础面试题精选(附刷题小程序)https://lupengfei.blog.csdn.net/article/details/113404618
2021最新 Java并发编程面试题精选(附刷题小程序)https://lupengfei.blog.csdn.net/article/details/113404925
2021最新 Java虚拟机(JVM)精选(附刷题小程序)https://lupengfei.blog.csdn.net/article/details/113404971
2021最新 Spring面试题精选(附刷题小程序)https://lupengfei.blog.csdn.net/article/details/113417060
2021最新 SpringMVC面试题精选(附刷题小程序)https://lupengfei.blog.csdn.net/article/details/113417329
2021最新 SpringBoot面试题精选(附刷题小程序)https://lupengfei.blog.csdn.net/article/details/113417545
2021最新 Dubbo面试题精选(附刷题小程序)https://lupengfei.blog.csdn.net/article/details/114525995
2021最新 SpringCloud面试题精选(附刷题小程序)https://lupengfei.blog.csdn.net/article/details/113428174
2021最新 Redis常见面试题精选(附刷题小程序)https://lupengfei.blog.csdn.net/article/details/113416372
2021最新 MySQL常见面试题精选(附刷题小程序)https://lupengfei.blog.csdn.net/article/details/113405031
2021最新 MyBatis面试题精选(附刷题小程序)https://lupengfei.blog.csdn.net/article/details/114523271
2021最新 RabbitMQ面试题精选(附刷题小程序)https://lupengfei.blog.csdn.net/article/details/114525479
2021最新 Kafka面试题精选(附刷题小程序)https://lupengfei.blog.csdn.net/article/details/114001162
2021最新 RocketMQ面试题精选(附刷题小程序)https://lupengfei.blog.csdn.net/article/details/114525762
持续更新请关注👉👉👉https://blog.lupf.cn/category/ms

文章目录


1. 消息队列基础

为什么使用MQ?优点?常用场景?

  • 简答

    • 异步处理 - 相比于传统的串行、并行方式,提高了系统吞吐量。
    • 应用解耦 - 系统间通过消息通信,不用关心其他系统的处理。
    • 流量削锋 - 可以通过消息队列长度控制请求量;可以缓解短时间内的高并发请求。
    • 日志处理 - 解决大量日志传输。
    • 消息通讯 - 消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等。
  • 详答

    主要是:解耦异步削峰

    • 解耦
      A 系统发送数据到 BCD 三个系统,通过接口调用发送。如果 E 系统也要这个数据呢?那如果 C 系统现在不需要了呢?A 系统负责人几乎崩溃…A 系统跟其它各种乱七八糟的系统严重耦合,A 系统产生一条比较关键的数据,很多系统都需要 A 系统将这个数据发送过来。如果使用 MQ,A 系统产生一条数据,发送到 MQ 里面去,哪个系统需要数据自己去 MQ 里面消费。如果新系统需要数据,直接从 MQ 里消费即可;如果某个系统不需要这条数据了,就取消对 MQ 消息的消费即可。这样下来,A 系统压根儿不需要去考虑要给谁发送数据,不需要维护这个代码,也不需要考虑人家是否调用成功、失败超时等情况。

      就是一个系统或者一个模块,调用了多个系统或者模块,互相之间的调用很复杂,维护起来很麻烦。但是其实这个调用是不需要直接同步调用接口的,如果用 MQ 给它异步化解耦。

    • 异步
      A 系统接收一个请求,需要在自己本地写库,还需要在 BCD 三个系统写库,自己本地写库要 3ms,BCD 三个系统分别写库要 300ms、450ms、200ms。最终请求总延时是 3 + 300 + 450 + 200 = 953ms,接近 1s,用户感觉搞个什么东西,慢死了慢死了。用户通过浏览器发起请求。如果使用 MQ,那么 A 系统连续发送 3 条消息到 MQ 队列中,假如耗时 5ms,A 系统从接受一个请求到返回响应给用户,总时长是 3 + 5 = 8ms。

    • 削峰
      减少高峰时期对服务器压力。

使用MQ有什么缺点?

  • 系统可用性降低
    系统引入的外部依赖越多,越容易挂掉。本来你就是 A 系统调用 B、C、D三个系统的接口就好了,人 A、B、C、D四个系统好好的,没啥问题,你偏加个MQ进来,万一MQ挂了咋整,MQ一挂,整套系统崩溃的,你不就完了?如何保证消息队列的高可用,可以点击这里查看。

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

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

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

Kafka、ActiveMQ、RabbitMQ、RocketMQ 的区别?

ActiveMQRabbitMQRocketMQKafkaZeroMQ
单机吞吐量比RabbitMQ低2.6w/s(消息做持久化)11.6w/s17.3w/s29w/s
开发语言JavaErlangJavaScala/JavaC
主要维护者ApacheMozilla/SpringAlibabaApacheiMatix,创始人已去世
成熟度成熟成熟开源版本不够成熟比较成熟只有C、PHP等版本成熟
订阅形式点对点(p2p)、广播(发布-订阅)提供了4种:direct, topic ,Headers和fanout。fanout就是广播模式基于topic/messageTag以及按照消息类型、属性进行正则匹配的发布订阅模式基于topic以及按照topic进行正则匹配的发布订阅模式点对点(p2p)
持久化支持少量堆积支持少量堆积支持大量堆积支持大量堆积不支持
顺序消息不支持不支持支持支持不支持
性能稳定性一般较差很好
集群方式支持简单集群模式,比如’主-备’,对高级集群模式支持不好。支持简单集群,'复制’模式,对高级集群模式支持不好。常用 多对’Master-Slave’ 模式,开源版本需手动切换Slave变成Master天然的‘Leader-Slave’无状态集群,每台服务器既是Master也是Slave不支持
管理界面一般较好一般

综上,各种对比之后,有如下建议:

一般的业务系统要引入 MQ,最早大家都用 ActiveMQ,但是现在确实大家用的不多了,没经过大规模吞吐量场景的验证,社区也不是很活跃,所以大家还是算了吧,我个人不推荐用这个了;

后来大家开始用 RabbitMQ,但是确实 erlang 语言阻止了大量的 Java 工程师去深入研究和掌控它,对公司而言,几乎处于不可控的状态,但是确实人家是开源的,比较稳定的支持,活跃度也高;

不过现在确实越来越多的公司会去用 RocketMQ,确实很不错,毕竟是阿里出品,但社区可能有突然黄掉的风险(目前 RocketMQ 已捐给 Apache,但 GitHub 上的活跃度其实不算高)对自己公司技术实力有绝对自信的,推荐用 RocketMQ,否则回去老老实实用 RabbitMQ 吧,人家有活跃的开源社区,绝对不会黄。

所以中小型公司,技术实力较为一般,技术挑战不是特别高,用 RabbitMQ 是不错的选择;大型公司,基础架构研发实力较强,用 RocketMQ 是很好的选择。

如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没问题,社区活跃度很高,绝对不会黄,何况几乎是全世界这个领域的事实性规范。

消息队列如何选型?

  1. 如果消息队列不是将要构建系统的重点,对消息队列功能和性能没有很高的要求,只需要一个快速上手易于维护的消息队列,建议使用RabbitMQ。

  2. 如果系统使用消息队列主要场景是处理在线业务,比如在交易系统中用消息队列传递订单,需要低延迟和高稳定性,建议使用RocketMQ。

  3. 如果需要处理海量的消息,像收集日志、监控信息或是埋点这类数据,或是你的应用场景大量使用了大数据、流计算相关的开源产品,那Kafka是最适合的消息队列。

MQ 有哪些常见问题?如何解决这些问题?

MQ 的常见问题有:

  • 消息的顺序问题

    消息有序指的是可以按照消息的发送顺序来消费。

    假如生产者产生了 2 条消息:M1、M2,假定 M1 发送到 S1,M2 发送到 S2,如果要保证 M1 先于 M2 被消费,怎么做?

    img

    解决方案:

    • 保证生产者 - MQServer - 消费者是一对一对一的关系

      img

      缺陷:

      • 并行度就会成为消息系统的瓶颈(吞吐量不够)
      • 更多的异常处理,比如:只要消费端出现问题,就会导致整个处理流程阻塞,我们不得不花费更多的精力来解决阻塞的问题。 (2)通过合理的设计或者将问题分解来规避。
      • 不关注乱序的应用实际大量存在
      • 队列无序并不意味着消息无序 所以从业务层面来保证消息的顺序而不仅仅是依赖于消息系统,是一种更合理的方式。
  • 消息的重复问题

    造成消息重复的根本原因是:网络不可达。

    所以解决这个问题的办法就是绕过这个问题。那么问题就变成了:如果消费端收到两条一样的消息,应该怎样处理?

    消费端处理消息的业务逻辑保持幂等性。只要保持幂等性,不管来多少条重复消息,最后处理的结果都一样。保证每条消息都有唯一编号且保证消息处理成功与去重表的日志同时出现。利用一张日志表来记录已经处理成功的消息的 ID,如果新到的消息 ID 已经在日志表中,那么就不再处理这条消息。

依托消息中间件如何实现异步?

引入了MQ后,用来的依赖关系转移了,从系统之间的依赖,变成系统都依赖MQ

A调用B,只需要向MQ发送一条消息,A就认为自己的工作完成了。不用像之前调用B一样等着B处理一堆的业务逻辑和数据库操作。

B会从MQ中读取A发送的特定消息,完成自己该做的事情。

这样就实现了A异步调用B

2. RabbitMQ精选面试题

RabbitMQ是什么?

RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。RabbitMQ服务器是用Erlang语言编写的,而群集和故障转移是构建在开放电信平台框架上的。所有主要的编程语言均有与代理接口通讯的客户端库。

使用RabbitMQ有什么好处?

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

RabbitMQ的特点?

  • 可靠性: RabbitMQ使用一些机制来保证可靠性, 如持久化、传输确认及发布确认等。

  • 灵活的路由 : 在消息进入队列之前,通过交换器来路由消息。对于典型的路由功能, RabbitMQ 己经提供了一些内置的交换器来实现。针对更复杂的路由功能,可以将多个 交换器绑定在一起, 也可以通过插件机制来实现自己的交换器。

  • 扩展性: 多个RabbitMQ节点可以组成一个集群,也可以根据实际业务情况动态地扩展 集群中节点。

  • 高可用性 : 队列可以在集群中的机器上设置镜像,使得在部分节点出现问题的情况下队 列仍然可用。

  • 多种协议: RabbitMQ除了原生支持AMQP协议,还支持STOMP, MQTT等多种消息 中间件协议。

  • 多语言客户端 :RabbitMQ 几乎支持所有常用语言,比如 Java、 Python、 Ruby、 PHP、 C#、 JavaScript 等。

  • 管理界面 : RabbitMQ 提供了一个易用的用户界面,使得用户可以监控和管理消息、集 群中的节点等。

  • 令插件机制: RabbitMQ 提供了许多插件 , 以实现从多方面进行扩展,当然也可以编写自 己的插件

AMQP是什么?

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

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

AMQP协议3层?

  • Module Layer
    协议最高层,主要定义了一些客户端调用的命令,客户端可以用这些命令实现自己的业务逻辑。

  • Session Layer
    中间层,主要负责客户端命令发送给服务器,再将服务端应答返回客户端,提供可靠性同步机制和错误处理。

  • TransportLayer
    最底层,主要传输二进制数据流,提供帧的处理、信道服用、错误检测和数据表示等。

AMQP模型的几大组件?

  • 交换器 (Exchange):消息代理服务器中用于把消息路由到队列的组件。
  • 队列 (Queue):用来存储消息的数据结构,位于硬盘或内存中。
  • 绑定 (Binding): 一套规则,告知交换器消息应该将消息投递给哪个队列。

RabbitMQ有那些基本概念?

  • Broker: 简单来说就是消息队列服务器实体
  • Exchange: 消息交换机,它指定消息按什么规则,路由到哪个队列
  • Queue: 消息队列载体,每个消息都会被投入到一个或多个队列
  • Binding: 绑定,它的作用就是把exchange和queue按照路由规则绑定起来
  • Routing Key: 路由关键字,exchange根据这个关键字进行消息投递
  • VHost: vhost 可以理解为虚拟 broker ,即 mini-RabbitMQ server。其内部均含有独立的 queue、exchange 和 binding 等,但最最重要的是,其拥有独立的权限系统,可以做到 vhost 范围的用户控制。当然,从 RabbitMQ 的全局角度,vhost 可以作为不同权限隔离的手段(一个典型的例子就是不同的应用可以跑在不同的 vhost 中)。
  • Producer: 消息生产者,就是投递消息的程序
  • Consumer: 消息消费者,就是接受消息的程序
  • Channel: 消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务

ExchangeQueueRoutingKey三个才能决定一个从Exchange到Queue的唯一的线路。

什么是生产者Producer?

消息生产者,就是投递消息的一方。

消息一般包含两个部分:消息体(payload)和标签(Label)。

什么是消费者Consumer?

消费消息,也就是接收消息的一方。

消费者连接到RabbitMQ服务器,并订阅到队列上。消费消息时只消费消息体,丢弃标签。

什么是Broker服务节点?

Broker可以看做RabbitMQ的服务节点。一般请下一个Broker可以看做一个RabbitMQ服务器。

什么是Queue队列?

Queue 是 RabbitMQ 的内部对象,用于存储消息。多个消费者可以订阅同一队列,这时队列中的消息会被平摊(轮询)给多个消费者进行处理。

什么是Exchange交换器?

Exchange:生产者将消息发送到交换器,有交换器将消息路由到一个或者多个队列中。当路由不到时,或返回给生产者或直接丢弃。

什么是RoutingKey路由键?

生产者将消息发送给交换器的时候,会指定一个RoutingKey,用来指定这个消息的路由规则,这个RoutingKey需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效。

什么是Binding绑定?

通过绑定将交换器和队列关联起来,一般会指定一个BindingKey,这样RabbitMq就知道如何正确路由消息到队列了。

RabbitMQ 中的 Broker 是指什么?Cluster 又是指什么?

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

vhost 是什么? 起什么作用?

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

RabbitMQ的工作模式有几种?

一. simple模式(即最简单的收发模式)

img

  1. 消息产生消息,将消息放入队列

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

二. work工作模式(资源的竞争)

img

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

三. publish/subscribe发布订阅(共享资源)
img

  1. 每个消费者监听自己的队列;
  2. 生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息。

四. routing路由模式
img

  1. 消息生产者将消息发送给交换机按照路由判断,路由是字符串(info) 当前产生的消息携带路由字符(对象的方法),交换机根据路由的key,只能匹配上路由key对应的消息队列,对应的消费者才能消费消息;

  2. 根据业务功能定义路由字符串

  3. 从系统的代码逻辑中获取对应的功能字符串,将消息任务扔到对应的队列中。

  4. 业务场景:error 通知;EXCEPTION;错误通知的功能;传统意义的错误通知;客户通知;利用key路由,可以将程序中的错误封装成消息传入到消息队列中,开发者可以自定义消费者,实时接收错误;

五. topic 主题模式(路由模式的一种)

img

  1. 星号井号代表通配符

  2. 星号代表多个单词,井号代表一个单词

  3. 路由功能添加模糊匹配

  4. 消息产生者产生消息,把消息交给交换机

  5. 交换机根据key的规则模糊匹配到对应的队列,由队列的监听消费者接收消息消费(在我的理解看来就是routing查询的一种模糊匹配,就类似sql的模糊查询方式)

消息基于什么传输?

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

RabbitMQ中消息可能有的几种状态?

  • alpha: 消息内容(包括消息体、属性和 headers) 和消息索引都存储在内存中 。

  • beta: 消息内容保存在磁盘中,消息索引保存在内存中。

  • gamma: 消息内容保存在磁盘中,消息索引在磁盘和内存中都有 。

  • delta: 消息内容和索引都在磁盘中 。

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

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

生产者消息如何运转?

  1. Producer先连接到Broker,建立连接Connection,开启一个信道(Channel)。

  2. Producer声明一个交换器并设置好相关属性。

  3. Producer声明一个队列并设置好相关属性。

  4. Producer通过路由键将交换器和队列绑定起来。

  5. Producer发送消息到Broker,其中包含路由键、交换器等信息。

  6. 相应的交换器根据接收到的路由键查找匹配的队列。

  7. 如果找到,将消息存入对应的队列,如果没有找到,会根据生产者的配置丢弃或者退回给生产者。

  8. 关闭信道。

  9. 管理连接。

RabbitMQ队列结构?

通常由以下两部分组成:

  • rabbit_amqqueue_process
    负责协议相关的消息处理,即接收生产者发布的消息、向消费者交付消息、处理消息的确认(包括生产端的 confirm 和消费端的 ack) 等。

  • backing_queue
    是消息存储的具体形式和引擎,并向 rabbit amqqueue process 提供相关的接口以供调用。

消费者获取消息的方式?

消息如何分发?

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

消息怎么路由?

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

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

常用的交换器主要分为一下三种:

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

消息传输保证层级?

  • At most once:
    最多一次。消息可能会丢失,单不会重复传输。

  • At least once:
    最少一次。消息觉不会丢失,但可能会重复传输。

  • Exactly once:
    恰好一次,每条消息肯定仅传输一次。

消费者接收消息过程?

  1. Producer 先连接到 Broker,建立连接 Connection,开启一个信道(Channel)。

  2. 向 Broker 请求消费响应的队列中消息,可能会设置响应的回调函数。

  3. 等待 Broker 回应并投递相应队列中的消息,接收消息。

  4. 消费者确认收到的消息,ack。

  5. RabbitMq 从队列中删除已经确定的消息。

  6. 关闭信道。

  7. 关闭连接

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

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

下面罗列几种特殊情况:

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

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

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

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

  1. 拿到这个消息做数据库的insert操作。然后给这个消息做一个唯一主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据。

  2. 拿到这个消息做redis的set的操作,因为你无论set几次结果都是一样的,set操作本来就算幂等操作。

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

消费者某些原因无法处理当前接受的消息如何来拒绝?

  • channel.basicNack
  • channel.basicReject

如何解决RabbitMQ丢数据的问题?

  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” 不使用确认机制,只要消息发送完成会立即在队列移除,无论客户端异常还是断开,只要发送完就移除,不会重发。

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

  • 发送方确认模式
    将信道设置成confirm模式(发送方确认模式),则所有在信道上发布的消息都会被指派一个唯一的ID。

    一旦消息被投递到目的队列后,或者消息被写入磁盘后(可持久化的消息),信道会发送一个确认给生产者(包含消息唯一ID)。
    如果RabbitMQ发生内部错误从而导致消息丢失,会发送一条nack(not acknowledged,未确认)消息。

    发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消息。当确认消息到达生产者应用程序,生产者应用程序的回调方法就会被触发来处理确认消息。

  • 接收方确认机制
    消费者接收每一条消息后都必须进行确认(消息接收和消息确认是两个不同操作)。只有消费者确认了消息,RabbitMQ才能安全地把消息从队列中删除。

    这里并没有用到超时机制,RabbitMQ仅通过Consumer的连接中断来确认是否需要重新发送消息。也就是说,只要连接不中断,RabbitMQ给了Consumer足够长的时间来处理消息。保证数据的最终一致性;
    下面罗列几种特殊情况:

    • 如果消费者接收到消息,在确认之前断开了连接或取消订阅,RabbitMQ会认为消息没有被分发,然后重新分发给下一个订阅的消费者。(可能存在消息重复消费的隐患,需要去重)
    • 如果消费者接收到消息却没有确认消息,连接也未断开,则RabbitMQ认为该消费者繁忙,将不会给该消费者分发更多的消息。

消息如何保证幂等性?

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

    AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
    porperties.messageId(String.valueOF(UUID.randomUUID()))
    
  • 消费者方面
    消息体中必须携带一个业务ID,如银行流水号,消费者可以根据业务ID去重,避免重复消费

消息如何被优先消费?

  • 生产者

    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 属性使用
    

如何保证消息的顺序性?

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

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

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

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

channel.basicQos(int x)

无法被路由的消息去了哪里?

mandatory:true 返回消息给生产者。

mandatory:false 直接丢弃。

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

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

  • 过期消息:
    在 rabbitmq 中存在2种方可设置消息的过期时间,第一种通过对队列进行设置,这种设置后,该队列中所有的消息都存在相同的过期时间,第二种通过对消息本身进行设置,那么每条消息的过期时间都不一样。如果同时使用这2种方法,那么以过期时间小的那个数值为准。当消息达到过期时间还没有被消费,那么那个消息就成为了一个 死信 消息。

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

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

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


场景演示:
需求:用户在系统中创建一个订单,如果超过时间用户没有进行支付,那么自动取消订单。

分析:

  1. 上面这个情况,我们就适合使用延时队列来实现,那么延时队列如何创建
  2. 延时队列可以由 过期消息+死信队列 来时间
  3. 过期消息通过队列中设置 x-message-ttl 参数实现
  4. 死信队列通过在队列申明时,给队列设置 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();

消息在什么时候会变成死信?

  1. 消息拒绝并且没有设置重新入队
  2. 消息过期
  3. 消息堆积,并且队列达到最大长度,先入队的消息会变成DL

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");

RabbitMQ事务机制?

RabbitMQ 客户端中与事务机制相关的方法有三个:

  • channel.txSelect
    用于将当前的信道设置成事务模式。

  • channel.txCommit
    用于提交事务 。

  • channel.txRollback
    用于事务回滚,如果在事务提交执行之前由于 RabbitMQ 异常崩溃或者其他原因抛出异常,通过txRollback来回滚。

RabbitMQ的集群模式有几种?

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

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

  • 普通模式:
    以两个节点(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方案,该模式解决了普通模式中的问题,其实质和普通模式不同之处在于,消息体会主动在镜像节点间同步,而不是在客户端取数据时临时拉取,该模式带来的副作用也很明显,除了降低系统性能外,如果镜像队列数量过多,加之大量的消息进入,集群内部的网络带宽将会被这种同步通讯大大消耗掉,所以在对可靠性要求比较高的场合中适用

集群节点类型有几种?

  • 内存节点
    保存状态到内存,但持久化的队列和消息还是会保存到磁盘;
  • 磁盘节点
    保存状态到内存和磁盘,一个集群中至少需要一个磁盘节点

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

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

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

如何确保消息不丢失?

消息持久化,当然前提是队列必须持久化

RabbitMQ 确保持久性消息能从服务器重启中恢复的方式是,将它们写入磁盘上的一个持久化日志文件,当发布一条持久性消息到持久交换器上时,RabbitMQ 会在消息提交到日志文件后才发送响应。

一旦消费者从持久队列中消费了一条持久化消息,RabbitMQ 会在持久化日志中把这条消息标记为等待垃圾收集。如果持久化消息在被消费之前 RabbitMQ 重启,那么 RabbitMQ 会自动重建交换器和队列(以及绑定),并重新发布持久化日志文件中的消息到合适的队列。


微信扫码关注公众号,回复【资料】获取更多粉丝福利、学习、面试资料。

相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页