RabbitMQ 入门教程之十
大纲
- RabbitMQ 入门教程之一、RabbitMQ 入门教程之二、RabbitMQ 入门教程之三
- RabbitMQ 入门教程之四、RabbitMQ 入门教程之五、RabbitMQ 入门教程之六
- RabbitMQ 入门教程之七、RabbitMQ 入门教程之八、RabbitMQ 入门教程之九
- RabbitMQ 入门教程之十、RabbitMQ 入门教程之十一
前言
学习资源
RabbitMQ 集群搭建
背景介绍
最开始我们使用的 RabbitMQ 只是单机版本,随着业务的发展,已经无法满足实际应用中的高可用性和高并发处理需求。试想一下:如果 RabbitMQ 所在的服务器出现内存溢出、机器掉电、主板损坏等硬件故障,整个消息服务就会中断,系统将面临严重的可用性问题。此外,虽然一台 RabbitMQ 服务器在良好的配置下可以支持大约每秒 1000 条消息的吞吐量,但如果业务系统需要处理每秒 10 万条甚至更高数量级的消息,仅靠一台服务器远远不够。此时,一味依赖升级硬件来提升单机性能不仅成本高昂、扩展性差,而且无法从根本上解决问题。因此,搭建 RabbitMQ 集群才是应对实际业务压力、提升系统稳定性和可用性的关键解决方案。
集群节点
RabbitMQ 的集群节点分为磁盘节点、内存节点。RabbitMQ 支持消息的持久化,也就是数据写在磁盘上。在 RabbitMQ 集群中,必须至少有一个磁盘节点,否则队列元数据无法写入到集群中。当磁盘节点宕掉时,集群将无法写入新的队列元数据信息。如果 RabbitMQ 集群全部宕机,必须先启动磁盘节点,然后再启动内存节点。最合适的方案就是既有磁盘节点,又有内存节点;建议至少 2 个磁盘节点,其他节点可以作为内存节点来提高性能,比如采用 2 个 磁盘节点 + 1 个内存节点的集群搭建方式。
节点类型 | 节点特点 |
---|---|
disc (磁盘节点) | 元数据持久化到磁盘,集群元数据(如队列、交换机、绑定、用户、权限的定义等)会复制到这些节点上。至少需要一个 disc 节点才能组成完整的 RabbitMQ 集群。 |
ram (内存节点) | 大部分集群元数据仅保存在内存中,性能更高。启动时会从磁盘节点同步元数据,但自身不会持久化元数据。一旦内存节点宕机或重启,它的元数据就会丢失(除非从磁盘节点重新同步)。掉电就丢失元数据,不适合长期存储,只适合当辅助节点使用。 |
磁盘节点补充说明
- RabbitMQ 默认的集群节点类型是
disc
(磁盘节点),至少需要一个disc
节点才能组成完整的 RabbitMQ 集群。
内存节点补充说明
ram
(内存节点)决定的是 RabbitMQ 的集群元数据保存在内存中,元数据包括队列、交换机、绑定、用户、权限的定义等,而不是决定队列和消息都存储在内存里面,也就是消息的存储还是由队列持久化、消息持久化决定的。- 假设在 RabbitMQ 的一个内存节点上创建了队列,如果这个节点宕机,它的元数据就没了,重启后队列的定义也不存在。但是,如果将队列设置为持久化(
durable=true
),消息也设置为持久化(delivery_mode=2
),即使内存节点宕机了,只要还有磁盘节点存在,就可以恢复内存节点的队列和消息数据。
集群模式
RabbitMQ 集群模式分为两种,包括普通模式和镜像模式;可以说镜像模式是普通模式的升级版,其中 RabbitMQ 默认使用的是普通模式。
普通模式
- 这里以两个节点(节点 A、节点 B)为例来进行说明。
- 对于 Queue 来说,消息实体只存在于其中一个节点,A、B 两个节点仅有相同的元数据,即队列结构。
- 当消息进入 A 节点的队列中后,消费者从 B 节点拉取时,RabbitMQ 会临时在 A、B 间进行消息传输,把 A 中的消息实体取出并经过 B 发送给消费者。
- 所以,消费者应尽量连接每一个节点,从中取消息。即对于同一个逻辑队列,要在多个节点建立物理队列,否则,无论消费者连 A 或者连 B,出口总在 A,会产生瓶颈。
- 该模式还存在一个问题就是当 A 节点宕机后,B 节点无法取到 A 节点中还未消费的消息实体。
- 如果做了消息持久化,那么得等 A 节点恢复,才可被消费;如果没有持久化的话,消息会丢失。
镜像模式
- 在普通模式的基础上,通过镜像队列策略(HA Policy)将需要的队列配置成镜像队列,让队列存储在多个节点上,消息实体会主动在镜像节点之间同步,而不是在客户端取数据时临时拉取,也就是说有多少个镜像节点消息就会备份多少份。
- 该模式带来的副作用也很明显,除了降低系统性能外,如果镜像队列数量过多,加之大量的消息进入,集群内部的网络带宽将会被这种同步通讯大大消耗掉,适用于对业务可靠性要求较高的场景。
- 由于镜像队列之间消息自动同步,且内部有选举 Master 机制,即使 Master 节点宕机也不会影响整个集群的使用,达到去中心化的目的,从而有效的防止消息丢失及服务不可用等问题。
集群架构
RabbitMQ 集群的典型架构(高可用负载均衡集群)如下图所示,展示了如何通过多个节点实现高可用和负载均衡。这种架构可以有效地提高 RabbitMQ 的吞吐量和容错能力,适用于对消息可靠性和系统可用性要求较高的生产环境。架构说明如下:
- 客户端连接:客户端通过虚拟 IP(VIP)连接到集群,VIP 由 Keepalived 管理,实现高可用性。
- 负载均衡:HAProxy 作为负载均衡器,将客户端请求分发到后端的 RabbitMQ 节点。在 HAProxy 的配置中启用健康检查后,HAProxy 可以感知到 RabbitMQ 节点的宕机,并自动将其从负载均衡列表中剔除。
- RabbitMQ 节点:多个 RabbitMQ 节点组成集群,节点之间通过镜像队列(Mirroring)机制同步消息,确保消息的高可用性。
重要提示
无论 RabbitMQ 集群是运行在普通模式,还是启用了镜像模式(使用镜像队列),客户端(包括生产者和消费者)都可以直接连接到集群中的任意节点进行消息的发送与接收(类似于无状态设计)。RabbitMQ 集群节点之间通过内部通信机制来保持数据同步和状态一致,从而对客户端屏蔽了具体的队列位置,使得客户端无需关心消息实际存储在哪个节点上,连接任一节点即可正常使用。这就是为什么可以使用 Keepalived + HAProxy 来实现 RabbitMQ 高可用负载均衡集群的原因。若希望进一步提高可用性,推荐使用 RabbitMQ 的 Quorum Queue + Keepalived + HAProxy + Prometheus + AlertManager 的组合,可以构建一个企业级高可用消息中间件系统。
集群搭建
RabbitMQ 镜像队列
背景介绍
在 RabbitMQ 集群中,如果只有一个 Broker 节点,一旦该节点发生故障,不仅会导致服务的临时性不可用,还可能造成消息的丢失。虽然可以通过将所有消息设置为持久化(delivery_mode = 2
),并将队列设置为持久化(durable = true
)来降低数据丢失的风险,但这仍然无法完全避免由于缓存机制带来的数据丢失问题。因为从消息发送到真正写入磁盘并完成刷盘(Flush)之间存在一个短暂的时间窗口,在此期间如果节点宕机,消息依然可能丢失。为此,可以启用 RabbitMQ 的发布确认机制(Publisher Confirm),让客户端明确知道哪些消息已经被 RabbitMQ 持久化到磁盘,从而提升消息投递的可靠性。尽管如此,从高可用性的角度考虑,仍然不希望因单点故障而导致服务中断。此时,可以通过引入镜像队列(Mirrored Queue)的方式,将队列数据复制(镜像)到集群中的其他 Broker 节点上。当某个节点发生故障时,RabbitMQ 可以自动将队列切换到其镜像副本上,确保服务的持续可用,避免单点故障带来的影响。
概念介绍
RabbitMQ 的镜像队列(Mirrored Queue)是一种用于实现高可用(High Availability, HA)的机制,它可以将一个队列的内容实时同步复制到多个节点(也称为镜像节点)上。这样,即使主节点发生宕机,系统也可以自动将主队列提升到其他镜像节点上,从而继续提供服务,确保消息不丢失、业务不中断。特别注意的是,镜像队列依赖于 RabbitMQ 的集群架构,因此在使用镜像队列之前,必须先搭建好一个基于普通模式(即非分布式分片或联邦模式)的 RabbitMQ 集群环境。镜像队列是在该集群的基础上,通过策略(Policy)配置来实现队列在多个节点之间的同步复制和主备切换的。
镜像队列的工作机制
- 队列创建时,主副本(Master) 会存在某个节点上;
- 该队列的镜像会被复制到指定的其他节点上,称为副本(Slaves);
- 所有生产者和消费者仍然连接主副本,但副本会同步接收数据;
- 如果主副本节点故障,系统会自动从副本列表中提升一个新的主副本。
镜像队列的优缺点
- 优点:
- 高可用性:主副本故障时,副本可以快速接管;
- 数据冗余:减少消息丢失风险;
- 适合关键业务:金融、电商等要求消息高可靠性的场景。
- 缺点:
- 性能开销大:每条消息都需要同步到多个副本,影响吞吐量;
- 资源消耗高:每个镜像副本都占用内存和磁盘;
- 不推荐大规模使用:尤其是在高并发系统中,性能瓶颈比较明显。
- 优点:
镜像队列的使用场景
- 适合使用的场景:
- 少量关键队列,需要高可用保障;
- 节点数量不多,资源压力不大;
- 需要快速故障转移、消息不容丢失。
- 不适合使用的场景:
- 高并发写入、高吞吐场景(容易成为性能瓶颈);
- 队列数量多,副本同步占用大量资源。
- 适合使用的场景:
配置步骤
配置镜像队列的步骤
- 配置 RabbitMQ 的镜像队列,只需要执行以下两个步骤即可:
- (1) 基于普通模式,将 RabbitMQ 集群搭建完成。
- (2) 通过控制台可视化界面或者命令行,添加镜像队列策略(HA Policy)。
通过可视化界面添加镜像队列策略(HA Policy)
- (1) 基于普通模式,将 RabbitMQ 集群搭建完成。
- (2) 在 RabbitMQ 的 Web 控制台中,添加镜像队列策略(HA Policy)。
- (3) 镜像队列策略(HA Policy)创建完成后的样子。
镜像策略配置参数说明
Name:
- 策略的名称
Pattern:
- 匹配的规则,会按照设置的规则进行镜像设置。
- 比如:
^mirror
表示匹配mirror
开头的队列(即会为开头是mirror
的队列进行镜像备份),如果是匹配所有的队列,那就是^.
Apply to:
- 作用:定义策略的作用范围
- 可选值:
Exchanges and queues
:策略会同时应用到队列(Queue)和交换机(Exchange)。不常用于镜像队列配置,因为镜像策略主要是给队列用的。Exchanges
:策略只作用于交换机(Exchange),比如设置 TTL、自动删除、名称匹配等。queues
:策略只作用于队列,包括经典队列(Classic Queues)和仲裁队列(Quorum Queues)。
Definition:
ha-mode
:- 作用:定义镜像队列复制的策略(即在哪些节点上创建副本)。
- 可选值:
all
:在所有节点上都创建副本。exactly
:在指定数量的节点上创建副本,数量由ha-params
指定。nodes
:只在指定的节点上创建副本,节点名称由ha-params
指定。
ha-params
:- 作用:根据
ha-mode
的不同,用于指定副本的数量或副本所在的节点。 - 具体含义:
- 如果
ha-mode
是all
,则ha-params
可以省略不配置。 - 如果
ha-mode
是exactly
,则ha-params
是一个整数,且必须配置,表示副本数量。 - 如果
ha-mode
是nodes
,则ha-params
是一个节点名称组成的数组(如["rabbit@rabbitmq-node1", "rabbitmq-node2"]
),且必须配置。
- 如果
- 作用:根据
ha-sync-mode
:- 作用:控制镜像队列同步的方式(即新加入的副本如何与主副本进行同步)。
- 可选值:
automatic
:新加入的镜像副本会自动从主队列同步数据。manual
:默认值,必须手动触发同步,即通过可视化界面或者执行rabbitmqctl sync_queue <queue_name>
命令进行数据同步。
ha-promote-on-shutdown
:- 作用:控制当主节点优雅关闭(Shutdown)时,是否自动提升一个镜像为新的主队列。
- 可选值:
always
:默认值,主节点关闭时始终提升副本。when-synced
:推荐值,仅当副本已完全同步时才会被提升为主。
ha-promote-on-failure
:- 作用:控制主节点在故障(意外宕机)时,是否自动提升一个镜像为新的主队列。
- 可选值:
true
:默认值,在主队列宕机时,自动选举一个镜像为主。false
:不自动提升,需要人工干预。
特别注意
ha-mode
配置参数没有默认值,因为它是定义 RabbitMQ 镜像队列策略(HA Policy)的核心参数之一,属于必填项。ha-sync-mode
、ha-promote-on-shutdown
、ha-promote-on-failure
配置参数都有默认值,属于非必填项。
通过命令行添加镜像队列策略(HA Policy)
除了可以在 RabbitMQ 的控制台上,通过可视化界面添加镜像队列策略(HA Policy),还可以使用命令行进行添加(前提是 RabbitMQ 集群已搭建完成),如下所示:
1 | # ha-mode: all |
验证配置
(1) 在任意一个集群节点上(如
rabbitmq-node1
)创建一个队列(名称为mirror.test.queue
),该队列会被自动应用镜像队列策略(HA Policy),然后手动发送一条消息到该队列中
(2) 通过
rabbitmqctl stop_app
命令停掉节点二(rabbitmq-node2
)的 RabbitMQ 服务
(3) 当节点二(
rabbitmq-node2
)的 RabbitMQ 服务停掉后,发现新的镜像队列会存储在节点三(rabbitmq-node3
)上面,并且新的镜像队列会自动完成数据同步
总结
这就意味着,在启用了镜像队列策略之后,即使集群中某个节点(即原本承载某个队列的主节点)发生宕机,消费者仍然可以继续正常消费该队列中的消息。因为该队列的内容已经通过镜像机制被实时同步到了其他节点上,也就是说,队列在多个节点上拥有完整的副本。当主节点不可用时,RabbitMQ 会自动从这些副本中选举一个新的主节点,从而保障服务的连续性与消息的高可用性。
注意事项
在 RabbitMQ 的镜像队列(Mirrored Queue)中,存在主副本(Master)和副本(Slave)的概念。消费者永远只能从主副本(Master)读取和写入数据,副本(Slave)不对外提供读取和写入服务,它们的唯一职责是从主副本(Master)同步数据,以保持数据的一致性。这种设计的主要原因是为了确保数据的一致性和简化系统的复杂性。如果允许副本(Slave)提供读服务,可能会导致消费者读取到尚未同步完成的数据,造成数据不一致的问题。当主副本(Master)发生故障时,RabbitMQ 会从副本(Slave)列表中选举一个新的主副本(Master)。一旦新的主副本(Master)选举完成,消费者将自动从新的主副本(Master)继续读取或写入数据,确保服务的连续性。
生产者是往哪个副本写入消息?
在 RabbitMQ 的镜像队列中,写入操作永远是由主副本(Master)来处理的。也就是说:
- 生产者发送消息时,可以将消息发送到 RabbitMQ 集群中的任意一个节点;
- 该节点会将请求路由到该队列的主副本(Master)所在节点上执行;
- 主副本(Master)接收到消息后,会将消息同步复制到所有的镜像副本(Slave);
- 如果设置了强同步策略,当所有副本都确认收到消息后,才会认为消息写入成功。
副本(Slave)的作用是什么?
- 高可用性保障
- 当 Master 节点宕机时,自动从剩余的 Slave 节点中选出一个新的 Master,确保队列持续可用。
- 数据冗余与备份
- Slave 会实时同步 Master 队列中的所有消息和状态,实现数据冗余,防止数据丢失。
- 故障切换支持
- 发生故障时,能够快速切换角色,无需人工干预,提升系统的容错能力。
- 读写一致性保障
- Slave 跟随 Master 接收写操作,确保在 Master 崩溃前的数据都能保存在至少一个副本中。
- 保障消费者不受影响
- 故障切换后消费者可以继续从新的 Master 消费,不需要重新声明或绑定队列。
- 支撑分布式架构
- 多节点协同工作,适应 RabbitMQ 集群化部署需求,提高系统整体稳定性。
- 管理和维护友好
- 提高系统可维护性,在节点维护、升级或扩容时不会中断服务。
副本(Slave)中的消息能不能被消费者消费?
在 RabbitMQ 的镜像队列中,消费者不能消费副本(Slave)中的消息,只能消费主副本(Master)中的消息。也就是说:
- 即使副本(Slave)上有完整的数据,但它们在 RabbitMQ 中是只读的,并且不对外提供读服务;
- 所有消费者都必须连接到主副本(Master)所在的节点,由主副本(Master)派发消息;
- 如果连接的是其他副本节点(Slave),那么该节点会自动将请求路由到主副本节点(Master);
- 这也是为什么 RabbitMQ 推荐使用 HAProxy 或客户端连接逻辑来避免客户端只连接某个节点。
主副本(Master)和副本(Slave)之间的数据同步能否保持强一致性?
在 RabbitMQ 的镜像队列中,主副本(Master)和副本(Slave)之间的数据同步是否可以保持强一致性取决于具体的策略配置。RabbitMQ 提供了两种同步策略:
同步策略 | 是否强一致性 | 是否为默认策略 | 描述 |
---|---|---|---|
automatic (自动同步) | ❌ 弱一致性 | ✅ 是 | 主副本(Master)发送数据给副本(Slave)后,不等待确认就返回成功,副本(Slave)有可能会一时落后 |
always (强同步) | ✅ 强一致性 | ❌ 否 | 主副本(Master)在写入消息时,必须等待所有副本(Slave)都确认收到后才返回成功 |
- 当镜像队列策略配置了
ha-sync-mode = automatic
,即没有启用强同步机制,那么数据是最终一致性,在极端情况下可能丢失一小部分数据,比如主副本(Master)崩溃,但副本(Slave)尚未同步完成。 - 当镜像队列策略配置了
ha-sync-mode = manual
,即启用了强同步机制,则数据可以保持强一致性,但这会牺牲一定的性能和吞吐量。
RabbitMQ 仲裁队列
Quorum Queue(仲裁队列)是 RabbitMQ 官方推荐的新一代高可用队列,支持自动 Leader、日志复制、更可靠。Quorum 队列会在多个节点中选举一个 Leader,其他节点同步消息。Consumer 可以连接任意节点,性能、容错性都比 RabbitMQ 集群的普通模式好很多。