本文主要讲解以下内容
- 集群搭建
- 单节点故障恢复
- 集群迁移
- 集群监控
本文主要从运维的角度来保证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。
创建节点互联基础
代码语言:javascript复制# 创建私有网络docker newtwork create rabbitmqnet
创建节点
先创建一个目录,用来备份容器中的数据,这样以后删除容器时,使用新的容器也可以将旧容器的数据加载。注意这个目录的权限一定要给够。
代码语言:javascript复制mkdir /usr/local/docker_app
运行三个RabbitMQ服务容器
代码语言:javascript复制# 创建第一节点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)的集群
代码语言:javascript复制# 进入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秒以等待最后关闭的节点启动。
代码语言:javascript复制# 修改节点类型# type有两个值:disc、ramrabbitmqctl change_cluster_node_type {type}# 剔除单个节点# 在关闭集群中的每个节点之后,如果最后一个关闭的节点最终由于某些异常而无法启动,# 则可以通过rabbitmqctl forget_cluster_node命令来将此节点剔除出当前集群。# 进入节点2容器# 停止节点3容器,并且将节点3剔除到集群docker exec -it rabbit002 bashrabbitmqctl -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文件的格式。
代码语言:javascript复制{ "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来实现