一、前言
Queue(队列)是RabbitMQ的内部对象,用于存储消息队列,并将它们转发给消费者;
二、Queue队列
队列跟交换机共享某些属性,但是队列也有一些另外的属性
- Name:队列的名称
- Durable:是否持久化(重启rabbitmq之后,队列是否还存在)
- Exclusive:是否只被一个客户端连接使用,且当连接关闭后,删除队列
- AutoDelete :是否自动删除(当最后一个消费者退订后即被删除)
- Arguments:队列的其他属性参数,有如下可选项:
(1)x-message-ttl:消息的过期时间,单位:毫秒;
(2)x-expires:队列过期时间,队列在多长时间未被访问将被删除,单位:毫秒;
(3)x-max-length:队列最大长度,超过该最大值,则将从队列头部开始删除消息;
(4)x-max-length-bytes:队列消息内容占用最大空间,受限于内存大小,超过该阈值则从队列头部开始删除消息;
(5)x-overflow:设置队列溢出行为。这决定了当达到队列的最大长度时消息会发生什么。有效值是drop-head、reject-publish或reject-publish-dlx。仲裁队列类型仅支持drop-head;
(6)x-dead-letter-exchange:死信交换器名称,过期或被删除(因队列长度超长或因空间超出阈值)的消息可指定发送到该交换器中;
(7)x-dead-letter-routing-key:死信消息路由键,在消息发送到死信交换器时会使用该路由键,如果不设置,则使用消息的原来的路由键值
(8)x-single-active-consumer:表示队列是否是单一活动消费者,true时,注册的消费组内只有一个消费者消费消息,其他被忽略,false时消息循环分发给所有消费者(默认false)
(9)x-max-priority:队列要支持的最大优先级数;如果未设置,队列将不支持消息优先级;
(10)x-queue-mode(Lazy mode):将队列设置为延迟模式,在磁盘上保留尽可能多的消息,以减少RAM的使用;如果未设置,队列将保留内存缓存以尽可能快地传递消息;
(11)x-queue-master-locator:在集群模式下设置镜像队列的主节点信息。
队列创建
队列在声明后才能被使用,如果声明的队列已经存在,并且属性完全相同,那么此次声明不会对原有队列产生任何影响,如果声明的队列属性与已存在的队列属性有差异,那么就会抛出{ Channel shutdown: channel error; protocol method: #method<channel.close>reply-code=406, reply-text…}
队列持久化
持久化队列会被存储在磁盘上,当重启rabbitmq的时候,队列依旧存在,没有被持久化的队列称作暂存队列,需注意的是,队列的持久化不意味着队列里的消息也是持久化,需要另外设置;
队列的长度
队列的最大长度可以限制为一定数量的消息或字节,或者两者都可以;如果两者同时设置,都适用,但执行会按两者之间的最小值
如果设置了队列长度,并且达到最大值时,其它的消息该何去何从?
当设置了队列长度,并且达到最大值时,RabbitMQ的默认行为是将排在队列最前面的消息丢弃掉或死信消息(如果死信队列设置了最大值,当达到最大值,就会转到死信队列设置转发的另一个队列,当另一个队列再次达到最大值时,就会将排在队列最前面的消息丢弃掉)
如何改变RabbitMQ对队列消息溢出的行为?
RabbitMQ有两种对队列中消息溢出的策略,一种是默认,即抛弃排在队列最前面的消息,另一种是拒绝发布,即丢弃最新发布的消息;
PS:另外,如果启用了发布者确认,则将通过basic.nack消息通知发布者拒绝。如果一条消息被路由到多个队列并被其中至少一个拒绝,则通道将通过basic.nack通知发布者。该消息仍将发布到所有其他可以排队的队列;
消息的确认
RabbitMQ消息的确认分为两种机制:
1、生产者发送消息的确认
2、消费者接受消息的确认
生产者发送消息的确认
这个是用来确认生产者将消息发送给交换机,交换机传递给队列的过程中,消息是否成功投递,发送确认分为两步:
1、确认是否到达交换器
2、确认是否到达队列
具体实现,需要在yml文件中,设置参数,如下图
RabbitTemplate(这个大家都会熟悉,就不多介绍)
消费者接受消息的确认
在实际应用中,可能会发生消费者收到Queue中的消息,在处理消息的时候由于宕机或其它原因,导致消息未被正确处理,而丢失掉(消息被队列删除);为了避免此情况的发生,RabbitMQ提供了两种消息确认模式:
1、自动确认模式:当消息发送到消费者之后,立即删除 ;
2、显示确认模式:当消息发送到消费者之后,等消费者发送一个确认回执,再删除消息;
PS:如果一个消费者在尚未发送确认回执的情况下挂掉了,那么RabbitMQ会将消息重新发送到另一个消费者,如果当时没有可用的消费者,就会一直等待监听此队列的消费者,然后再次尝试发送;
具体实现,需要在yml文件中,设置参数,如下图
配置SimpleMessageListenerContainer消息监听容器的参数
PS:监听的队列名称可以设置多个
当设置为手动确认时,要创建一个类,实现ChannelAwareMessageListener接口(我们都知道消息的监听都是通过channel设置的,因此要实现此接口),重写onMessage()方法,见下图
PS:channel.basicNack与channel.basicReject的区别在于前者的消息能重新入列,后者是直接丢弃
注意:当实现ChannelAwareMessageListener接口,并重写onMessage()方法的类,此类就是一个消费者;当有消息发送到SimpleMessageListenerContainer监听的队列中时,就会执行此方法,并发送消息回执到队列中;
消息的分发
在多个消费者同时订阅同一个Queue中的消息,Queue中的消息会被平摊给多个消费者,也就是给每个消费者分派一个消息,谁先执行完,Queue就会再发送一条消息;
消息的分发数量由prefetch控制,有两种配置方法,见下图
消费者并发数量
默认情况下,RabbitMQ的消费者为单线程串行消费,这意味着此消费者如果因为某些原因,使得队列中的消息发生阻塞,那么后续的消息就无法被消费;RabbitMQ有提供消费者的并发设置,用来解决此问题,有两种配置方式,见下图
死信队列
死信,在官网中对应的单词为“Dead Letter”,它是RabbitMQ中的一种消息机制,当你在消费消息时,如果队列里的消息出现以下情况:
1、消息被否认确认,使用channel.basicNack或channel.basicReject,并且requeue属性被设置为false;
2、消息在队列的存活时间超过设置的TTL(过期时间);
3、消息队列的消息数量已超过队列设置的最大长度;
那么该消息将成为“死信”,死信消息会被RabbitMQ进行特殊处理;
死信队列的操作流程
首先,我们来比对下死信队列和普通队列创建的形式,以下是它们的区别:
普通队列和交换机
死信队列和死信交换机
对比以上两张图发现,除了死信队列和普通队列的创建略有些不同,其它步骤都是一样的;
PS:
“x-dead-letter-exchange”的值“userOrderExchange”,其实是另一个交换机的名称
“x-dead-letter-routing-key”的值“userOrderRouting”,是发送消息的路由规则,见下图:
死信交换机userOrderExchange和它绑定的queue和routingkey;
为什么死信队列在创建的时候,要在HashMap集合中设置另一个交换机的名称和routingkey(路由规则)?请看下面死信队列的工作图:
总结:正常来说,当生产者发送消息时,会根据交换机和路由规则,匹配到指定的死信队列,当发送的消息在死信队列中,不会因为TTL或超过队列最大长度的上限(不会成为死信消息),就会直接发送到消费者,如果发送的消息TTL或超过队列最大长度的上限(成为死信消息),就会把消息按创建死信队列时,设置Routingkey和Exchange,发送到绑定的Queue的上,再发给消费者;