RabbitMQ
基于KeepAlived 搭建 RabbitMQ 高可用集群
PerfTest:RabbitMQ性能压测工具
本文档使用 MrDoc 发布
-
+
home page
基于KeepAlived 搭建 RabbitMQ 高可用集群
# 一、集群简介 ## 1.1 集群架构 当单台 RabbitMQ 服务器的处理消息的能力达到瓶颈时,此时可以通过 RabbitMQ 集群来进行扩展,从而达到提升吞吐量的目的。RabbitMQ 集群是一个或多个节点的逻辑分组,集群中的每个节点都是对等的,每个节点共享所有的用户,虚拟主机,队列,交换器,绑定关系,运行时参数和其他分布式状态等信息。一个高可用,负载均衡的 RabbitMQ 集群架构应类似下图 ![](/media/202404/2024-04-08_190450_1876670.7612207565467591.png) 这里对上面的集群架构做一下解释说明: 首先一个基本的 RabbitMQ 集群不是高可用的,虽然集群共享队列,但在默认情况下,消息只会被路由到某一个节点的符合条件的队列上,并不会同步到其他节点的相同队列上。假设消息路由到 node1 的 my-queue 队列上,但是 node1 突然宕机了,那么消息就会丢失,想要解决这个问题,需要开启队列镜像,将集群中的队列彼此之间进行镜像,此时消息就会被拷贝到处于同一个镜像分组中的所有队列上。 其次 RabbitMQ 集群本身并没有提供负载均衡的功能,也就是说对于一个三节点的集群,每个节点的负载可能都是不相同的,想要解决这个问题可以通过硬件负载均衡或者软件负载均衡的方式,这里我们选择使用 HAProxy 来进行负载均衡,当然也可以使用其他负载均衡中间件,如 LVS 等。HAProxy 同时支持四层和七层负载均衡,并基于单一进程的事件驱动模型,因此它可以支持非常高的井发连接数。 接着假设我们只采用一台 HAProxy,那么它就存在明显的单点故障的问题,所以至少需要两台 HAProxy ,同时这两台 HAProxy 之间需要能够自动进行故障转移,通常的解决方案就是 KeepAlived 。KeepAlived 采用 VRRP (Virtual Router Redundancy Protocol,虚拟路由冗余协议) 来解决单点失效的问题,它通常由一主一备两个节点组成,同一时间内只有主节点会提供对外服务,并同时提供一个虚拟的 IP 地址 (Virtual Internet Protocol Address ,简称 VIP) 。 如果主节点故障,那么备份节点会自动接管 VIP 并成为新的主节点 ,直到原有的主节点恢复。 ## 1.2 集群元数据的同步 RabbitMQ 集群会始终同步四种类型的内部元数据: - 队列元数据:队列名称和它的属性 - 交换器元数据:交换器名称、类型和属性 - 绑定元数据:一张简单的表格展示了如何将消息路由到队列 - vhost 元数据:为 vhost 内的队列、交换器和绑定提供命名空间和安全属性 因此,当用户访问其中任何一个 RabbitMQ 节点时,通过 rabbitmqctl 查询到的 queue/user/exchange/vhost 等信息都是相同的。 >说明:**RabbitMQ集群仅采用元数据同步的原因** > > 1. 存储空间。如果每个集群节点都拥有所有 Queue 的完全数据拷贝,那么每个节点的存储空间会非常大,集群的消息积压能力会非常弱(无法通过集群节点的扩容提高消息积压能力); > 2. 性能。消息的发布者需要将消息复制到每一个集群节点,对于持久化消息,网络和磁盘同步复制的开销都会明显增加。 ## 1.3 集群发送/订阅的基本原理 RabbitMQ 集群的工作原理图如下: ![](/media/202404/2024-04-09_150524_5724230.8285701118157149.png) **情况一、 客户端直接连接队列所在节点** 如果有一个消息生产者或者消息消费者通过 client 的客户端连接至节点 1 进行消息的发布或者订阅,那么此时的集群中的消息收发只与节点 1 相关。 **情况二、客户端连接的是非队列数据所在节点** 如果消息生产者所连接的是节点 2 或者节点 3,此时队列 1 的完整数据不在该两个节点上,那么在发送消息过程中这两个节点主要起了一个路由转发作用,根据这两个节点上的元数据转发至节点 1 上,最终发送的消息还是会存储至节点 1 的队列 1 上。同样,如果消息消费者所连接的节点 2 或者节点 3,那这两个节点也会作为路由节点起到转发作用,将会从节点 1 的队列 1 中拉取消息进行消费。 ## 1.4 集群节点的类型说明 1. 磁盘节点 将配置信息和元信息存储在磁盘上(单节点系统必须是磁盘节点,否则每次重启 RabbitMQ 之后所有的系统配置信息都会丢失)。 2. 内存节点 将配置信息和元信息存储在内存中。性能是优于磁盘节点的。 RabbitMQ 要求集群中至少有一个磁盘节点,当节点加入和离开集群时,必须通知磁盘节点(如果集群中唯一的磁盘节点崩溃了,则不能进行创建队列、创建交换器、创建绑定、添加用户、更改权限、添加和删除集群节点)。总之如果唯一磁盘的磁盘节点崩溃,集群是可以保持运行的,但不能更改任何东西。因此建议在集群中设置两个磁盘节点,只要一个可以,就能正常操作。 ## 1.5 RabbitMQ安装与规划 ### 1.5.1 基于Linux安装RabbitMQ 设置 linux 机器主机名,如下三行命令需要分别在各自的主机上执行单独的命令 ```bash hostnamectl set-hostname mqnode1 # 主机1 hostnamectl set-hostname mqnode2 # 主机2 hostnamectl set-hostname mqnode3 # 主机3 ``` 配置每台服务器上ip与主机名称的映射关系,如下三行命令都需在每个主机上执行 ```bash echo "192.168.3.215 mqnode1" >> /etc/hosts echo "192.168.3.216 mqnode2" >> /etc/hosts echo "192.168.3.217 mqnode3" >> /etc/hosts ``` ### 1.5.2 安装 Erlang ```bash #第一步 curl -s https://packagecloud.io/install/repositories/rabbitmq/erlang/script.rpm.sh | sudo bash #第二步 安装erlang yum install erlang #第三步 查看erlang版本号,在命令行直接输入erl erl ``` ### 1.5.3 安装 RabbitMQ ```bash #第一步 先导入两个key rpm --import https://packagecloud.io/rabbitmq/rabbitmq-server/gpgkey rpm --import https://packagecloud.io/gpg.key #第二步 curl -s https://packagecloud.io/install/repositories/rabbitmq/rabbitmq-server/script.rpm.sh | sudo bash #第三步 wget https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.8.5/rabbitmq-server-3.8.5-1.el8.noarch.rpm #第四步 rpm --import https://www.rabbitmq.com/rabbitmq-release-signing-key.asc #第五步 yum -y install epel-release yum -y install socat #第六步 rpm -ivh rabbitmq-server-3.8.5-1.el8.noarch.rpm #第七步 启用管理平台插件,启用插件后,可以可视化管理RabbitMQ rabbitmq-plugins enable rabbitmq_management #第八步 启动应用 systemctl start rabbitmq-server ``` ### 1.5.4 设置访问权限 ```bash 复制成功 #创建管理员账户 rabbitmqctl add_user gerry gerry #设置注册的账户为管理员 rabbitmqctl set_user_tags gerry administrator #授权远程访问 rabbitmqctl set_permissions -p / gerry "." "." ".*" #重启服务 systemctl restart rabbitmq-server ``` ## 1.2 集群部署规划 下面我们开始进行搭建,这里我使用三台主机进行演示,主机名分别为 rabbit1、rabbit2 和 rabbit3 ,其功能分配如下: - **215 服务器** :部署 RabbitMQ - **216 服务器** :部署 RabbitMQ + KeepAlived - **217 服务器** :部署 RabbitMQ + KeepAlived 以上三台主机上我均已安装好了 RabbitMQ ## 1.3 自定义默认端口(可选) **1) 查找安装位置** 默认情况下RabbitMQ的安装位置是: `/usr/lib/rabbitmq` 也可执行命令查找安装位置: `whereis rabbitmq` ```bash [root@localhost]# whereis rabbitmq rabbitmq: /usr/lib/rabbitmq /etc/rabbitmq ``` **2) 新增配置文件** >注意⚠️: 可以是任意位置 本次示例配置文件位置为 /etc/rabbitmq/rabbitmq.conf ```bash [root@mqnode1 ~]# cat /etc/rabbitmq/rabbitmq.conf #默认端口为5672 listeners.tcp.default=5672 #界面管理端口(默认端口为15672) management.tcp.port=15672 [root@mqnode1 ~]# ``` **1) 添加配置文件路径** rabbitmq-defaults 在路径 `/usr/lib/rabbitmq/lib/rabbitmq_server-实际版本/sbin/` 下 在文件中新增配置行 `CONFIG_FILE=/etc/rabbitmq/rabbitmq.conf` ``` [root@mqnode1 ~]# cat /usr/lib/rabbitmq/lib/rabbitmq_server-3.8.5/sbin/rabbitmq-defaults #!/bin/sh -e ## The contents of this file are subject to the Mozilla Public License ## Version 1.1 (the "License"); you may not use this file except in ## compliance with the License. You may obtain a copy of the License ## at https://www.mozilla.org/MPL/ ## ## Software distributed under the License is distributed on an "AS IS" ## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See ## the License for the specific language governing rights and ## limitations under the License. ## ## The Original Code is RabbitMQ. ## ## The Initial Developer of the Original Code is GoPivotal, Inc. ## Copyright (c) 2012-2019 Pivotal Software, Inc. All rights reserved. ## ### next line potentially updated in package install steps SYS_PREFIX= CLEAN_BOOT_FILE=start_clean SASL_BOOT_FILE=start_sasl BOOT_MODULE="rabbit" if test -z "$CONF_ENV_FILE" && test -z "$RABBITMQ_CONF_ENV_FILE"; then CONF_ENV_FILE=${SYS_PREFIX}/etc/rabbitmq/rabbitmq-env.conf fi CONFIG_FILE=/etc/rabbitmq/rabbitmq.conf [root@mqnode1 ~]# ``` **4) 重启rabbitmq服务** 重启rabbitmq服务使其端口生效 ``` service rabbitmq-server restart ``` # 二、RabbitMQ 集群配置 普通集群模式是在多台机器上启动多个 RabbitMQ 实例,每个机器启动一个实例。创建的 queue 只会放在一个 RabbitMQ 实例上,但是每个实例都同步 queue 的元数据(元数据可以认为是 queue 的一些配置信息,通过元数据,可以找到 queue 所在实例)。消费端消费时,如果连接到了另外一个实例,那么该实例会从 queue 所在实例上拉取数据过来。 ![](/media/202404/2024-04-11_103016_8666470.4985793547052616.png) 首先先进行 RabbitMQ 集群的搭建,具体步骤如下: ## 2.1 停止服务 停止node2,node3上的服务 ```bash rabbitmqctl stop_app ``` ## 2.2 拷贝 cookie 将 node01 上的 `.erlang.cookie` 文件拷贝到其他两台主机上。该 cookie 文件相当于密钥令牌,集群中的 RabbitMQ 节点需要通过交换密钥令牌以获得相互认证,因此处于同一集群的所有节点需要具有相同的密钥令牌,否则在搭建过程中会出现 Authentication Fail 错误。 RabbitMQ 服务启动时,erlang VM 会自动创建该 cookie 文件,默认的存储路径为 `/var/lib/rabbitmq/.erlang.cookie` 或 `$HOME/.erlang.cookie`,该文件是一个隐藏文件,需要使用 ls -al 命令查看。这里我使用的是 root 账户,$HOME 目录就是 /root 目录,对应的拷贝命令如下: ```bash scp /var/lib/rabbitmq/.erlang.cookie root@node2:/var/lib/rabbitmq scp /var/lib/rabbitmq/.erlang.cookie root@node3:/var/lib/rabbitmq ``` 由于在三台主机上使用不同的账户进行操作,为避免后面出现权限不足的问题,这里建议将 cookie 文件原来的 400 权限改为 777,命令如下: ``` chmod 400 /root/.erlang.cookie ``` >注⚠️: >1. cookie 中的内容就是一行随机字符串,可以使用 cat 命令查看 >2. 也可在cat并复制原cookie内容后,用如下指令完成同步 > >`echo -n '原cookie内容' > /var/lib/rabbitmq/.erlang.cookie` ## 2.3 集群搭建 RabbitMQ 集群的搭建需要选择其中任意一个节点为基准,将其它节点逐步加入。这里我们以 node1 为基准节点,将 node2 和 node3 加入集群。 分别在 node2 和 node3 上执行以下命令: ```bash # 1.停止服务 rabbitmqctl stop_app # 2.重置状态(需要更改节点类型的时候执行,首次不需要执行,除非你节点是以disk加入集群的) rabbitmqctl reset # 3.节点加入 rabbitmqctl join_cluster --ram rabbit@k8s-01 # 4.启动服务 rabbitmqctl start_app ``` join_cluster 命令有一个可选的参数 `--ram` ,该参数代表新加入的节点是内存节点,默认是磁盘节点。 如果是内存节点,则所有的队列、交换器、绑定关系、用户、访问权限和 vhost 的元数据都将存储在内存中。 如果是磁盘节点,则存储在磁盘中。 内存节点可以有更高的性能,但其重启后所有配置信息都会丢失,因此 RabbitMQ 要求在集群中至少有一个磁盘节点,其他节点可以是内存节点。 当内存节点离开集群时,它可以将变更通知到至少一个磁盘节点;然后在其重启时,再连接到磁盘节点上获取元数据信息。除非是将 RabbitMQ 用于 RPC 这种需要超低延迟的场景,否则在大多数情况下,RabbitMQ 的性能都是够用的,可以采用默认的磁盘节点的形式。 >注意⚠️: > >如果节点以磁盘节点的形式加入,则需要先使用 reset 命令进行重置后,才能加入现有群集,重置节点会删除该节点上存在的所有的历史资源和数据。采用内存节点的形式加入时可以略过 reset 这一步,因为内存上的数据本身就不是持久化的。 ## 2.4 查看集群状态 **1) 命令行查看** 在 node2 和 node3 上执行以上命令后,集群就已经搭建成功,此时可以在任意节点上使用如下命令查看集群状态,输出如下: ```bash rabbitmqctl cluster_status ``` 默认的 cluster_name 名字为 **rabbit@主机名**,如果想进行修改,可以使用以下命令: ``` rabbitmqctl set_cluster_name MY_RABBITMQ_CLUSTER ``` **2) UI 界面查看** 除了可以使用命令行外,还可以使用打开任意节点的 UI 界面进行查看,情况如下: 默认访问地址为: http://localhost/:15672 账密为之前使用*rabbitmqctl add_user*命令创建的账号密码 ![](/media/202404/2024-04-09_143238_4453450.5926753613258288.png) ## 2.5 配置镜像模式 镜像模式是RabbitMQ 的高可用模式。跟普通集群模式不一样的是,在镜像集群模式下,无论元数据还是 queue 里的消息都会存在于多个实例上,换言之,每个 RabbitMQ 节点都有这个 queue 的一个完整镜像,包含 queue 的全部数据的。然后每次生产消息到 queue 的时候,都会自动把消息同步到多个实例的 queue 上。 ![](/media/202404/2024-04-11_102656_6234540.2986129623870942.png) 1. 开启镜像队列 为所有队列开启镜像配置,其语法如下: ```bash rabbitmqctl set_policy ha-all "^" '{"ha-mode":"all"}' ``` 2. 复制系数 在上面指定了 `ha-mode` 的值为 `all` ,代表消息会被同步到所有节点的相同队列中。这里之所以这样配置,因为示例本身只有三个节点,因此复制操作的性能开销比较小。 如果集群有很多节点,此时复制的性能开销就比较大,此时需要选择合适的复制系数。通常可以遵循过 **半写原则** 即对于一个节点数为 n 的集群,只需要同步到 `n/2+1` 个节点上即可。此时需要同时修改镜像策略为 exactly,并指定复制系数 ha-params,示例命令如下: ```bash rabbitmqctl set_policy ha-two "^" '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}' rabbitmqctl set_policy ha-all "^ha\." '{"ha-mode":"all"}' ``` 此时只会对 ha 开头的队列进行镜像。更多镜像队列的配置说明,可以参考官方文档:[Highly Available (Mirrored) Queues](https://www.rabbitmq.com/ha.html "Highly Available (Mirrored) Queues") 3. 查看镜像状态 配置完成后,可以通过 Web UI 界面查看任意队列的镜像状态,情况如下: ![](/media/202404/2024-04-09_153112_1811140.8550146746492595.png) ## 2.6 节点下线/缩容 以上介绍的集群搭建的过程就是服务扩容的过程,如果想要进行服务缩容,即想要把某个节点剔除集群,有两种可选方式: 第一种:可以先使用 rabbitmqctl stop 停止该节点上的服务,然后在其他任意一个节点上执行 forget_cluster_node 命令。这里以剔除 node3 上的服务为例,此时可以在 node1 或 node2 上执行下面的命令: ```bash rabbitmqctl forget_cluster_node rabbit@mqnode3 ``` 第二种:先使用 rabbitmqctl stop 停止该节点上的服务,然后再执行 rabbitmqctl reset 这会清空该节点上所有历史数据,并主动通知集群中其它节点它将要离开集群。 ## 2.7 集群的关闭与重启 没有一个直接的命令可以关闭整个集群,需要逐一进行关闭。但是需要保证在重启时,最后关闭的节点最先被启动。如果第一个启动的不是最后关闭的节点,那么这个节点会等待最后关闭的那个节点启动,默认进行 10 次连接尝试,超时时间为 30 秒,如果依然没有等到,则该节点启动失败。 这带来的一个问题是,假设在一个三节点的集群当中,关闭的顺序为 node1,node2,node3 如果 node1 因为故障暂时没法恢复,此时 node2 和 node3 就无法启动。想要解决这个问题,可以先将 node1 节点进行剔除,命令如下: ```bash rabbitmqctl forget_cluster_node rabbit@node1 -offline ``` 此时需要加上 -offline 参数,它允许节点在自身没有启动的情况下将其他节点剔除。 ## 2.8 修改节点数据的类型 ```bash rabbitmqctl stop_app rabbitmqctl reset # 如果是磁盘节点需要重置内容 rabbitmqctl join_cluster --ram rabbit@node1 rabbitmqctl start_app ``` 至此 RabbitMQ 的镜像部署均已完成,但目前非高可用方案,需再由keepalived配合完成高可用切换 # 三、keepalived实现高可用 kepalived 安装过程:略 keepalived高可用配置参考: [点击访问](/doc/400/ "点击访问") # 四、相关问题 **1. 报错:RabbitMQ Node rabbit@xxx thinks its clustered with node rabbit@xxx, but rabbit disagrees** >原因: 主机集群认为该节点仍在集群中, 而该节点实际上退出集群了。 导致数据文件日志不一致,而无法加入集群。 清理问题机的缓存 ``` rabbitmqctl stop_app rm -rf /var/lib/rabbitmq/mnesia #删除缓存 ``` 主节点中将该节点移除集群 ``` rabbitmqctl forget_cluster_node rabbit@问题机 ``` 问题机器重新加入集群 ``` # rabbitmqctl join_cluster --disc rabbit@问题机 rabbitmqctl join_cluster --ram rabbit@问题机 rabbitmqctl start_app ```
Nathan
April 11, 2024, 10:30 a.m.
转发文档
Collection documents
Last
Next
手机扫码
Copy link
手机扫一扫转发分享
Copy link
Markdown文件
PDF文件
Docx文件
share
link
type
password
Update password