单机问题就是不能高可用,吞吐量有瓶颈、存储有瓶颈。搭建集群才能解决这些
但是 RabbitMQ 集群不能保证消息的万无一失,当集群中一个 RabbitMQ 节点崩溃时,该节点上的所有队列中的消息也会丢失。RabbitMQ 集群中的所有节点都会备份所有的元数据信息,包括以下内容:

  • 队列元数据:队列名称和属性
  • 交换器元数据:交换器名称和属性
  • 绑定关系元数据:交换器与队列或交换器与交换器之间的绑定关系
  • vhost 元数据:为 vhost 内的队列、交换器和绑定提供命名空间及安全属性

但是不会备份消息(可以通过特殊的镜像队列解决这个问题)。
基于存储空间和性能的考虑, 在 RabbitMQ 集群中 创建队列,集群只会在 单个节点 上创建队列的进程并包含完整的队列信息(元数据、状态、内容),这样只有 队列的宿主节点 知道队列的所有信息,其他节点只知道队列的元数据指向该队列存在的那个节点的指针。因此当集群节点崩溃时,该节点的队列进程和关联的绑定都会消失。订阅这个队列的上的消费者也会丢失所订阅的信息,并且任何匹配该队列绑定信息的新消息也会消失。
交换器 其实只是一个名称和绑定列表,当消息发布到交换器时,实际上是由 所连接的信道消息上的路由键同交换器的绑定列表进行比较,然后再路由消息。当创建一个新的交换器时,RabbitMQ 要做的是 将绑定列表添加到集群中的所有节点上,这样,每个节点的每条信道都可以访问到新的交换器了

多机多节点配置

每台机器上部署一个 RabbitMQ,组成的 RabbitMQ 集群。
由于 RabbitMQ 集群对延迟非常敏感,需要在局域网中组成集群,广域网集群可以使用 Federation 或则 Shovel 来代替。

部署节规划:

名称 ip
node1 192.168.0.2
node2 192.168.0.3
node3 192.168.0.4

在三台机器上安装好 RabbitMQ。然后修改各个节点的 /etc/hosts 文件

  1. # 让机器之间通过 hostname 访问
  2. vim /etc/hosts
  3. 192.168.110.10 node1
  4. 192.168.110.12 node2
  5. 192.168.110.13 node3
  6. # 设置每台机器的 hostname,用 hostname 命令
  7. hostnamectl set-hostname node1
  8. # 配置每个节点上的 nodename,内容配置为上面设置的 hostname
  9. /opt/rabbitmq/etc/rabbitmq/rabbitmq-env.conf
  10. # 内容为对应的 hostname
  11. NODENAME=rabbit@node1
  12. # 修改完成之后,重启机器让 hostname 生效
  13. Copied!

编辑 RabbitMQ 的 cookie 文件,确保各个节点的 cookie 文件使用的是同一个值

  1. # cookie 文件默认路径在:/var/lib/rabbitmq/.erlang.cookie
  2. # 或则在: $HOME/.erlang.cookie
  3. [root@study ~]# vim /root/.erlang.cookie
  4. TGPVCOQIXBHWWDHUCJGP
  5. # 可以用其中的一台上面的 cookie 复制到其他节点上
  6. Copied!

cookie 相当于密匙令牌,所以要一致。

配置集群

可以通过:

  • rabbitmqctl :常用,本节讲解这种
  • rabbitmq.config 配置文件
  1. # 先启动三个节点的 RabbitMQ 服务
  2. rabbitmq-server -detached
  3. # 目前这三个节点都是独立的单节点集群
  4. # 可以查看他们的集群状态信息
  5. [root@study rabbitmq]# rabbitmqctl cluster_status
  6. Cluster status of node node1@node1
  7. [{nodes,[{disc,[node1@node1]}]},
  8. {running_nodes,[node1@node1]},
  9. {cluster_name,<<"node1@node1">>},
  10. {partitions,[]},
  11. {alarms,[{node1@node1,[]}]}]
  12. # 然后以 node1 为基准,将其他两个节点加入到 node1 节点的集群中
  13. # 加入要执行以下命令,在其他两个节点上执行
  14. # 0. 要先打开每台机器上的 4369 端口,加入集群需要访问这个端口
  15. firewall-cmd --zone=public --add-port=4369/tcp --permanent
  16. firewall-cmd --zone=public --add-port=25672/tcp --permanent
  17. firewall-cmd --reload
  18. # 1. 停止 rabbitmq 应用
  19. rabbitmqctl stop_app
  20. # 2. 重置
  21. rabbitmqctl reset
  22. # 3. 加入集群
  23. [root@node2 ~]# rabbitmqctl join_cluster rabbit@node1
  24. Clustering node rabbit@node2 with rabbit@node1
  25. # 3. 启动 rabbitmq 应用
  26. rabbitmqctl start_app
  27. # 再次查看集群状态
  28. [root@node1 ~]# rabbitmqctl cluster_status
  29. Cluster status of node rabbit@node1
  30. [{nodes,[{disc,[rabbit@node1,rabbit@node2,rabbit@node3]}]},
  31. {running_nodes,[rabbit@node2,rabbit@node3,rabbit@node1]},
  32. {cluster_name,<<"rabbit@node1">>},
  33. {partitions,[]},
  34. {alarms,[{rabbit@node2,[]},{rabbit@node3,[]},{rabbit@node1,[]}]}]
  35. # 发现已经加入进来了
  36. Copied!

关闭集群

  1. # 关闭 node2 节点的应用
  2. [root@node2 ~]# rabbitmqctl stop_app
  3. Stopping rabbit application on node rabbit@node2
  4. # 然后查看集群状态
  5. # 会看到 running_nodes 中的确少了一个
  6. [root@node1 ~]# rabbitmqctl cluster_status
  7. Cluster status of node rabbit@node1
  8. [{nodes,[{disc,[rabbit@node1,rabbit@node2,rabbit@node3]}]},
  9. {running_nodes,[rabbit@node3,rabbit@node1]},
  10. {cluster_name,<<"rabbit@node1">>},
  11. {partitions,[]},
  12. {alarms,[{rabbit@node3,[]},{rabbit@node1,[]}]}]
  13. Copied!

如果关闭了集群中的所有节点,则需要确保 最后关闭的那个节点是第一个启动 的,如果不是最后关闭的节点被第一个启动,该节点会默认等待 30 秒(新版有重试机制,时间另算),等待最后一个节点的启动,等待不到,则自己启动失败。
如果因为最后一个节点启动不了,可以参考前一章节的集群管理命令,将这个节点踢出去,选择其他的节点启动。(也可以本章下一小节的内容)

集群节点类型

查看集群状态信息时,会看到 {nodes,[{disc,[rabbit@node1,rabbit@node2,rabbit@node3]} 的信息。其中 disc 就是 RabbitMQ 节点的类型。有两种类型:

  • disc:磁盘节点元数据保存在磁盘上
  • ram:内存节点将所有的 队列、交换器、绑定关系、用户、权限和 vhost 的 元数据 都存储在内存中。

单节点的集群中,只能有磁盘类型的节点,否则重启 RabbitMQ 后,所有系统配置信息都会丢失。在集群节点中,可以选择配置部分节点为内存节点,可以获得更好的信息。
比如将 node2 加入节点时指定为内存节点

  1. # 加入节点时指定 --ram 参数
  2. [root@node2 ~]# rabbitmqctl join_cluster rabbit@node1 --ram
  3. Clustering node rabbit@node2 with rabbit@node1
  4. # 如果已经加入了集群,则可以更改节点类型
  5. rabbitmqctl change_cluster_node_type {disc,ram}
  6. [root@node1 ~]# rabbitmqctl cluster_status
  7. Cluster status of node rabbit@node1
  8. [{nodes,[{disc,[rabbit@node1,rabbit@node3]},{ram,[rabbit@node2]}]},
  9. {running_nodes,[rabbit@node3,rabbit@node1]},
  10. {cluster_name,<<"rabbit@node1">>},
  11. {partitions,[]},
  12. {alarms,[{rabbit@node3,[]},{rabbit@node1,[]}]}]
  13. Copied!

在集群中创建队列、交换器或则绑定关系时,这些操作直到 所有集群节点都成功提交元数据变更后才会返回,这就意味着磁盘节点会耗费更多的时间,而内存节点将耗费更少的时间。

  • 内存节点:提供出色的性能
  • 磁盘节点:能保证集群配置信息的高可靠性

RabbitMQ· 只要求在 集群中至少有一个磁盘节点,当节点加入或则离开集群时,他们必须将变更通知到至少一个磁盘节点。如果 只有一个磁盘节点,该节点 崩溃 的话,那么 将不能执行创建队列、交换器、绑定关系、用户、更改权限、添加或删除集群节点的操作了,但是可以继续收发信息。
内存节点重启后,会连接到预先配置的磁盘节点,下载当前集群元数据的副本。当在集群中添加内存节点时,确保告知其所有的磁盘节点(内存节点唯一存储到磁盘的元数据信息是集群中磁盘节点的地址),只要内存节点可以找到至少一个磁盘节点,那么它在重启后,就能重新加入集群

如何选择磁盘节点类型?

如上所述,再看场景:只有在使用 RPC 功能时,创建队列、交换器绑定关系等的操作会很频繁,其他的场景都不频繁,所以建议都使用磁盘节点类型

剔除单个节点

剔除单个节点有两种方式

适合不再运行 RabbitMQ 应用

当一个节点不再运行 RabbitMQ 应用时,可以使用如下命令。比如将 node2 剔除

  1. # 关闭 node2
  2. [root@node2 ~]# rabbitmqctl stop_app
  3. # 在其他节点上将 node2 踢出去
  4. [root@node1 ~]# rabbitmqctl forget_cluster_node rabbit@node2
  5. Removing node rabbit@node2 from cluster
  6. Copied!

这种操作方式之后, node2 节点就无法运行起来了。
这本书讲的太乱了。完全才堆砌功能,也不说场景?感觉特别乱
下面的由于无法前面强制剔除了 node2, node2 无法启动了,下面的实验也无法做下去了,只记录
前面提到过,当关闭集群最后一个节点,该节点无法启动时,可以通过 forget_cluster_node 命令将此节点剔除当前集群。比如,集群按照 node3、node2、node1 的顺序关闭,如果要启动集群,就要先启动 node1 节点。

  1. # 按顺序关闭节点
  2. [root@node3 ~]# rabbitmqctl stop
  3. [root@node2 ~]# rabbitmqctl stop
  4. [root@node3 ~]# rabbitmqctl stop
  5. # 如果由于 node1 节点启动不起来了。
  6. # 可以在 node2 上剔除 node1 节点
  7. # 这里使用 -offline 是离线模式,由于 node2 启动不起来
  8. [root@node2 ~]# rabbitmqctl forget_cluster_node rabbit@node1 -offline
  9. # 然后启动
  10. [root@node2 ~]# rabbitmq-server -detached
  11. Copied!

第二种方式

就是在能启动的情况下,哪个节点要退出,就使用 reset 命令

  1. [root@node2 ~]# rabbitmqctl stop_app
  2. [root@node2 ~]# rabbitmqctl reset
  3. [root@node2 ~]# rabbitmqctl start_app