RabbitMQ
阅读提示
建议先看题目目录,再按“概念 -> 原理 -> 场景 -> 优化”顺序复习。
每题先讲结论,再补关键机制和项目实践,回答会更稳。
1、为什么要用MQ
在分布式系统下具备异步、削峰、解耦等功能。
- 拥有持久化的机制,进程消息、队列中的信息也可以保存下来;
- 实现消费者和生产者之间的解耦;
- 对于高并发场景下,利用消息队列可以使得同步访问变为串行访问达到一定量的限流,利于数据库的操作;
- 可以使用消息队列达到异步下单的效果,排队中,后台进行逻辑下单;
2、RabbitMQ的组成成分和工作原理
- 生产者(Producer)、信道(Channel)、交换机(Exchange)、绑定(Binding)、队列(Queue)、消费者(Consumer)。
- 生产者通过信道把消息发送给交换机,交换机接收消息并且对消息进行路由,交换机绑定队列并根据路由键来把消息分发到不同的队列上,消息会一直留在队列里直到被消费者消费。
3、常用的交换机有哪几种
- Direct(默认):一对一、点对点的发送,定向把消息交给符合指定routing key 的队列;
- Fanout:广播,将消息交给所有绑定到交换机的队列;
- Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列;
- Routingkey 一般都是由一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert
- 通配符规则:
#:匹配一个或多个词*:匹配不多不少恰好1个词
4、如何保证消息不丢失
- 消息丢失的原因:网络问题。网络发生故障、丢包造成消息丢失。
- 生产者丢失:将信道设置成confirm模式。生产者通过调用chamnel.confimSelect方法将信道设置为confmm模式,一旦信道进入confirm模式,所有在该信道上面发布的消息都会被指派一个唯一的ID(从1开始),一消息被投递到所有匹配的队列之后,RabbitMQ就会发送一个确认 (Basic.Ack) 给生产者;
- mq中丢失:做消息的持久化。避免RabbitMQ把消息弄丢,发消息前,将消息持久化到数据库,并记录消息状态。
- 前提是:队列和交换机也都进行了持久化.(因为消息都经历了队列和交换机,中途也可能发生消息的丢失)
- 消费者丢失:手动ACK。消费端默认是自动ack的,这样生产者在发送完消息就把消息标记为清除,改成手动ack,当消息正确处理完再通知mq清除消息;当消费端处理异常的时候回传nack,这样mq会把这个消息投递到另外一个消费者。
- 手动Ack:主要是确认消息被消费者消费完成后通知服务器将队列里面的消息清除
5、如何防止消息重复消费
- 什么是消息重复消费?首先我们来看一下消息的传输流程。消息生产者-->MQ-->消息消费者;消息生产者发送消息到MQ服务器,MQ服务器存储消息,消息消费者监听MQ的消息,发现有消息就消费消息。所以消息重复也就出现在两个阶段:
- 生产者多发送了消息给MQ:在消息生产时, MQ 内部针对每条生产者发送的消息生成⼀个 inner-msg-id,作为去重和幂等的依据(消息投递失败并重传),避免重复的消息进⼊队列;
- MQ的一条消息被消费者消费了多次:在消息消费时,要求消息体中必须要有⼀个 bizId(对于同一业务全局唯一,如支付id 、订单id 、帖子id 等)作为去重和幂等的依据,避免同⼀条消息被重复消费。
6、RabbitMQ的死信队列
- 死信队列:当消息在一个队列中变成无法被消费的消息之后,它能被重新投递到另一个交换机,这个交换机就是死信队列。
- 消息变成死信的几种情况:
- 消息被拒绝(channel.basicReject/channel.basicNack)并且request=false;
- 消息在队列的存活时间超过设置的生存时间(TTL)时间;
- 队列达到最大长度(队列满了,无法再添加数据到队列中)。
- 死信队列也是一个正常的交换机,和一般的交换机没有区别,它能在任何的队列上被指定,实际上就是设置某个队列的属性。
- 死信队列的设置:
- 首先,需要设置死信队列的交换机和队列,然后进行绑定;
- 然后,我们声明正常交换机、队列,再绑定,只不过我们需要在队列机上一个参数:
arguments.put("x-dead-letter-exchange","dlx.exchange");这样消息在过期、被拒绝、队列在达到最大长度时,消息就可以直接路由到死信队列。
7、RabbitMQ如何实现延迟消息的
- 通过死信队列来实现,创建一个正常的队列和一个死信队列,在发送消息时,设置消息的生存时间TTL(Time-To-Live),如果消息在正常队列中过期,会被转发到死信队列,创建一个消费者来监听死信队列,处理延迟消息。
- RabbitMQ提供了一个名为rabbitmq_delayed_message_exchange的插件,可以用来实现延迟消息。
8、消息长时间堆积怎么处理
- 临时扩容,以更快的速度去消费:修复并停掉 consumer,新建⼀个 topic,partition是原来的10倍,建⽴临时queue,数量是原来的10倍或20倍,写临时 consumer 程序,临时征用10倍的机器去消费数据,消费完成之后,恢复原来的consumer。
- 消息重发:RabbitMQ如果设置了过期时间,消息积压之后,过了这个时间就过期被清理了,所以消息就直接丢失了。就只能再重新补数据,在流量低峰期的时候写一个程序手动查询丢失的那部分数据,将消息重新发送到mq里,重新消费。
- 如果是积压的消息太多没消费,然后MQ放不下这么多消息导致后面的消息直接被丢弃。就结合上面两种方式去处理。
9、RabbitMQ集群
普通集群模式
普通集群模式是一种将多个RabbitMQ实例部署在不同服务器上,这些实例之间协同工作,共享队列和交换机的元数据,并通过内部通信协议来协调消息的传递和管理。
在这种模式下,我们可以创建队列Queue,其元数据(配置信息)会在集群中的所有实例之间同步。但是队列中的消息只会存在于一个RabbitMQ实例上,而不会同步到其他队列。这个实例通常被称为消息所在的“主节点”或“主实例”。
当我们消费消息时,如果消费者连接到了不保存消息的实例,那么该实例会通过元数据信息定位消息所在的主节点,并将数据拉取到该实例,然后发送给消费者进行处理。发送消息也是类似的,当发送者连接到一个不保存消息的实例时,消息会被转发到主节点进行写操作。
在这种集群模式下,每个实例的元数据是相同的,它们都具有相同的队列和交换机配置。但是队列中的消息数据在不同的实例上是不同的,它们仅存在于主节点上。通过增加实例的方式,可以提升整个集群的消息存储容量和处理性能。
这种集群模式提高了高可用性,因为即使某个实例出现故障,其他实例仍然可以继续提供服务。但它的一个主要限制是无法在每个实例上读取和写入消息数据,只有主节点上可以进行消息的读写操作。这可能导致在消息数据所在的主节点上出现瓶颈,尤其在大量消息处理的情况下。
镜像队列模式
镜像队列模式下,每个RabbitMQ实例都像镜像一样,存储的内容都是相同的。在这种模式下,队列的元数据和消息数据不再是单独存储在某个实例上,而是集群中的所有实例上都存储一份。
每次在消息写入时,需要在集群中的所有实例上都同步一份消息数据和元数据。这样,即使其中一台实例发生故障,剩余的实例也可以继续提供完整的数据和服务。这种模式提高了消息队列的高可用性,因为消息数据在集群中的多个实例上都有备份,不会因单个节点的故障而导致消息的丢失。
