RabbitMQ
一、RabbitMQ的架构设计
Rabbit是一个开源的消息中间件,用于在应用程序之间传递消息。它实现了AMQP(高级消息队列协议)并支持其他消息传递协议,如STOMP和MQTT。
整体架构如下:
Producer(生产者):生产者是消息的发送方,负责将消息发布到RabbitMQ的交换机(Exchange)。
VHost:是RabbitMQ中虚拟主机的概念,它类似于操作系统的命名空间,用于将RabbitMQ的资源进行隔离和分组。每个VHost拥有自己的交换机、队列、绑定和权限设置,不同的VHost之间的资源互相独立,互不干扰。VHost开源用于将不同的应用或服务进行隔离,以防止彼此之间的消息冲突和资源竞争。
Exchange(交换机):交换机是消息的接收和路由中心,它接受来自生产者的消息,并将消息路由到一个或多个与之绑定的队列(Queue)中。
Queue(队列):队列是消息的存储和消费地,它保存着未被消费的消息,等待消费者(Consumer)从队列获取并处理消息。
Binding(绑定):绑定是交换机和队列之间的关联关系,它定义了交换机将信息路由到了哪些队列中。
Consumer(消费者):消费者是消息的接收方,负责从队列获取消息,并进行处理和消费。
二、Rabbit是怎么做消息分发的?
rabbitMQ一共有6种工作模式(消息分发方式)分别是简单模式、工作队列模式、发布订阅模式、
路由模式、主题模式以及RPC模式。
简单模式:最基本的工作模式,也是最简单的消息传递方式。在简单模式中,一共生产者将消息发送到一个队列中,一个消费者从队列中获取并处理消息。这种模式适应于单个生产者和单个消费者的简单场景,消息处理也是同步的。

工作队列模式:用于实现一个任务在多个消费者之间的并发处理。在工作队列模式中,一个生产者将消息发送到一个队列中,多个消费者从中获取并处理消息。每个消息只能被一个消费者处理。这种模式适用于多个消费者并发处理信息的情况,提高了系统的处理能力和吞吐量。

发布/订阅模式:用于实现一条消息被多个消费者同时接收和处理。在发布/订阅模式中,一个生产者将消息发送到交换机(Exchange)中,交换机将消息广播到所有绑定的队列,每个队列对应一个消费者。这种模式适用于消息需要被多个消费者同时接收和处理的场景,如日志订阅和事件通知等。
路由模式:用于实现根据消息的路由键(Routing Key)将消息路由到不同的队列中。在路由模式中,一个生产者将消息发送到交换机中,并指定消息的路由键,交换机根据路由键将消息路由到与之匹配的队列中。这种模式适用于根据不同的条件将消息发送到不同的队列中,以实现消息的筛选和分发。

主题模式:是一种更灵活的消息路由模式,它使用通配符匹配路由键,将消息路由到多个队列中。在主题模式中,一个生产者将消息发送到交换机中,并指定主题(Topic)作为路由键,交换机根据通配符匹配将消息路由到与之匹配的队列中。这种模式适用于用于消息的复杂路由需求,可以实现高度灵活的筛选和分发。

RPC模式:是一种用于实现分布式系统中远程调用的工作模式。指的是通过rabbitMQ来实现一种RPC的能力。
三、RabbitMQ如何实现延迟信息消息?
有两种方式:① 死信队列 ② 延迟消息插件
死信队列
当RabbitMQ中的一条正常的消息,因为过了存活时间(TTL过期)、队列长度超限、被消费者拒绝等原因无法被消费时,就会变成Dead Message,即死信。
当一个消息变成死信后,它就能重新发送到死信队列中(其实是交换机-- Exchange)。
基于这样的机制,就可以实现延迟消息。首先给定一个消息设定 TTL ,但是并不消费这个消息,等他过期,过期之后就会进入到死信队列,然后我们再监听死信队列的消息消费就行了。
而且,RabbitMQ中的TTL是可以设置任意时长的,这相比于RocketMQ只支持一些固定的时长更加灵活一些。
存在的问题:可能会造成对头阻塞,因为队列是先进先出的,而且每次只会判断队头的消息是否过期,那么,如果对头的信息时间很长,一直都不过期,那么就会阻塞整个队列,这时候即使安排在它后面的消息过期了,那么也会被一直阻塞。
基于RabbitMQ的死信队列,可以实现延迟消息,非常灵活的实现定时关单,并且借助RabbitMQ的集群扩展性,可以实现高可用,以及处理大并发量。缺点就是可能会存储消息阻塞的问题,还有就是方案比较复杂,不仅要依赖于RabbitMQ,而且还需要声明很多队列,增加系统的复杂度。
RabbitMQ插件
基于RabbitMQ的话,不用死信队列也可以实现延迟消息,那就是基于 rabbitmq_delayed_massage_exchange插件,这种方案能够解决通过死信队列实现延迟消息出现的消息阻塞问题。该插件是RabbitMQ 3.6.12 开始支持的,对版本有要求。安装之后就可以创建delated-message类型的交换机了。
区别:基于死信队列的方式,是消息会先投递到一个正常的队列中,在 TTL 过期后进入死信队列。但是基于插件的方式,消息并不会立即进去队列,而且先把它保存在一个基于Erlang开发的Mnesia数据库中,然后通过一个定时器取查询需要被投递的消息,再把它们投递到x-delayed-message交换机中。
缺点(限制):因为将消息存储于Mnesia表中,并且在当前节点上具有单个磁盘副本,会存在丢失的可能。
四、什么是RabbitMQ的死信队列?
RabbitMQ的死信队列(Dead Letter Queue ,简称QLD)是一种用于消息处理失败或无法路由的消息的机制。它允许将无法被正常消费的消费重新路由到一个队列,以便稍后进一步的处理、分析和排查问题。
当消息队列里面的消息出现以下几种情况时,就可能被称为“死信”:
① 消息处理失败:当消费者由于代码错误、消息格式不正确、业务规则冲突等原因无法成功处理一条消息时,这条消息可以被标记为死信。
② 消息过期:在RabbitMQ中,消息可以设置过期时间。如果消息在规定时间内没有被消费,它可以被认为是死信并被发送到死信队列。
③ 消息被拒绝:当消费者明确拒绝一条消息时,它可以被标记为死信并发送到死信队列。拒绝消息的原因可能是消息无法处理,获取消费者认为消息不符合处理条件。
④ 消息无法路由:当消息不能被路由到任何队列时,例如,没有匹配的绑定关系或路由键时,消息可以被发送到死信队列。

当消息变成 “死信”之后,如何配置了死信队列,它将会被发送到死信交换机,死信交换机将死信投递到一个队列上,这个队列就是死信队列。但是如果没有配置死信队列,那么这个消息将被丢弃。
RabbitMQ的死信队列其实有很多作用,比如实现延迟消息,进而实现订单的到期关闭,超时关单的业务逻辑。
五、RabbitMQ是如何保证高可用的?
RabbitMQ可以通过多种方式来实现高可用性,以确保在硬件故障或其他不可预测的情况下,消息队列系统仍然能够正常运行。RabbitMQ有三种模式:单机模式、普通集群模式、镜像集群模式。
其中单机模式一般用于demo搭建,不适合在生产环境中使用。剩下的集群模式和镜像模式都可以实现不同程度的高可用。
普通集群模式
普通集群模式,就是将RabbitMQ实例部署到多台服务器上,多个实例之间协同工作,共享队列和交换机的元数据,并通过内部通信协议来协调消息的传递和管理。
在这种模式下,我们创建的Queue,它的元数据(配置信息)会在集群中的所有实例间进行同步,但是队列中的消息只会存在一个RabbitMQ实例上,而不会同步到其他队列。

当我们消费消息的时候,如果消费者连接到了为保存消息的实例,那么那个实例会通过元数据定位到消息所在的实例,拉取数据过来发送给消费者进行消费。
消息的发送也是一样的,当发送者连接到了一个不保存消息的实例时,也会被转发到保存信息的实例上进行写操作。
这种集群模式下,每一个实例中的元数据是一样的,大家都是完整的数据。但是队列中的消息数据,在不同的实例上保存的是不一样的。这样通过增加实例的方式就可以提升整个集群的消息存储量,以及性能。
这种方式在高可用上有一段帮助,不至于一个节点挂了就全挂了。但是也还有缺点,至少这个是实例上的数据是没法被读写了。
镜像模式
顾名思义,就是每一台RabbitMQ都像一个镜像一样,存储的内容都是一样的。这种模式下,Queue的元数据和消息数据不再是单独存储在某个实例上,而是集群中所有实例上都会存储一份。

这样每次在消息写入的时候,就需要在集群中的所有实例上都同步一份,这样即使有一台实例发生故障,剩余的实例也可以正常提供完整的数据和服务。
这种模式下,就保障了RabbitMQ的高可用。
六、RabbitMQ如何实现消费端限流?
什么是消费端限流,这是一种保护消费者的手段,假如说,现在是业务高峰期,消息由大量堆积,导致MQ消费者需要不断地进行消息消费,很容易被打挂,甚至重启之后还是被大量消息涌入,继续被打挂。
为了解决这个问题,RabbitMQ提供了basicQos地方式来实现消费端限流。我们可以在消费者端指定最大地未确认消息数,当达到这个限制时,Rabbit将不再推送新的消息给消费者,直到有一些消息得到确认。
要想实现这个功能,首先把自动提交关闭。

接着进行限流配置:

如以上配置,可以实现消费者在处理完一条消息后,才会获取下一条消息。
然后再在消费者处理完一条消息后,手动发送确认消息给到RabbitMQ,这样就可以拉取下一条消息了:

七、RabbitMQ如何防止重复消费?
RabbitMQ的消息是有确认机制的,正常情况下,消费者在消息消费成功后,会发送一个确认消息,消息队列接收到之后,就会将该消息从消息队列中删除,下次也就不会再投递了。
但是如果存在网络延迟的问题,导致确认消息没有发送到消息队列,导致消息重投了,是有可能,所以,在使用MQ时,消费者端自己也需要做好幂等控制来防止消息被重复消费。
一般来说,处理这种幂等问题就是“一锁、二判、三更新”
一锁:第一步,先加锁,可以加分布式锁,悲观锁都可以。但是一定要是个互斥锁!
二判:第二步,进行幂等性判断。可以基于状态机、流水表、唯一性索引等进行重复操作的判断。
三更新:第三步,进行数据的更新,将数据进行持久化。
也就是说我们在发送消息是需要生成一个唯一的标识并且把它放到消息体中,根据这个标识就可以判断两次消息是不是同一条。这样我们在消费者端,接收到消息以后,只需要解析出消息体中的这个唯一标识,就可以判断是否消费成功过了。
八、如何保障消息一定能发送到RabbitMQ?
作为一个消息发送方,如何保证给RabbitMQ发送的消息一定能发送成功,如何确保它一定能收到这个消息呢?
RabbitMQ的消息最终是存储在Queue上的,而在Queue之前还要经过Exchange,那么这个过程中就有两个地方可能导致消息丢失。第一个是Producer到Exchange的过程,第二个就是Exchange到Queue的过程。
为了解决这个问题,有两种方案,一种是通过 confirm 机制,另外就是事务机制(不推荐)。
上面两个可能丢失的过程,都可以利用confirm机制,注册回调来监听是否成功。
① Publisher Confirm是一种机制,用于确保消息已经被Exchange成功接收和处理。一旦消息成功到达Exchange并被处理,RabbitMQ会向消息生产者发送确认信号(ACK)。如果由于某种原因(例如,Exchange不存在或路由键不匹配)消息无法被处理,RabbitMQ会向消息生产者发送否认信号(NACK)。

Publisher Returns机制与Publisher Confirm类似,但用于处理消息无法路由到任何队列时的情况。当RabbitMQ在无法路由消息时将消息返回给消费者,但是如果能正确路由,则不会返回消息。

通过以上方式,注册了两个回调监听,用于在消息发送到Exchange或者Queue失败时进行异常处理。通常可以在失败时进行报警或者重试来保障一定能发送成功。
九、RabbitMQ如何保证消息不丢失?
通过confirm 机制确保了RabbitMQ的生产者能够把消息投递给RabbitMQ的Exchange和Queue,那么,Queue又是如何保证消息能够不丢失呢?
RabbitMQ在接收到消息后,默认并不会立即进行持久化,而是先把消息暂存在内存中,这时候如果MQ挂了,那么消息就会丢失。所以要通过持久化机制来保证消息可以被持久化下来。
队列和交换机的持久化
在声明队列时,可以通过设置 durable 参数为ture来创建一个持久化队列。持久化队列会在RabbitMQ服务器重启后保留,确保队列的元数据不会丢失。
在声明交换机时,也可以通过 durable 参数为ture来创建一个持久化交换机。持久化交换机会在RabbitMQ服务器重启后保留,以确保交换机的元数据不会丢失。
绑定关系通常与队列和交换机关联。当创建绑定关系时,还是可以设置 durable 参数为ture,以创建一个持久化绑定。持久化绑定关系会在服务器重启后保留,以确保绑定关系不会丢失。

持久化消息
生产者发送的消息可以通过设置消息的deliveryMode 为 2来创建持久化消息。持久化消息发送到持久化队列后,将在服务器重启后保留,以确保消息不会丢失。

通过设置 deliveryMode 类来实现消息的持久化。但是将消息设置为持久化会增加磁盘I/O开销。
消费者确认机制
有了持久化机制后,那么怎么保证消息在持久化下来之后一定能被消费者消费呢?这里涉及到消息的消费者确认机制。
在RabbitMQ中,消费者处理消息成功后可以向MQ发送ack回执,MQ收到回执后才会删除该消息,这样才能确保消息不丢失。如果消费者在处理消息中出现了异常,那么就会返回nack回执,MQ收到这个回执之后就会重新投递一次消息,如果消费者一直都没有返回ACK / NACK的话,那么它会再尝试重新投递。
无法做到100%不丢。
虽然通过发送者进行异步回调,MQ进行持久化,消费者确认机制,但是也无法保证消息100%不丢,因为MQ的持久化过程是异步的。即使开了持久化,也有可能再内存暂存成功后,异步持久化之间宕机了,那么这个消息就会丢失。
十、介绍下RabbitMQ的事务机制
RabbitMQ的事务机制,允许生产者将一组操作打包成一个原子事务单元,要么全部执行成功,要么全部失败。事务提供了一种确保信息完整性的方法,但需要谨慎使用,因为它们对性能有一定的影响。
RabbitMQ是基于AMQP协议实现的,RabbitMQ中,事务是通过在通道(Channel)上启用的,与事务机制有关的方法有三个:
① txSelect() :将当前channel设置成transaction模式。
② txCommit() :提交事务。
③ txRollback() :回滚事务。
我们先通过txSelect方法开启事务,然后就可以发布消息给MQ,如果txCommit提交成功了,则消息一定到达了MQ,如果在txCommit执行之前MQ实例异常崩溃或者抛出异常,那我们就可以捕获这个异常然后执行txRollback进行回滚事务。
所以,通过事务机制,也能确保消息一定可以发送到MQ。
浙公网安备 33010602011771号