目录
前言
一、死信队列
概念
产生原因
死信队列的架构图
二、延迟队列
概念
使用场景
三、优先级队列
四、惰性队列
使用场景
两种模式
性能对比
RabbitMQ集群
使用集群的原因
搭建集群步骤
镜像队列
创建镜像队列
联邦交换机 Federation Exchange
步骤
联邦队列Federation Queue
总结
前言
继上一篇章了解完了消息队列的基本概念与使用场景,另外还讲解了RabbitMQ的几种工作模式,这一篇章我们来对RabbitMQ的死信队列,延迟队列,以及RabbitMQ的集群搭建等内容进行学习。
一、死信队列
概念
先从概念解释上搞清楚这个定义,死信,顾名思义就是无法被消费的消息,字面意思可以这样理 解,一般来说,生产者 将消息投递到 交换机或者直接到 队列里里了,消费者从队列取出消息 进行消费,但某些时候由于特定的原因导致 queue 中的某些消息无法被消费,这样的消息如果没有 后续的处理,就变成了死信,有死信自然就有了死信队列。
应用场景:为了保证订单业务的消息数据不丢失,需要使用到 RabbitMQ 的死信队列机制,当消息 消费发生异常时,将消息投入死信队列中.还有比如说: 用户在商城下单成功并点击去支付后在指定时 间未支付时自动失效。
产生原因
消息 TTL 过期
队列达到最大长度(队列满了,无法再添加数据到 mq 中)
消息被拒绝(basic.reject 或 basic.nack)并且 requeue=false
死信队列的架构图

开始按照正常步骤,生产者将消息传送给交换机,交换机通过zhangsan 这个key与队列normal绑定,然而这个时候,如果传输的消息由于被拒绝,或者TTL时间过期,队列已满的原因会进入到另一个交换机dead中,然后传输到死信队列dead中,交给c2消费者进行消费。如果没有出现问题,消息会按照正常情况由c1消费者进行消费。
二、延迟队列
概念
顾名思义 延迟队列的概念即我们队列中的消息不会立刻由消费者取出并进行消费,而是延迟到我们设定的时间后再进行消费。
使用场景
1.订单在十分钟之内未支付则自动取消
2.新创建的店铺,如果在十天内都没有上传过商品,则自动发送消息提醒。
3.用户注册成功后,如果三天内没有登陆则进行短信提醒。
4.用户发起退款,如果三天内没有得到处理则通知相关运营人员。
5.预定会议后,需要在预定的时间点前十分钟通知各个与会人员参加会议
这些场景都有一个特点,需要在某个事件发生之后或者之前的指定时间点完成某一项任务,如: 发生订单生成事件,在十分钟之后检查该订单支付状态,然后将未支付的订单进行关闭;看起来似乎 使用定时任务,一直轮询数据,每秒查一次,取出需要被处理的数据,然后处理不就完事了吗?如果 数据量比较少,确实可以这样做,比如:对于“如果账单一周内未支付则进行自动结算”这样的需求, 如果对于时间不是严格限制,而是宽松意义上的一周,那么每天晚上跑个定时任务检查一下所有未支 付的账单,确实也是一个可行的方案。但对于数据量比较大,并且时效性较强的场景,如:“订单十 分钟内未支付则关闭“,短期内未支付的订单数据可能会有很多,活动期间甚至会达到百万甚至千万 级别,对这么庞大的数据量仍旧使用轮询的方式显然是不可取的,很可能在一秒内无法完成所有订单 的检查,同时会给数据库带来很大压力,无法满足业务要求而且性能低下。
在讲延迟队列的时候,我们首先要了解一个概念 “TTL属性”。
TTL
是什么呢?
TTL
是
RabbitMQ
中一个消息或者队列的属性,表明一条消息或者该队列中的所有消息的最大存活时间,
单位是毫秒。换句话说,如果一条消息设置了 TTL
属性或者进入了设置
TTL
属性的队列,那么这 条消息如果在 TTL
设置的时间内没有被消费,则会成为
"
死信
"
。如果同时配置了队列的
TTL
和消息的 TTL,那么较小的那个值将会被使用。
有两种方式设置
TTL
。
一种是在声明队列的时候设置,另一种是对消息设置。
三、优先级队列
在我们创建队列的时候可以勾选让之成为优先级队列,然后在创建消息的时候对其设置优先级,这样消费者在队列中有多个消息的时候会优先取优先级最高的消息进行消费。
四、惰性队列
使用场景
RabbitMQ
从
3.6.0
版本开始引入了惰性队列的概念。惰性队列会尽可能的将消息存入磁盘中,而在消 费者消费到相应的消息时才会被加载到内存中,它的一个重要的设计目标是能够支持更长的队列,即支持 更多的消息存储。当消费者由于各种各样的原因(
比如消费者下线、宕机亦或者是由于维护而关闭等
)
而致 使长时间内不能消费消息造成堆积时,惰性队列就很有必要了。
默认情况下,当生产者将消息发送到
RabbitMQ
的时候,队列中的消息会尽可能的存储在内存之中,
这样可以更加快速的将消息发送给消费者。即使是持久化的消息,在被写入磁盘的同时也会在内存中驻留 一份备份。当 RabbitMQ
需要释放内存的时候,会将内存中的消息换页至磁盘中,这个操作会耗费较长的 时间,也会阻塞队列的操作,进而无法接收新的消息。虽然 RabbitMQ
的开发者们一直在升级相关的算法, 但是效果始终不太理想,尤其是在消息量特别大的时候。
两种模式
队列具备两种模式:
default
和
lazy
。默认的为
default
模式,在
3.6.0
之前的版本无需做任何变更。
lazy 模式即为惰性队列的模式,可以通过调用 channel.queueDeclare
方法的时候在参数中设置,也可以通过 Policy 的方式设置,如果一个队列同时使用这两种方式设置的话,那么
Policy
的方式具备更高的优先级。
如果要通过声明的方式改变已有队列的模式的话,那么只能先删除队列,然后再重新声明一个新的。 在队列声明的时候可以通过“x-queue-mode”
参数来设置队列的模式,取值为
“default”
和
“lazy”
。下面示 例中演示了一个惰性队列的声明细节:
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-queue-mode", "lazy");
channel.queueDeclare("myqueue", false, false, false, args);
性能对比

在发送 1 百万条消息,每条消息大概占 1KB 的情况下,普通队列占用内存是 1.2GB,而惰性队列仅仅占用 1.5MB。
RabbitMQ集群
使用集群的原因
最开始我们一直介绍的都是基于单机版运行
RabbitMQ 服务,然而如果
RabbitMQ
服务器遇到内存崩溃、机器掉电或者主板故障等情况,该怎么办?单台
RabbitMQ 服务器可以满足每秒 1000
条消息的吞吐量,那么如果应用需要
RabbitMQ
服务满足每秒
10
万条消息的吞
吐量呢?购买昂贵的服务器来增强单机
RabbitMQ
务的性能显得捉襟见肘,搭建一个
RabbitMQ
集群才是 解决实际问题的关键。
搭建集群步骤
1.修改 3 台机器的主机名称
vim /etc/hostname
2.配置各个节点的 hosts 文件,让各个节点都能互相识别对方
vim /etc/hosts
10.211.55.74 node1
10.211.55.75 node2
10.211.55.76 node3
3.以确保各个节点的 cookie 文件使用的是同一个值
在
node1
上执行远程操作命令
scp /var/lib/rabbitmq/.erlang.cookie root@node2:/var/lib/rabbitmq/.erlang.cookie
scp /var/lib/rabbitmq/.erlang.cookie root@node3:/var/lib/rabbitmq/.erlang.cookie
4.启动 RabbitMQ 服务,顺带启动 Erlang 虚拟机和 RbbitMQ 应用服务(在三台节点上分别执行以
下命令)
rabbitmq-server -detached
5.在节点 2 执行
rabbitmqctl stop_app
(rabbitmqctl stop
会将
Erlang
虚拟机关闭,
rabbitmqctl stop_app
只关闭
RabbitMQ
服务
)
rabbitmqctl reset
rabbitmqctl join_cluster rabbit@node1
rabbitmqctl start_app(
只启动应用服务
)
6.在节点 3 执行rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster rabbit@node2
rabbitmqctl start_app
7.查看集群状态
rabbitmqctl cluster_status
8.需要重新设置用户
创建账号
rabbitmqctl add_user admin 123
设置用户角色
rabbitmqctl set_user_tags admin administrator
设置用户权限
rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"
9.
解除集群节点
(node2
和
node3
机器分别执行
)
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl start_app
rabbitmqctl cluster_status
rabbitmqctl forget_cluster_node rabbit@node2(node1
机器上执行
)
镜像队列
如果
RabbitMQ
集群中只有一个
Broker
节点,那么该节点的失效将导致整体服务的临时性不可用,并 且也可能会导致消息的丢失。可以将所有消息都设置为持久化,并且对应队列的durable
属性也设置为
true
, 但是这样仍然无法避免由于缓存导致的问题:因为消息在发送之后和被写入磁盘井执行刷盘动作之间存在 一个短暂却会产生问题的时间窗。通过publisherconfirm
机制能够确保客户端知道哪些消息己经存入磁盘,
尽管如此,一般不希望遇到因单点故障导致的服务不可用。
引入镜像队列
(Mirror Queue)
的机制,可以将队列镜像到集群中的其他
Broker
节点之上,如果集群中的一个节点失效了,队列能自动地切换到镜像中的另一个节点上以保证服务的可用性。
创建镜像队列
1.
启动三台集群节点
2.
随便找一个节点添加
policy
3.
在
node1
上创建一个队列发送一条消息,队列存在镜像队列
4.
停掉
node1
之后发现
node2
成为镜像队列
5.
就算整个集群只剩下一台机器了 依然能消费队列里面的消息
说明队列里面的消息被镜像队列传递到相应机器里面了
联邦交换机 Federation Exchange
(broker
北京
)
,
(broker
深圳
)
彼此之间相距甚远,网络延迟是一个不得不面对的问题。有一个在北京的业务(Client
北京
)
需要连接
(broker
北京
)
,向其中的交换器
exchangeA
发送消息,此时的网络延迟很小, (Client 北京
)
可以迅速将消息发送至
exchangeA
中,就算在开启了
publisherconfirm
机制或者事务机制的情况下,也可以迅速收到确认信息。此时又有个在深圳的业务(Client
深圳
)
需要向
exchangeA
发送消息,那么(Client
深圳
) (broker
北京
)
之间有很大的网络延迟,
(Client
深圳
)
将发送消息至
exchangeA
会经历一定的延迟,尤其是在开启了 publisherconfirm
机制或者事务机制的情况下,
(Client
深圳
)
会等待很长的延迟时间来接收(broker
北京
)
的确认信息,进而必然造成这条发送线程的性能降低,甚至造成一定程度上的阻塞。
将业务
(Client
深圳
)
部署到北京的机房可以解决这个问题,但是如果
(Client
深圳
)
调用的另些服务都部署在深圳,那么又会引发新的时延问题,总不见得将所有业务全部部署在一个机房,那么容灾又何以实现?
这里使用
Federation
插件就可以很好地解决这个问题
.
1.
需要保证每台节点单独运行
2.
在每台机器上开启
federation
相关插件
rabbitmq-plugins enable rabbitmq_federation
rabbitmq-plugins enable rabbitmq_federation_management
3.
原理图
(
先运行
consumer
在
node2
创建
fed_exchange)
4.
在
downstream(node2)
配置
upstream(node1)
5.
添加
policy
6.
成功的前提
步骤
联邦队列Federation Queue
联邦队列可以在多个
Broker
节点
(
或者集群
)
之间为单个队列提供均衡负载的功能。一个联邦队列可以 连接一个或者多个上游队列(upstream queue)
,并从这些上游队列中获取消息以满足本地消费者消费消息的需求。
原理图
2.
添加
upstream(
同上
)
3.
添加
policy
总结
消息队列作为开发中常用的中间件,是当下热门的技术之一,除了RabbitMQ以外,还有.ActiveMQ,Kafka,RocketMQ等等,消息队列在多种场景都可以使用。