RabbitMQ集群
一、集群原理
如果有一个消息生产者或者消息消费者通过amqp-client的客户端连接至节点1进行消息的发布或者订阅,那么此时的集群中的消息收发只与节点1相关。
如果消息生产者所连接的是节点2或者节点3,此时队列1的完整数据不在该两个节点上,那么在发送消息过程中这两个节点主要起了一个路由转发作用,根据这两个节点上的元数据转发至节点1上,最终发送的消息还是会存储至节点1的队列1上。
二、集群的形成
RabbitMQ
集群是一个或多个节点的逻辑分组,每个节点共享用户、虚拟主机、队列、交换器、绑定、运行时参数和其他分布式状态。
一些分布式系统 有leader和follower节点。 对于 RabbitMQ 来说, RabbitMQ集群中的所有节点都是平等的。
RabbitMQ
集群可以通过多种方式组成:
- 在config file中以声明方式列出集群节点
- 以声明方式使用基于DNS发现
- 使用AWS (EC2) instance discovery声明方式
- 使用Kubernetes discovery声明方式
- 使用Consul-based discovery声明方式
- 使用etcd-based discovery声明方式
- 手动使用rabbitmqctl
三、访问端口
RabbitMQ
节点绑定到端口以接受客户端和 CLI 工具连接。其他进程和工具(例如 SELinux)可能会阻止 RabbitMQ 绑定到端口。发生这种情况时,节点将无法启动。
CLI工具、客户端库和RabbitMQ
节点也会打开连接(客户端 TCP 套接字)。防火墙会阻止节点和 CLI 工具相互通信。确保可以访问以下端口:
- 4369:epmd,RabbitMQ节点和CLI工具使用的辅助发现守护进程。
- 5672,5671:由没有和有 TLS 的 AMQP 0-9-1 和 1.0 客户端使用。
- 25672:用于节点间和 CLI 工具通信(Erlang 分发服务器端口)并从动态范围分配(默认限制为单个端口,计算为 AMQP 端口 + 20000)。 除非确实需要这些端口上的外部连接(例如,集群使用联合或在子网外的机器上使用 CLI 工具),否则不应公开这些端口。
- 35672-35682:CLI 工具(Erlang 分发客户端端口)用于与节点通信,并从动态范围(计算为服务器分发端口 + 10000 到服务器分发端口 + 10010)分配。
- 15672:HTTP API 客户端、管理 UI 和 rabbitmqadmin(仅当启用管理插件时)。
- 61613,61614:没有和有 TLS 的 STOMP 客户端(仅当启用了 STOMP 插件时)。
- 1883,8883:没有和有 TLS 的 MQTT 客户端,如果启用了 MQTT 插件。
- 15675:MQTT-over-WebSockets 客户端(仅当启用了 Web MQTT 插件时)。
- 15692:Prometheus指标(仅当启用 Prometheus 插件时)。
四、CLI 工具如何对节点(以及节点彼此)进行身份验证:Erlang Cookie
RabbitMQ节点和 CLI 工具(例如 rabbitmqctl)使用 cookie 来确定它们是否被允许相互通信。为了让两个节点能够通信,它们必须具有相同的共享密钥,称为 Erlang cookie。cookie 只是一串最多 255 个字符的字母数字字符。 它通常存储在本地文件中。该文件必须只能由所有者访问(例如具有 600 或类似的 UNIX 权限)。每个集群节点必须具有相同的 cookie。
在 UNIX 系统上,cookie通常是位于/var/lib/rabbitmq/.erlang.cookie(由服务器使用)和$HOME/.erlang.cookie(由 CLI 工具使用)。
五、部署步骤
1、添加hosts信息
RabbitMQ
节点使用主机名相互寻址
cat >> /etc/hosts <<'EOF'
10.81.64.37 node01
10.81.64.38 node02
10.81.64.39 node03
EOF
2、安装Erlang环境
cd /usr/local/src && wget https://github.com/rabbitmq/erlang-rpm/releases/download/v22.1.3/erlang-22.1.3-1.el7.x86_64.rpm
yum -y install erlang-22.1.3-1.el7.x86_64.rpm
3、RabbitMQ集群部署
- 安装MQ软件
wget https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.7.17/rabbitmq-server-3.7.17-1.el7.noarch.rpm
yum -y install rabbitmq-server-3.7.17-1.el7.noarch.rpm
- 修改配置文件
默认配置文件/usr/lib/rabbitmq/lib/rabbitmq_server-3.7.17/ebin/rabbit.app
cat > /etc/rabbitmq/rabbitmq-env.conf <<'EOF'
RABBITMQ_MNESIA_BASE=/data/rabbitmq/mnesia
RABBITMQ_LOG_BASE=/data/rabbitmq/log
RABBITMQ_NODENAME=rabbit@node01
EOF
cat > /etc/rabbitmq/rabbitmq-env.conf <<'EOF'
RABBITMQ_MNESIA_BASE=/data/rabbitmq/mnesia
RABBITMQ_LOG_BASE=/data/rabbitmq/log
RABBITMQ_NODENAME=rabbit@node02
EOF
cat > /etc/rabbitmq/rabbitmq-env.conf <<'EOF'
RABBITMQ_MNESIA_BASE=/data/rabbitmq/mnesia
RABBITMQ_LOG_BASE=/data/rabbitmq/log
RABBITMQ_NODENAME=rabbit@node03
EOF
- 启动MQ
mkdir -p /data/rabbitmq/{mnesia,log}
chown -R rabbitmq. /data/rabbitmq/
rabbitmq-plugins enable rabbitmq_management rabbitmq_shovel rabbitmq_shovel_management rabbitmq_top
systemctl start rabbitmq-server
systemctl enable rabbitmq-server
- 同步
Erlang
cookie
因RabbitMQ 集群元数据同步基于 cookie 共享方案实现
文件路径/var/lib/rabbitmq/.erlang.cookie
- 创建集群
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster rabbit@node01
rabbitmqctl start_app
rabbitmqctl cluster_status
- 设置管理员用户
rabbitmqctl add_user admin xxxxxxxxx
rabbitmqctl set_user_tags admin administrator
- 设置Nginx负载均衡
$ vim rabbitmq-admin.conf
upstream rabbitmqadmin {
server 10.81.64.37:15672 weight=4 max_fails=2 fail_timeout=30s;
server 10.81.64.38:15672 weight=4 max_fails=2 fail_timeout=30s;
server 10.81.64.39:15672 weight=4 max_fails=2 fail_timeout=30s;
}
server {
listen 80;
server_name rabbitmq-admin.eminxing.com;
charset utf-8;
rewrite ^/(.*) https://$host/$1 permanent;
}
server {
listen 443 ssl;
server_name rabbitmq-admin.eminxing.com;
charset utf-8;
ssl_certificate /etc/nginx/eminxing-ssl/eminxing.crt;
ssl_certificate_key /etc/nginx/eminxing-ssl/eminxing.key;
access_log /home/logs/nginx/rabbitmq-admin/rabbitmq-admin.access.log access;
error_log /home/logs/nginx/rabbitmq-admin/rabbitmq-admin.error.log;
location / {
proxy_pass http://rabbitmqadmin/;
#proxy_next_upstream http_502 http_504 error timeout invalid_header non_idempotent;
proxy_next_upstream http_502 http_504 error invalid_header;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header $http_x_forwarded_for $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 600s;
proxy_send_timeout 600s;
proxy_read_timeout 600s;
proxy_buffer_size 64k;
proxy_buffers 4 128k;
proxy_http_version 1.1;
proxy_busy_buffers_size 256k;
proxy_temp_file_write_size 256k;
}
location ^~ /WEB-INF{
deny all;
}
}
$ vim rabbitmq.conf
upstream rabbitmq {
server 10.81.64.37:5672 weight=4 max_fails=2 fail_timeout=30s;
server 10.81.64.38:5672 weight=4 max_fails=2 fail_timeout=30s;
server 10.81.64.39:5672 weight=4 max_fails=2 fail_timeout=30s;
}
server {
listen 5672;
server_name rabbitmq.eminxing.com;
charset utf-8;
access_log /home/logs/nginx/rabbitmq/rabbitmq.access.log access;
error_log /home/logs/nginx/rabbitmq/rabbitmq.error.log;
location / {
proxy_pass http://rabbitmq;
proxy_connect_timeout 600s;
proxy_send_timeout 600s;
proxy_read_timeout 600s;
}
}
六、RAM节点集群
节点分为:磁盘节点及内存节点
节点类型 | 描述 |
---|---|
磁盘节点 | 存储所有数据信息 |
内存节点 | RAM节点仅在RAM中存储内部数据库表。这不包括消息、消息存储索引、队列索引和其他节点状态 |
RAM节点是一种特殊情况,可用于提高具有高队列、交换或绑定搅动的集群的性能。RAM节点不提供更高的消息速率。官方建议在绝大多数情况下,仅使用磁盘节点。
如果一个集群中都是RAM节点,那么集群停止,将无法再次启动,并将丢失所有数据
- 设置方法
rabbitmqctl stop_app
rabbitmqctl change_cluster_node_type ram
rabbitmqctl start_app
七、经典队列镜像
- 简介
官方提示:经典队列镜像将在未来版本中删除,考虑改用仲裁队列或非复制经典队列
每个镜像队列由一个领导副本和一个或多个镜像副本,leader被托管的节点成为leader节点。首先应用给定队列的所有操作 在队列的leader节点上,然后传播到镜像。
如果承载队列的leader节点出现故障,则只要已同步,最旧的镜像将升级为新的leader。根据队列镜像参数,也可以升级未同步的镜像。
- 队列镜像策略
队列通过策略启用了镜像,策略模式的说明如下:
ha-mode | ha-params | Result |
---|---|---|
exactly | count | 集群中的队列副本(领导加镜像)的数量。计数值为1表示只有一个领导副本,计数值为2表示一个领导副本一个镜像副本。换句话说:NumberOfQueueMirrors=NumberOfNodes-1 。如果运行队列的leader节点变得不可用,队列镜像将根据配置的镜像升级策略自动升级为leader。如果集群节点数少于count值,则将队列镜像到所有节点。如果集群节点数多于count值,一个包含镜像的节点宕机,那么将在另一个节点上创建一个新镜像。使用带有 "ha-promote-on-shutdown": "always" 的 exactly 模式很危险,因为队列可以跨集群迁移,并且在它关闭时变得不同步。 |
all | (none) | 跨集群中的所有节点进行队列镜像。当一个新节点被添加到集群中时,队列将被镜像到该节点。这个设置非常保守。 建议改为镜像到仲裁 (N/2 + 1) 个集群节点。镜像到所有节点会给所有集群节点带来额外的压力,包括网络 I/O、磁盘 I/O 和磁盘空间使用。 |
nodes | node names | 队列被镜像到节点名称中列出的节点。节点名称是出现在 rabbitmqctl cluster_status 中的 Erlang 节点名称; 通常具有“rabbit@hostname”的形式。如果节点名称中的其中一个不属于集群,则不构成错误。如果节点名称中所有的都不属于集群,则将在声明客户端连接到的节点上创建队列。 |
每当队列的策略发生变化时,它都保持其现有的镜像尽可能适用新策略。
- 启用队列镜像方法
设置的镜像队列可以通过开启的网页的管理端Admin->Policies,也可以通过命令。
管理端界面:
命令行:
rabbitmqctl set_policy ha-two "^two\." '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
rabbitmqctl set_policy ha-all "^ha\." '{"ha-mode":"all"}'
rabbitmqctl set_policy ha-nodes "^nodes\." '{"ha-mode":"nodes","ha-params":["rabbit@nodeA", "rabbit@nodeB"]}'
- 控制leader队列位置
为避免集群中的某个节点托管大多数leader队列,因此导致高负载,leader队列应合理均匀的分布在集群节点上。控制leader队列分布节点策略有三种,可以在rabbitmq.conf文件中定义queue_master_locator参数设置
值 | 含义 |
---|---|
min-masters | 选择承载最少领导者数量的节点 |
client-local | 选择声明队列的客户端连接到的节点 |
random | 随机选择节点 |
- 修改节点策略和迁移leader
修改节点策略可能会导致现有的leader队列没有在新的策略中,为了防止消息丢失,RabbitMQ会保留现有的leader直到至少另一个镜像已同步,一旦发生同步,消费者将与leader断开连接,需要重新连接。
- 高可用实现流程
如果leader故障,其他镜像升级为leader过程如下:
- 运行时间最长的镜像被提升为leader,假设它最有可能与leader完全同步。 如果没有与leader同步的镜像,那么只存在于leader上的消息就会丢失。
- 这个镜像认为所有先前的消费者都已突然断开连接。它将所有已传递给客户端但等待确认的消息重新排队。这可能包括客户端已发出确认的消息,例如,确认在到达托管leader队列的节点之前在线上丢失,或者从leader向镜像广播时丢失。在任何一种情况下,新的leader都别无选择,只能将所有没有看到确认的消息重新排队。
- 请求在队列故障转移时得到通知的消费者将收到取消通知。
- 作为重新排队的结果,从队列中重新消费的客户端必须知道,它们随后可能会收到它们已经收到的消息。
- 当所选镜像成为leader时,在此期间发布到镜像队列的任何消息都不会丢失(除非升级节点上出现后续故障)。发布到承载队列镜像的节点的消息将路由到leader队列,然后复制到所有镜像。如果引导程序失败,消息将继续发送到镜像,并在镜像升级到引导程序完成后添加到队列中。
- 即使leader(或任何镜像)在发布的消息与发布者收到确认之间失败,客户端使用publisher confirms发布的消息仍将被确认。从发布者的角度来看,发布到镜像队列与发布到非镜像队列没有什么不同。
如果消费者使用自动确认模式,那么消息可能会丢失。这与非镜像队列没有什么不同:代理认为消息一旦以自动确认模式发送给消费者,就会被确认。
如果客户端突然断开连接,则可能永远不会收到消息。在镜像队列的情况下,如果leader死亡,那些正在以自动确认模式发送给消费者的消息可能永远不会被这些客户端接收,并且不会被新leader重新排队。由于消费客户端连接到存活节点的可能性,消费者取消通知有助于识别此类事件何时发生。 当然,在实践中,如果数据安全性不如吞吐量重要,那么自动确认模式是可行的方法。
- 不同步的镜像
节点可以随时加入集群。 根据队列的配置,当节点加入集群时,队列可能会在新节点上添加镜像。 此时,新镜像将是空的:它不会包含队列中任何现有的内容。 这样的镜像将接收发布到队列的新消息,因此随着时间的推移将准确地表示镜像队列的尾部。 随着消息从镜像队列中排出,新镜像丢失消息的队列头部的大小将缩小,直到最终镜像的内容与leader的内容完全匹配。 在这一点上,镜像可以被认为是完全同步的。
新添加的镜像不提供添加镜像之前存在的队列内容的额外形式的冗余或可用性,除非队列已明确同步。 由于在发生明确同步时队列变得无响应,因此最好允许正在从中排出消息的活动队列自然同步,并且仅明确同步非活动队列。
启用自动队列镜像时,请考虑所涉及队列的预期磁盘数据集。 具有庞大数据集(例如,数十 GB 或更多)的队列必须将其复制到新添加的镜像中,这会给集群资源(例如网络带宽和磁盘 I/O)带来很大的负载。
要查看镜像状态(是否同步):
rabbitmqctl list_queues name slave_pids synchronised_slave_pids
手动同步队列:
rabbitmqctl sync_queue {name}
取消正在进行的同步:
rabbitmqctl cancel_sync_queue {name}
- 停止节点和同步
如果你停止一个包含镜像队列leader的 RabbitMQ 节点,其他节点上的一些镜像将被提升为leader。 如果继续停止节点,那么将到达一个镜像队列不再有镜像的点:它仅存在于一个节点上,且该节点上它是leader。 如果它的最后一个剩余节点关闭,但是镜像队列被声明为持久的,那么队列中的持久消息将在该节点重新启动后继续存在。
然而,镜像目前无法知道它的队列内容是否已经偏离了它重新加入的leader。 因此,当一个镜像重新加入一个镜像队列时,它会丢弃已经拥有的任何持久本地内容并开始为空。
- 只使用不同步镜像时停止leader节点
默认情况下,RabbitMQ 将拒绝leader节点在受控关闭(即明确停止 RabbitMQ 服务或关闭操作系统)时提升非同步镜像,以避免消息丢失; 相反,整个队列将关闭,就好像未同步的镜像不存在一样。
leader节点不受控制的关闭(即服务器或节点崩溃,或网络中断)仍将触发未同步镜像的提升。
如果您希望在所有情况下都让leader队列移动到未同步的镜像(即,您会选择队列的可用性而不是避免由于未同步的镜像升级而导致的消息丢失),那么将 ha-promote-on-shutdown
策略键设置为 always
而不是比它的默认值 when-synced
。
如果 ha-promote-on-failure
策略键设置为 when-synced
,则即使 ha-promote-on-shutdown
键设置为 always
,也不会提升未同步的镜像。 这意味着如果leader节点发生故障,队列将变得不可用,直到leader恢复。 如果leader队列永久丢失,队列将不可用,除非它被删除(这也将删除其所有内容)并重新声明。
- 所有镜像停止时丢失leader
当队列的所有镜像都关闭时,可能会丢失队列的leader。 在正常操作中,队列关闭的最后一个节点将成为leader,该节点在再次启动时仍然是leader(因为它可能收到了其他镜像没有看到的消息)。
但是,当您调用 rabbitmqctl forget_cluster_node
时,RabbitMQ 将尝试为每个在我们忘记的节点上有其领导者的队列找到当前停止的镜像,并在它再次启动时“提升”该镜像成为新的领导者。 如果有多个候选者,将选择最近停止的镜像。
重要的是要理解 RabbitMQ 只能在 forget_cluster_node
期间提升停止的镜像,因为任何再次启动的镜像都会清除它们的内容,如上面“停止节点和同步”中所述。 因此在停止的集群中移除丢失的leader时,您必须在再次启动镜像之前调用 rabbitmqctl forget_cluster_node
。