共计 10211 个字符,预计需要花费 26 分钟才能阅读完成。
最近刚学完 RabbitMQ,顺便整顿了一下罕用的面试题,用于总结跟回顾,也供各位大佬参考,如有不对的中央,欢送指出哈!
1、为什么要应用 MQ
1、流量消峰
举个例子:如果订单零碎最多能解决一万次订单,这个解决能力应酬失常时段的下单时入不敷出,失常时段咱们下繁多秒后就能返回后果。然而在高峰期,如果有两万次下单操作系统是解决不了的,只能限度订单超过一万后不容许用户下单。应用音讯队列做缓冲,咱们能够勾销这个限度,把一秒内下的订单扩散成一段时间来解决,这时有些用户可能在下单十几秒后能力收到下单胜利的操作,然而比不能下单的体验要好。
2、利用解耦
以电商利用为例,利用中有订单零碎、库存零碎、物流零碎、领取零碎。用户创立订单后,如果耦合调用库存零碎、物流零碎、领取零碎,任何一个子系统出了故障,都会造成下单操作异样。当转变成基于音讯队列的形式后,零碎间调用的问题会缩小很多,比方物流零碎因为产生故障,须要几分钟来修复。在这几分钟的工夫里,物流零碎要解决的内存被缓存在音讯队列中,用户的下单操作能够失常实现。当物流零碎复原后,持续解决订单信息即可,中单用户感触不到物流零碎的故障,晋升零碎的可用性。
3、异步解决
有些服务间调用是异步的,例如 A 调用 B,B 须要破费很长时间执行,然而 A 须要晓得 B 什么时候能够执行完,以前个别有两种形式:
- A 过一段时间去调用 B 的查问 api 查问
- A 提供一个 callback api,B 执行完之后调用 api 告诉 A 服务。
这两种形式都不是很优雅,应用音讯总线,能够很不便解决这个问题,A 调用 B 服务后,只须要监听 B 解决实现的音讯,当 B 解决实现后,会发送一条音讯给 MQ,MQ 会将此音讯转发给 A 服务。这样 A 服务既不必循环调用 B 的查问 api,也不必提供 callback api。同样 B 服务也不必做这些操作。A 服务还能及时的失去异步解决胜利的音讯。
2、什么是RabbitMQ
RabbitMQ
是一个消息中间件:它承受并转发音讯。你能够把它当做一个快递站点,当你要发送一个包裹时,你把你的包裹放到快递站,快递员最终会把你的快递送到收件人那里,依照这种逻辑RabbitMQ
是一个快递站,一个快递员帮你传递快件。RabbitMQ
与快递站的次要区别在于,它不解决快件而是接管,存储和转发音讯数据。
3、RabbitMQ
各组件的性能
- Server:接管客户端的连贯,实现
AMQP
实体服务。 - Connection:连贯,应用程序与 Server 的网络连接,TCP 连贯。
-
Channel:信道,音讯读写等操作在信道中进行。客户端能够建设多个信道,每个信道代表一个会话工作。如果每一次拜访 RabbitMQ 都建设一个 Connection,在音讯量大的时候建设 TCP Connection 的开销将是微小的,效率也较低。Channel 是在 connection 外部建设的逻辑连贯,如果应用程序反对多线程,通常每个 thread 创立独自的 channel 进行通信,AMQP method 蕴含了 channel id 帮忙客户端和 message broker 辨认 channel,所以 channel 之间是齐全隔离的。Channel 作为轻量级的
Connection 极大缩小了操作系统建设 TCP connection 的开销
- Message:音讯,应用程序和服务器之间传送的数据,音讯能够非常简单,也能够很简单。由 Properties 和 Body 组成。Properties 为外包装,能够对音讯进行润饰,比方音讯的优先级、提早等高级个性;Body 就是音讯体内容。
- Virtual Host:虚拟主机,用于逻辑隔离。一个虚拟主机外面能够有若干个 Exchange 和 Queue,同一个虚拟主机外面不能有雷同名称的 Exchange 或 Queue。
- Exchange:交换器,接管音讯,依照路由规定将音讯路由到一个或者多个队列。如果路由不到,或者返回给生产者,或者间接抛弃。
RabbitMQ
罕用的交换器罕用类型有 direct、topic、fanout、headers 四种,前面具体介绍。 - Binding:绑定,交换器和音讯队列之间的虚构连贯,绑定中能够蕴含一个或者多个
RoutingKey
,Binding 信息被保留到 exchange 中的查问表中,用于 message 的散发根据 RoutingKey
:路由键,生产者将音讯发送给交换器的时候,会发送一个RoutingKey
,用来指定路由规定,这样交换器就晓得把音讯发送到哪个队列。路由键通常为一个“.”宰割的字符串,例如“com.rabbitmq”
。- Queue:音讯队列,用来保留音讯,供消费者生产。
3、RabbitMQ 工作原理
不得不看一下经典的图了,如下
AMQP 协定模型由三局部组成:生产者、消费者和服务端,执行流程如下:
- 生产者是连贯到 Server,建设一个连贯,开启一个信道。
- 生产者申明交换器和队列,设置相干属性,并通过路由键将交换器和队列进行绑定。
- 消费者也须要进行建设连贯,开启信道等操作,便于接管音讯。
- 生产者发送音讯,发送到服务端中的虚拟主机。
- 虚拟主机中的交换器依据路由键抉择路由规定,发送到不同的音讯队列中。
- 订阅了音讯队列的消费者就能够获取到音讯,进行生产。
4、RabbitMQ 上的一个 queue 中寄存的 message 是否有数量限度?
能够认为是无限度,因为限度取决于机器的内存,然而音讯过多会导致解决效率的降落。
5、RabbitMQ 容许发送的 message 最大可达多大?
依据 AMQP 协定规定,音讯体的大小由 64-bit 的值来指定,所以你就能够晓得到底能发多大的数据了
6、RabbitMQ 的工作模式
1、simple 模式(即最简略的收发模式)
- 音讯产生音讯,将音讯放入队列
- 音讯的消费者(consumer) 监听 音讯队列,如果队列中有音讯,就生产掉,音讯被拿走后,主动从队列中删除(隐患 音讯可能没有被消费者正确处理,曾经从队列中隐没了,造成音讯的失落,这里能够设置成手动的 ack,但如果设置成手动 ack,解决完后要及时发送 ack 音讯给队列,否则会造成内存溢出)。
2、Work Queues(工作队列)
音讯产生者将音讯放入队列消费者能够有多个, 消费者 1, 消费者 2 同时监听同一个队列, 音讯被生产。C1 C2 独特争抢以后的音讯队列内容, 谁先拿到谁负责生产音讯(隐患:高并发状况下, 默认会产生某一个音讯被多个消费者独特应用, 能够设置一个开关(syncronize) 保障一条音讯只能被一个消费者应用)。
3、publish/subscribe 公布订阅(共享资源)
- 每个消费者监听本人的队列;
- 生产者将音讯发给 broker,由交换机将音讯转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接管到音讯。
4、routing 路由模式
- 音讯生产者将音讯发送给交换机依照路由判断,路由是字符串(info) 以后产生的音讯携带路由字符(对象的办法),交换机依据路由的 key,只能匹配上路由 key 对应的音讯队列,对应的消费者能力生产音讯;
- 依据业务性能定义路由字符串
- 从零碎的代码逻辑中获取对应的性能字符串, 将音讯工作扔到对应的队列中。
- 业务场景:error 告诉;EXCEPTION; 谬误告诉的性能; 传统意义的谬误告诉; 客户告诉; 利用 key 路由, 能够将程序中的谬误封装成音讯传入到音讯队列中, 开发者能够自定义消费者, 实时接管谬误;
5、topic 主题模式(路由模式的一种)
- 星号井号代表通配符
- 星号代表多个单词, 井号代表一个单词
- 路由性能增加含糊匹配
- 音讯产生者产生音讯, 把音讯交给交换机
- 交换机依据 key 的规定含糊匹配到对应的队列, 由队列的监听消费者接管音讯生产
PS:(在我的了解看来就是 routing 查问的一种含糊匹配,就相似 sql 的含糊查问形式)
7、如何保障 RabbitMQ 音讯的程序性?
- 拆分多个 queue(音讯队列),每个 queue(音讯队列) 一个 consumer(消费者),就是多一些 queue(音讯队列)而已,的确是麻烦点;
- 或者就一个 queue (音讯队列)然而对应一个 consumer(消费者),而后这个 consumer(消费者)外部用内存队列做排队,而后分发给底层不同的 worker 来解决。
8、RabbitMQ 音讯失落的状况有哪些?
- 生产者发送音讯 RabbitMQ Server 音讯失落
- RabbitMQ Server 中存储的音讯失落
- RabbitMQ Server 中存储的音讯分发给消费者者失落
1、生产者发送音讯 RabbitMQ Server 音讯失落
- 发送过程中存在网络问题,导致音讯没有发送胜利
- 代码问题,导致音讯没发送
2、RabbitMQ Server 中存储的音讯失落
- 音讯没有长久化,服务器重启导致存储的音讯失落
3、RabbitMQ Server 到消费者音讯失落
- 生产端接管到相干音讯之后,生产端还没来得及解决音讯,生产端机器就宕机了
- 解决音讯存在异样
9、RabbitMQ 如何保障音讯不失落?
针对下面的状况,确保音讯不失落
生产者发送音讯 RabbitMQ Server 音讯失落解决方案:
- 罕用解决方案:发送方确认机制(publisher confirm)
- 开启 AMQP 的事务处理(不举荐)
RabbitMQ Server 中存储的音讯失落解决方案:
- 音讯回退:通过设置 mandatory 参数能够在当消息传递过程中不可达目的地时将音讯返回给生产者
- 设置长久化:保障重启过程中,交换机和队列也是长久化的
RabbitMQ Server 到消费者音讯失落解决方案:
- 手动 ack 确认机制
1、生产者发送音讯 RabbitMQ Server 音讯失落解决方案
1、公布确认机制
生产者将信道设置成 confirm 模式,一旦信道进入 confirm 模式,所有在该信道下面公布的音讯都将会被指派一个惟一的 ID(从 1 开始),一旦音讯被投递到所有匹配的队列之后,broker 就会发送一个确认给生产者(蕴含音讯的惟一 ID),这就使得生产者晓得音讯曾经正确达到目标队列了,如果音讯和队列是可长久化的,那么确认音讯会在将音讯写入磁盘之后收回,broker 回传给生产者的确认音讯中 delivery-tag 域蕴含了确认音讯的序列号,此外 broker 也能够设置
basic.ack
的 multiple 域,示意到这个序列号之前的所有音讯都曾经失去了解决。
confirm 模式最大的益处在于它是异步的,一旦公布一条音讯,生产者应用程序就能够在等信道返回确认的同时持续发送下一条音讯,当音讯最终失去确认之后,生产者利用便能够通过回调办法来解决该确认音讯,如果 RabbitMQ 因为本身外部谬误导致音讯失落,就会发送一条 nack 音讯,生产者应用程序同样能够在回调办法中解决该 nack 音讯。公布确认分为三种:
独自公布确认 :这是一种简略的确认形式,它是一种 同步确认公布 的形式,也就是公布一个音讯之后只有它被确认公布,后续的音讯能力持续公布,
waitForConfirmsOrDie(long)
这个办法只有在音讯被确认的时候才返回,如果在指定工夫范畴内这个音讯没有被确认那么它将抛出异样。这种确认形式有一个最大的毛病就是:公布速度特地的慢,因为如果没有确认公布的音讯就会阻塞所有后续音讯的公布,这种形式最多提供每秒不超过数百条公布音讯的吞吐量。当然对于某些应用程序来说这可能曾经足够了。
- 批量公布确认 :下面那种形式十分慢,与单个期待确认音讯相比,先公布一批音讯而后一起确认能够极大地提高吞吐量,当然这种形式的毛病就是: 当产生故障导致公布呈现问题时,不晓得是哪个音讯呈现问题了,咱们必须将整个批处理保留在内存中,以记录重要的信息而后从新公布音讯。当然这种计划依然是同步的,也一样阻塞音讯的公布。
- 异步公布确认:异步确认尽管编程逻辑比上两个要简单,然而性价比最高,无论是可靠性还是效率都没得说,他是利用回调函数来达到音讯可靠性传递的,这个中间件也是通过函数回调来保障是否投递胜利
2、开启 AMQP 的事务处理(不举荐)
为什么不举荐呢,因为它是同步的,一条音讯发送之后会使发送端阻塞,以期待 RabbitMQ Server 的回应,之后能力持续发送下一条音讯,生产者生产音讯的吞吐量和性能都会大大降低,这就跟独自公布确认一样。
如何应用:在生产者发送音讯之前,通过 channel.txSelect 开启一个事务,接着发送音讯,如果音讯投递 server 失败,进行事务回滚 channel.txRollback,而后从新发送,如果 server 收到音讯,就提交事务 channel.txCommit
2、RabbitMQ Server 中存储的音讯失落解决方案
第一种保障音讯失落,只可能保障发送方发送音讯胜利达到交换机,若此时服务器存在问题或者绑定的 routingKey 不正确,导致音讯发送失败,那么音讯最终也会失落。
- 采纳音讯回退:通过设置 mandatory 参数能够在当消息传递过程中不可达目的地时将音讯返回给生产者
- 设置长久化
1、音讯回退
源码:
mandatory 参数
true: 交换机无奈将音讯进行路由时,会将该音讯返回给生产者
false:如果发现音讯无奈进行路由,则间接抛弃
public void basicPublish(String exchange, String routingKey, boolean mandatory, BasicProperties props, byte[] body) throws IOException {this.delegate.basicPublish(exchange, routingKey, mandatory, props, body);
}
有了 mandatory 参数和回退音讯,咱们取得了对无奈投递音讯的感知能力,有机会在生产者的音讯无奈被投递时发现并解决。但有时候,咱们并不知道该如何解决这些无奈路由的音讯,最多打个日志,而后触发报警,再来手动解决。而通过日志来解决这些无奈路由的音讯是很不优雅的做法,特地是当生产者所在的服务有多台机器的时候,手动复制日志会更加麻烦而且容易出错。
这时须要采纳 备份交换机 了
备份交换机能够了解为 RabbitMQ 中交换机的“备胎”,当咱们为某一个交换机申明一个对应的备份交换机时,
就是为它创立一个备胎,当交换机接管到一条不可路由音讯时,将会把这条音讯转发到备份交换机中,由备份交换机来进行转发和解决,通常备份交换机的类型为 Fanout,这样就能把所有音讯都投递到与其绑定的队列中,而后咱们在备份交换机下绑定一个队列,这样所有那些原交换机无奈被路由的音讯,就会都进入这个队列了。当然,咱们还能够建设一个报警队列,用独立的消费者来进行监测和报警。
具体代码请参考这篇:
2、设置长久化
下面咱们的角度是站在生产者的方向,然而如果服务器重启了,此时交换机和队列都不存在了,音讯存在也发送不了,这时须要把交换机和队列都长久化。
/**
* 生成一个队列
* 1. 队列名称
* 2. 队列外面的音讯是否长久化 默认音讯存储在内存中
* 3. 该队列是否只供一个消费者进行生产 是否进行共享 true 能够多个消费者生产
* 4. 是否主动删除 最初一个消费者端开连贯当前 该队列是否主动删除 true 主动删除
* 5. 其余参数
*/
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
3、RabbitMQ Server 到消费者音讯失落解决方案
默认音讯采纳的是自动应答,所以咱们要想实现音讯生产过程中不失落,须要把自动应答改为手动应答
// 将自动应答敞开
boolean autoAck = false;
channel.basicConsume(TASK_QUEUE_NAME, autoAck, deliverCallback, consumerTag -> {});
10、RabbitMQ 音讯基于什么传输?
因为 TCP 连贯的创立和销毁开销较大,且并发数受系统资源限度,会造成性能瓶颈。RabbitMQ 应用 信道 的形式来传输数据。信道是建设在实在的 TCP 连贯内的虚构连贯,且每条 TCP 连贯上的信道数量没有限度。
11、RabbitMQ 反对音讯的幂等性吗?
反对。在音讯生产时,MQ 外部针对每条生产者发送的音讯生成一个 inner-msg-id,作为去重的根据(音讯投递失败并重传),防止反复的音讯进入队列。
在音讯生产时,要求音讯体中必须要有一个 bizId(对于同一业务全局惟一,如领取 ID、订单 ID、帖子 ID 等)作为去重的根据,防止同一条音讯被反复生产。
12、RabbitMQ 怎么确保音讯已被生产?
- 生产端配置手动 ACK 确认机制
- 联合数据库进行状态标记解决
13、RabbitMQ 反对事务音讯吗?
反对事务音讯。后面在第 9 题中保障生产者不失落音讯,提到能够应用 AMQP 的事务,然而它是同步的,所以不怎么举荐应用
事务的实现次要是对信道 (Channel) 的设置,次要办法如下:1. channel.txSelect() 申明启动事务模式 2.channel.txCommit() 提交事务 3.channel.txRollback()回滚事务
14、RabbitMQ 音讯长久化的条件?
音讯长久化,当然前提是队列必须长久化
- 申明队列必须设置长久化 durable 设置为 true.
- 音讯推送投递模式必须设置长久化,deliveryMode 设置为 2(长久)。
- 音讯曾经达到长久化交换器。
- 音讯曾经达到长久化队列。
15、RabiitMQ 音讯什么状况下会变成死信音讯?
因为特定的 起因导致 queue 中的某些音讯无奈被生产,这样的音讯如果没有后续的解决,就变成了死信音讯
16、RabbitMQ 死信音讯的起源?
- 音讯 TTL 过期
- 队列达到最大长度(队列满了,无奈再增加数据到 mq 中)
- 音讯被回绝 (basic.reject 或 basic.nack) 并且 requeue=false.
17、RabbitMQ 死信队列的用途?
- 能够用于实现提早队列
18、RabbitMQ 反对提早队列吗?
反对。延时队列,队列外部是有序的,最重要的个性就体现在它的延时属性上,延时队列中的元素是心愿在指定工夫到了当前或之前取出和解决,简略来说,延时队列就是用来寄存须要在指定工夫被解决的元素的队列。
19、RabbitMQ 提早队列的应用场景
- 订单在十分钟之内未领取则主动勾销
- 新创建的店铺,如果在十天内都没有上传过商品,则主动发送音讯揭示
- 用户注册胜利后,如果三天内没有登陆则进行短信揭示
- 用户发动退款,如果三天内没有失去解决则告诉相干经营人员
- 预约会议后,须要在预约的工夫点前十分钟告诉各个与会人员加入会议
20、RabbitMQ 实现提早队列的有什么条件?
- 音讯设置 TTL
- 配置了死信队列
21、RabbitMQ 怎么实现优先级队列?
控制台页面:增加一个
x-max-priority
生产者增加优先级,案例代码
public class Product { private static final String QUEUE_NAME = "hello"; public static void main(String[] args) throws Exception {try(Channel channel = RabbitMQConfig.getChannel()){ // 给音讯赋予一个 priority 属性 AMQP.BasicProperties basicProperties = new AMQP.BasicProperties().builder().priority(5).build(); for (int i = 1; i < 11; i++) { String msg = "info" + i; if(i==5){channel.basicPublish("", QUEUE_NAME, basicProperties, msg.getBytes()); }else{channel.basicPublish("", QUEUE_NAME, null, msg.getBytes()); } System.out.println("发送音讯实现:" + msg); } } } }
消费者队列中代码增加优先级
public class Consumer { private static final String QUEUE_NAME = "hello"; public static void main(String[] args) throws Exception {Channel channel = RabbitMQConfig.getChannel(); // 设置队列的最大优先级 最大能够设置到 255 官网举荐 1-10 如果设置太高比拟吃内存和 CPU Map<String, Object> map = new HashMap<>(); map.put("x-max-priority", 10); channel.queueDeclare(QUEUE_NAME, true, false, false, map); System.out.println("消费者期待启动接管音讯......"); DeliverCallback deliverCallback = (consumerTag, delivery) ->{String receivedMessage = new String(delivery.getBody()); System.out.println("接管到音讯:"+receivedMessage); }; channel.basicConsume(QUEUE_NAME, true, deliverCallback, (consumerTag) ->{System.out.println("消费者无奈生产音讯时调用,如队列被删除"); }); } }
22、哪些状况下举荐应用 RabbitMQ 的惰性队列
- 队列可能会产生音讯沉积
- 队列对性能(吞吐量)的要求不是十分高,例如 TPS 1 万以下的场景
- 心愿队列有稳固的生产生产性能,不受内存影响而稳定
23、RabbitMQ 如何解决音讯沉积状况?
办法:长期扩容,疾速解决积压的音讯
- 先修复 consumer 的问题,确保其复原生产速度,而后将现有的 consumer 都停掉;
- 长期创立原先 N 倍数量的 queue,而后写一个长期散发数据的消费者程序,将该程序部署下来生产队列中积压的数据,生产之后不做任何耗时解决,间接平均轮询写入长期建设好的 N 倍数量的 queue 中;
- 接着,长期征用 N 倍的机器来部署 consumer,每个 consumer 生产一个长期 queue 的数据
- 等疾速生产完积压数据之后,复原原先部署架构,从新用原先的 consumer 机器生产音讯。
这种做法相当于长期将 queue 资源和 consumer 资源扩充 N 倍,以失常 N 倍速度生产。
24、RabbitMQ 如何解决音讯沉积过程中失落的数据?
采纳 “批量重导” 的形式,在流量低峰期,写一个程序,手动去查问失落的那局部数据,而后将音讯从新发送到 mq 外面,把失落的数据从新补回来。
25、RabbitMQ 如何解决长时间未解决导致写满的状况?
如果音讯积压在 RabbitMQ 里,并且长时间都没解决掉,导致 RabbitMQ 都快写满了,这种状况必定是长期扩容计划执行太慢;这种时候只好采纳 “抛弃 + 批量重导” 的形式来解决了。首先,长期写个程序,连贯到 RabbitMQ 外面生产数据,生产一个抛弃一个,疾速生产掉积压的音讯,升高 RabbitMQ 的压力,而后在流量低峰期时去手动查问重导失落的这部分数据。
26、如何设计一个音讯队列?
要思考三点:伸缩性、长久化、可用性
- 伸缩性:须要扩容的时候能够疾速扩容,减少吞吐量和容量;能够参考
kafaka
的设计理念,broker -> topic -> partition,每个 partition 放一个机器,就存一部分数据;资源不够了,给 topic 减少 partition,而后做数据迁徙,减少机器;长久化:也就是数据要不要写入磁盘,不写入吧,过程挂了,数据就失落了,写入磁盘该如何高效写入呢?
kafaka
的思路:程序读写,采纳磁盘缓存(Page Cache)的策略,操作系统采纳预读和后写的形式,对磁盘进行优化。
- 预读:磁盘程序读取的效率是很高的(不须要寻道工夫,只须要很少的旋转工夫)。而在读取磁盘某块数据时,同时会程序读取相邻地址的数据加载到 PageCache,这样在读取后续间断数据时,只须要从 PageCache 中读取数据,相当于内存读写,速度会特地快
- 后写:数据并不是间接写入到磁盘,而是默认先写入到 Page Cache,再由 Page Cache 刷新到磁盘,刷新频率是由操作系统周期性的 sync 触发的(用户也能够手动调用 sync 触发刷新操作)。后写的形式大大减少对磁盘的总写入次数,进步写入效率
- 可用性:分布式系统的高可用简直都是通过冗余实现的,Kafka 同样如此。Kafka 的音讯存储到 partition 中,每个 partition 在其余的 broker 中都存在多个正本。对外通过主 partition 提供读写服务,当主 partition 所在的 broker 故障时,通过 HA 机制,将其余 Broker 上的某个正本 partition 会从新选举成主 partition,持续对外提供服务。
面试专栏合集:
- 靠近 2w 字 Redis 面试题总结,厉害
- Spring MVC“夺命”27 问,太扎实了
- 史上最全的 Spring 面试题总结,简直包含所有会问的点
- 抽空整顿的 45 道经典多线程面试题
- 充电篇:你理解 Java 概述吗?
- 充电篇:Java 异样面试题整顿大全
- 充电篇:Java 数据类型经典十五问
- Mybatis” 夺命 ”33 问,你能答复道第几问