本文主要讲解以下内容
集群搭建
单节点故障恢复
集群迁移
集群监控
本文主要从运维的角度来保证RabbitMQ服务的高可用,避免单点故障问题。
集群搭建
首先一个点,明白为什么要搭建集群。搭建集群的目的主要是为了避免单点故障,提高系统的高可用和性能的线性扩展。
节点类型
RabbitMQ集群中有两种节点类型,一种disk(磁盘)节点,一种ram(内存)节点。内存节点将队列、交换器、绑定关系、权限和vhost的元数据定义都存储在内存中,磁盘节点将这些信息存储在磁盘上。
RabbitMQ在创建队列、交换器、和绑定关系的时候,需要等集群所有的节点都成功提交元数据变更后才返回。内存节点可以为集群提供出色的性能,因为写入内存比写入磁盘快的不是一点半点,磁盘节点为集群提供了高可靠性。
RabbitMQ要求集群中至少有一个磁盘节点,其他节点都可以是内存节点。假设磁盘节点故障了,此时只能发送和消费消息,不能进行队列创建、交换器创建、绑定关系、用户,以及更改权限、添加和删除集群节点的操作。所以在建立集群的时候尽量保证多个磁盘节点的存在,其实在队列、交换器、绑定关系变化较小的RabbitMQ集群中,可以考虑将所有节点设置为磁盘节点。
集群搭建
集群搭建的方式主要有多机多节点和单机多节点,单机多节点可以用在测试环境中,不太适合生产。这里就着重讲多机多节点部署RabbitMQ集群。
RabbitMQ每一个节点都会存储队列、交换器等的元数据信息,但是消息的内容只会落到一个节点,假设其中一个节点宕机,该节点上的消息将会全部丢失,所以RabbitMQ集群并不能完全保证消息万无一失。
RabbitMQ节点备份所有的元数据信息主要包括以下内容:
队列元数据:名称和属性
交换器:名称及属性
绑定关系元数据:交换器与队列或者交换器与交换器之间的绑定关系
vhost元数据:为vhost内的队列、交换器和绑定提供命名空间及安全属性
之所以不会备份消息是基于空间和性能的考虑,RabbitMQ集群创建队列的时候,只会在单个节点创建队列的进程并包含完整的队列信息,其他节点只知道队列的元数据和指向该队列存在的那个节点的指针。
因此当节点崩溃时,该节点上的队列进程和关联的绑定都会消失,队列上的消费者会丢失消息,且任何匹配该队列的消息也会丢失。
交换器不同于队列,只是一个名称和绑定列表。当创建一个新的交换器时,RabbitMQ只需要将绑定列表添加到集群中的所有节点。
多机多节点部署
启动三个RabbitMQ容器,rabbit001、rabbit002、rabbit003均为磁盘节点。
我们采用容器部署集群,首要问题是节点互联,节点互联方式有两种:
私有DNS
Docker Networking:使用Docker network创建私有网络,连接到网络内的节点可以互联
我们采用第二种方式解决集群单机多容器节点的互联关系,多机的话大家可以自己去研究一下overlay。
创建节点互联基础
# 创建私有网络
docker newtwork create rabbitmqnet
创建节点
先创建一个目录,用来备份容器中的数据,这样以后删除容器时,使用新的容器也可以将旧容器的数据加载。注意这个目录的权限一定要给够。
mkdir /usr/local/docker_app
运行三个RabbitMQ服务容器
# 创建第一节点
docker run -d \
--name=rabbit001 \
-p 5672:5672 \
-p 15672:15672 \
-e RABBITMQ_NODENAME=rabbit001 \
-e RABBITMQ_ERLANG_COOKIE='rabbitmqcookie' \
-v /usr/local/docker_app/rabbitmq/rabbit001/data:/var/lib/rabbitmq \
-h rabbit001 \
--net rabbitmqnet \
rabbitmq:management
# 创建第二个节点
docker run -d \
--name=rabbit002 \
-p 5673:5672 \
-p 15673:15672 \
-e RABBITMQ_NODENAME=rabbit002 \
-e RABBITMQ_ERLANG_COOKIE='rabbitmqcookie' \
-v /usr/local/docker_app/rabbitmq/rabbit002/data:/var/lib/rabbitmq \
-h rabbit002 \
--net rabbitmqnet \
rabbitmq:management
# 创建第三个节点
docker run -d \
--name=rabbit003 \
-p 5674:5672 \
-p 15674:15672 \
-e RABBITMQ_NODENAME=rabbit003 \
-e RABBITMQ_ERLANG_COOKIE='rabbitmqcookie' \
-v /usr/local/docker_app/rabbitmq/rabbit003/data:/var/lib/rabbitmq \
-h rabbit003 \
--net rabbitmqnet \
rabbitmq:management
将节点2(rabbit002)和节点3(rabbit003)加入到节点1(rabbit001)的集群
# 进入rabbit002容器
# 停止RabbitMQ服务
# 重置RabbitMQ服务
# 将其加入rabbit001的集群
# 重启RabbitMQ服务
docker exec rabbit002 bash -c \
"rabbitmqctl stop_app && \
rabbitmqctl reset && \
rabbitmqctl join_cluster rabbit001@rabbit001 && \
rabbitmqctl start_app"
# 进入rabbit003容器
# 停止RabbitMQ服务
# 重置RabbitMQ服务
# 将其加入rabbit001的集群
# 重启RabbitMQ服务
docker exec rabbit003 bash -c \
"rabbitmqctl stop_app && \
rabbitmqctl reset && \
rabbitmqctl join_cluster rabbit001@rabbit001 && \
rabbitmqctl start_app"
# 查看集群状态
rabbitmqctl cluster_status
查看集群状态会返回下图信息
如果关闭了集群中的所有节点,则需要确保在启动的时候最后关闭的那个节点是第一个启动的。如果第一个启动的不是最后关闭的节点,那么这个节点会等待最后关闭的节点启动。
这个等待时间是30秒,如果没有等到,那么这个先启动的节点也会失败。在最新的版本中会有重试机制,默认重试10次30秒以等待最后关闭的节点启动。
# 修改节点类型
# type有两个值:disc、ram
rabbitmqctl change_cluster_node_type {type}
# 剔除单个节点
# 在关闭集群中的每个节点之后,如果最后一个关闭的节点最终由于某些异常而无法启动,
# 则可以通过rabbitmqctl forget_cluster_node命令来将此节点剔除出当前集群。
# 进入节点2容器
# 停止节点3容器,并且将节点3剔除到集群
docker exec -it rabbit002 bash
rabbitmqctl -n rabbit003@rabbit003 stop_app && rabbitmqctl forget_cluster_node rabbit003@rabbit003
集群节点升级
集群节点升级的过程中主要要确保一个点:确保原节点的Mnesia中的数据不被变更,且新节点中的Mnesia路径的指向要与原节点中的相同。RabbitMQ集群由多个节点组成。具体步骤:
关闭所有节点的服务,注意采用rabbitmqctl stop命令关闭。
保存各个节点的Mnesia数据(上面容器启动时使用了-v参数,将容器内的目录绑定到了宿主机下面)。
使用新版本的RabbitMQ镜像创建容器,保证-v的路径不变(这边是为了保证Mensia路径不变)。
启动新版本的容器,注意先重启原版本中最后关闭的那个节点 。
集群迁移
RabbitMQ集群迁移包括元数据重建、数据迁移,以及与客户端连接的切换。
元数据重建
可以通过将元数据文件导出为json,然后再导入到新集群中去,这种只适合可以访问web界面的情况。而且新旧版本如果不兼容,就会导入失败。
下面是json文件的格式。
{
"rabbit_version":"3.7.12",
"users":[
{
"name":"guest",
"password_hash":"BdRgHh8vezii4gceD0kBIAF+qcN4ROqEBRjES0SNxXytw5FW",
"hashing_algorithm":"rabbit_password_hashing_sha256",
"tags":"administrator"
}
],
"vhosts":[
{
"name":"/"
}
],
"permissions":[
{
"user":"guest",
"vhost":"/",
"configure":".*",
"write":".*",
"read":".*"
}
],
"topic_permissions":[
],
"parameters":[
],
"global_parameters":[
{
"name":"cluster_name",
"value":"rabbit001@rabbit001"
}
],
"policies":[
],
"queues":[
],
"exchanges":[
],
"bindings":[
]
}
这种元数据重建方式主要有以下几个问题:
必须打开web管理插件的支持
可能出现新老版本不兼容问题
队列在新集群中会全部在一个节点重建,造成负载不均。
更加晚上的方式元数据重建方式是通过HTTP API接口创建相应的数据。
数据迁移和客户端连接的切换
完成元数据的重建,下面就是数据迁移和客户端连接切换工作。
生产者只需要断开新客户端的连接,然后连接新的RabbitMQ集群即可。
消费者则需要考虑消费消息的情况,一种是等原来队列的消息全部消费完,然后切换新集群。还有一种情况就是原集群不可用需要立即切换到新集群,而此时的就需要处理原集群中的消息。
这时的处理办法是通过一个应用程序将消息取出来进行缓存,然后另一个程序取缓存中的消息发送到新集群中的队列中去。RabbitMQ本身提供的Federation和Shovel插件都可以实现此功能。
集群监控
主要通过HTTP API来实现




