暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

RabbitMQ集群

Different Java 2021-10-18
335

本文主要讲解以下内容

  1. 集群搭建

  2. 单节点故障恢复

  3. 集群迁移

  4. 集群监控

本文主要从运维的角度来保证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。

创建节点互联基础
  1. # 创建私有网络

  2. docker newtwork create rabbitmqnet

创建节点

先创建一个目录,用来备份容器中的数据,这样以后删除容器时,使用新的容器也可以将旧容器的数据加载。注意这个目录的权限一定要给够。

  1. mkdir /usr/local/docker_app

运行三个RabbitMQ服务容器

  1. # 创建第一节点

  2. docker run -d \

  3. --name=rabbit001 \

  4. -p 5672:5672 \

  5. -p 15672:15672 \

  6. -e RABBITMQ_NODENAME=rabbit001 \

  7. -e RABBITMQ_ERLANG_COOKIE='rabbitmqcookie' \

  8. -v /usr/local/docker_app/rabbitmq/rabbit001/data:/var/lib/rabbitmq \

  9. -h rabbit001 \

  10. --net rabbitmqnet \

  11. rabbitmq:management

  12. # 创建第二个节点

  13. docker run -d \

  14. --name=rabbit002 \

  15. -p 5673:5672 \

  16. -p 15673:15672 \

  17. -e RABBITMQ_NODENAME=rabbit002 \

  18. -e RABBITMQ_ERLANG_COOKIE='rabbitmqcookie' \

  19. -v /usr/local/docker_app/rabbitmq/rabbit002/data:/var/lib/rabbitmq \

  20. -h rabbit002 \

  21. --net rabbitmqnet \

  22. rabbitmq:management

  23. # 创建第三个节点

  24. docker run -d \

  25. --name=rabbit003 \

  26. -p 5674:5672 \

  27. -p 15674:15672 \

  28. -e RABBITMQ_NODENAME=rabbit003 \

  29. -e RABBITMQ_ERLANG_COOKIE='rabbitmqcookie' \

  30. -v /usr/local/docker_app/rabbitmq/rabbit003/data:/var/lib/rabbitmq \

  31. -h rabbit003 \

  32. --net rabbitmqnet \

  33. rabbitmq:management

将节点2(rabbit002)和节点3(rabbit003)加入到节点1(rabbit001)的集群

  1. # 进入rabbit002容器

  2. # 停止RabbitMQ服务

  3. # 重置RabbitMQ服务

  4. # 将其加入rabbit001的集群

  5. # 重启RabbitMQ服务

  6. docker exec rabbit002 bash -c \

  7. "rabbitmqctl stop_app && \

  8. rabbitmqctl reset && \

  9. rabbitmqctl join_cluster rabbit001@rabbit001 && \

  10. rabbitmqctl start_app"

  11. # 进入rabbit003容器

  12. # 停止RabbitMQ服务

  13. # 重置RabbitMQ服务

  14. # 将其加入rabbit001的集群

  15. # 重启RabbitMQ服务

  16. docker exec rabbit003 bash -c \

  17. "rabbitmqctl stop_app && \

  18. rabbitmqctl reset && \

  19. rabbitmqctl join_cluster rabbit001@rabbit001 && \

  20. rabbitmqctl start_app"

  21. # 查看集群状态

  22. rabbitmqctl cluster_status

查看集群状态会返回下图信息

如果关闭了集群中的所有节点,则需要确保在启动的时候最后关闭的那个节点是第一个启动的。如果第一个启动的不是最后关闭的节点,那么这个节点会等待最后关闭的节点启动。

这个等待时间是30秒,如果没有等到,那么这个先启动的节点也会失败。在最新的版本中会有重试机制,默认重试10次30秒以等待最后关闭的节点启动。

  1. # 修改节点类型

  2. # type有两个值:disc、ram

  3. rabbitmqctl change_cluster_node_type {type}

  4. # 剔除单个节点

  5. # 在关闭集群中的每个节点之后,如果最后一个关闭的节点最终由于某些异常而无法启动,

  6. # 则可以通过rabbitmqctl forget_cluster_node命令来将此节点剔除出当前集群。

  7. # 进入节点2容器

  8. # 停止节点3容器,并且将节点3剔除到集群

  9. docker exec -it rabbit002 bash

  10. rabbitmqctl -n rabbit003@rabbit003 stop_app && rabbitmqctl forget_cluster_node rabbit003@rabbit003

集群节点升级

集群节点升级的过程中主要要确保一个点:确保原节点的Mnesia中的数据不被变更,且新节点中的Mnesia路径的指向要与原节点中的相同。RabbitMQ集群由多个节点组成。具体步骤:

  1. 关闭所有节点的服务,注意采用rabbitmqctl stop命令关闭。

  2. 保存各个节点的Mnesia数据(上面容器启动时使用了-v参数,将容器内的目录绑定到了宿主机下面)。

  3. 使用新版本的RabbitMQ镜像创建容器,保证-v的路径不变(这边是为了保证Mensia路径不变)。

  4. 启动新版本的容器,注意先重启原版本中最后关闭的那个节点 。

集群迁移

RabbitMQ集群迁移包括元数据重建、数据迁移,以及与客户端连接的切换。

元数据重建

可以通过将元数据文件导出为json,然后再导入到新集群中去,这种只适合可以访问web界面的情况。而且新旧版本如果不兼容,就会导入失败。下面是json文件的格式。

  1. {

  2. "rabbit_version":"3.7.12",

  3. "users":[

  4. {

  5. "name":"guest",

  6. "password_hash":"BdRgHh8vezii4gceD0kBIAF+qcN4ROqEBRjES0SNxXytw5FW",

  7. "hashing_algorithm":"rabbit_password_hashing_sha256",

  8. "tags":"administrator"

  9. }

  10. ],

  11. "vhosts":[

  12. {

  13. "name":"/"

  14. }

  15. ],

  16. "permissions":[

  17. {

  18. "user":"guest",

  19. "vhost":"/",

  20. "configure":".*",

  21. "write":".*",

  22. "read":".*"

  23. }

  24. ],

  25. "topic_permissions":[

  26. ],

  27. "parameters":[

  28. ],

  29. "global_parameters":[

  30. {

  31. "name":"cluster_name",

  32. "value":"rabbit001@rabbit001"

  33. }

  34. ],

  35. "policies":[

  36. ],

  37. "queues":[

  38. ],

  39. "exchanges":[

  40. ],

  41. "bindings":[

  42. ]

  43. }

这种元数据重建方式主要有以下几个问题:

  • 必须打开web管理插件的支持

  • 可能出现新老版本不兼容问题

  • 队列在新集群中会全部在一个节点重建,造成负载不均。

更加晚上的方式元数据重建方式是通过HTTP API接口创建相应的数据。

数据迁移和客户端连接的切换

完成元数据的重建,下面就是数据迁移和客户端连接切换工作。

生产者只需要断开新客户端的连接,然后连接新的RabbitMQ集群即可。

消费者则需要考虑消费消息的情况,一种是等原来队列的消息全部消费完,然后切换新集群。还有一种情况就是原集群不可用需要立即切换到新集群,而此时的就需要处理原集群中的消息。

这时的处理办法是通过一个应用程序将消息取出来进行缓存,然后另一个程序取缓存中的消息发送到新集群中的队列中去。RabbitMQ本身提供的Federation和Shovel插件都可以实现此功能。

集群监控

主要通过HTTP API来实现


文章转载自Different Java,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论