RabbitMQ 教程

镜像队列

如果 RabbitMQ 集群中只有一个 Broker 节点,那么该节点的失效将导致整体服务的临时性不可用,并且也可能会导致消息的丢失。

当然,我们可以将所有消息都设置为持久化,并且对应队列的 durable 属性也设置为 true,以保证消息进行持久化,消息不丢失。

但是这样做仍然无法避免由于缓存导致的问题(因为消息在发送之后和被写入磁盘并执行刷盘动作之间存在一个短暂却会产生问题的时间窗)。然而,通过 Publisher Confirm 机制能够确保客户端知道哪些消息己经存入磁盘。尽管如此,一般也不希望遇到因单点故障而导致服务不可用。

如果 RabbitMQ 集群是由多个 Broker 节点组成的,那么从服务的整体可用性上来讲,该集群对于单点故障是有弹性的。由于在集群其他节点中只保存了交换器、队列、绑定关系元信息,队列中的消息是保存在本地。如果节点失效,依然会导致消息丢失,服务不可用问题。

RabbitMQ 引入镜像队列(Mirror Queue)机制,可以将队列镜像到集群中的其他 Broker 节点之上。如果集群中的一个节点失效了,队列能自动地切换到镜像中的另一个节点上以保证服务的可用性。在通常的用法中,针对每一个配置镜像的队列(以下简称镜像队列)都包含一个主节点(master)和若干个从节点(slave),相应的结构如下图:

slave 会准确地按照 master 执行命令的顺序进行动作,故 slave 与 master 上维护的状态应该是相同的。如果 master 由于某种原因失效,那么 “资历最老” 的 slave 会被提升为新的 master。根据 slave 加入的时间排序,时间最长的 slave 即为 “资历最老”。发送到镜像队列的所有消息会被同时发往 master 和所有的 slave 上,如果此时 master 挂掉了,消息还会在 slave 上,这样 slave 提升为 master 的时候消息也不会丢失。

注意:除发送消息(Basic.Publish)外的所有动作都只会向 master 发送,然后再由 master 将命令执行的结果广播给各个 slave。

如果消费者与 slave 建立连接并进行订阅消费,其实质上都是从 master 上获取消息,只不过看似是从 slave 上消费而己。比如消费者与 slave 建立了 TCP 连接之后执行一个 Basic.Get 的操作,那么首先是由 slave Basic.Get 请求发往 master,再由 master 准备好数据返回给 slave ,最后由 slave 投递给消费者。读者可能会有疑问,大多的读写压力都落到了 master 上,那么这样是否负载会做不到有效的均衡?或者说是否可以像 MySQL 一样能够实现 master 写而 slave 读呢?注意这里的 master slave 是针对队列而言的,而队列可以均匀地散落在集群的各个 Broker 节点中以达到负载均衡的目的,因为真正的负载还是针对实际的物理机器而言的,而不是内存中驻留的队列进程。如下图:

上图中,集群中的每个 Broker 点都包含三个队列,1 个队列的 master,2 个队列的 slave。其中,queue1 的负载大多都集中在 brokerl 上,queue2 的负载大多都集中在 broker3 上,queue3 的负载大多都集中在 broker2 上,只要确保队列的 master 节点均匀散落在集群中的各个 Broker 节点即可确保很大程度上的负载均衡。

注意:RabbitMQ 的镜像队列同时支持 Publisher Confirm 和事务两种机制。在事务机制中,只有当前事务在全部镜像中执行之后,客户端才会收到 Tx Commit-Ok 的消息。同样的,在 Publisher Confirm 机制中,生产者进行当前消息确认的前提是该消息被全部进行所接收了。

配置镜像队列

镜像队列是在普通集群搭建后,通过设置同步策略(Policy)来实现的。因此,我们先搭建一个简单的 RabbitMQ 集群,该集群中拥有三个 broker,分别如下:

  • broker1  192.168.116.51

  • broker2  192.168.116.52

  • broker3  192.168.116.53

怎样搭建 RabbitMQ 集群,请参考 搭建 RabbitMQ 集群。搭建好的集群信息如下图:

接下来继续配置镜像队列,完整配置步骤如下:

(1)使用浏览器访问 http://192.168.116.51:15672/#/policies  地址(即点击“Admin” > “Policies”),点击“Add / update a policy”按钮去添加一个用来同步镜像队列的策略。如下图:

上图中,创建了一个名为 ha-queue-demo 的策略,其中参数如下:

  • Name:policy 的名称

  • Appliy to:指定该策略用于交换器还是队列,或是两者

  • Pattern:一个用来匹配队列或交换器的匹配模式(正则表达式)

  • priority:可选参数,指定策略的优先级

  • Definition:镜像定义,包括三个部分 ha-mode, ha-params, ha-sync-mode

    • ha-mode:指明镜像队列的模式,有效值为 all/exactly/nodes

      all:表示在集群中所有的节点上进行镜像

      exactly:表示在指定个数的节点上进行镜像,节点的个数由 ha-params 指定

      nodes:表示在指定的节点上进行镜像,节点名称通过 ha-params 指定

    • ha-params:设置镜像队列的参数,根据 ha-mode 的取值,该 ha-params 的设置值有所不同。如果 ha-mode 为 all,则不使用该参数;如果 ha-mode 为 exactly,则为数字;如果 ha-mode 为 nodes,则为字符串列表。

    • ha-sync-mode:进行队列中消息的同步方式,有效值为 automatic(自动方式)和 manual(手动方式)。

slave 升级为 master

镜像队列 master 出现故障时,最老的 slave 会被提升为新的 master。如果新提升为 master 的这个副本与原有的 master 并未完成数据的同步,那么就会出现数据的丢失,而实际应用中,出现数据丢失可能会导致出现严重后果。

RabbitMQ 提供了两个参数让用户决策是保证队列的可用性,还是保证队列的一致性;两个参数分别控制正常关闭、异常故障情况下 slave 是否提升为 master,其可设置的值为 when-synced 和 always。

  • when-synced:从节点与主节点完成数据同步,才会被提升为主节点

  • always:无论什么情况下从节点都将被提升为主节点

(2)使用浏览器访问 http://192.168.116.51:15672/#/queues  地址(即 Queues 页面),点击“Add a new queue”按钮去添加一个队列。如下图:

上图中,创建了一个名为 ha-queue-demo 的可持久化的队列。

(3)分别进入 broker2 和 broker3 可以看到上面创建的 ha-queue-demo 队列已经自动进行了同步。broker2 如下图:

broker3 如下图:

(4)进入 broker1,访问 http://192.168.116.51:15672/#/queues/%2F/ha-queue-demo 地址(选择上面在 broker1 上面创建的 ha-queue-demo 队列),点击“Publish message”按钮,发送一个消息。如下图:

上图中,向队列“ha-queue-demo”发送一条内容为“hello world HA-MESSAGE”的消息。

(5)手动将 broker1 和 broker2 服务停止(笔者这里采用的是虚拟机,直接将虚拟机关了),集群节点状态如下图:

从上图可知,broker1(rabbit@node1)和 broker2(rabbit@node2)均已停止工作。

继续访问 http://192.168.116.53:15672/#/queues/%2F/ha-queue-demo  地址(即选择镜像队列 ha-queue-demo),点击“Get Message”按钮去消费一个消息,如下图:

从上图可知,成功消费到了消息。这也说明,消息的确被镜像保存到 broker3 中。

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号