RabbitMQ Clustering Guide
概述
一个RabbitMQ服务作为一个逻辑组或者几个Erlang节点,所有运行的Rabbitmq应用程序共享用户信息,虚拟主机,队列,交换器,绑定信息,运行参数。我们把引用的节点集合作为一个集群。
什么是复制?
对于在RabbitMQ服务中操作的所有数据和状态信息在所有的节点中都被复制,具有可靠性和伸缩性,满足ACID要求。但是存在一个例外是针对 message queue的,其默认是仅存在于创建它的那个 node 上面,尽管其同时对于所有其他 node 是可见和可达的。为了在 cluster 中的所有 node 上复制某个 queue 的内容,参考 high availability 相关文档(首先你可能需要一个可用的集群环境) 。
主机名解析要求
RabbitMQ的节点地址信息使用的是域名,短的或者完整的有效域名。因此集群中的所有成员都必须能够解析来自集群中节点的主机名信息,这样才能够在任何一台机子上使用命令行工具rabbitmqctl。
主机名解析可以使用任何一种标准的方式:
- DNS records
- Local host files (e.g. /etc/hosts)
In more restrictive environments, where DNS record or hosts file modification is restricted, impossible or undesired, Erlang VM can be configured to use alternative hostname resolution methods, such as an alternative DNS server, a local file, a non-standard hosts file location, or a mix of methods. Those methods can work in concert with the standard OS hostname resolution methods.
使用FQDN的话,在配置指南中查看RABBITMQ_USE_LONGNAME属性。
集群构成
集群可以通过以下的任何一种方式进行构成:
- 通过rabbitmqctl手动操作(比如在开发环境中)
- 在配置文件中声明集群节点列表
- 使用rabbitmq-autocluster方式 (一个插件)
集群的组成可以动态的改变。所有 RabbitMQ broker 在最初启动时都是从单独的一个节点开始的。 这些 node 可以加入到同一个 cluster 中,之后还可以重新退回成单个节点运行。
故障处理
RabbitMQ服务允许容忍单个节点的失效。节点可以随时启动和停止,只要他们可以在停止的时候通知一个已知的群集成员节点。
RabbitMQ有几种方式去处理网络分区问题,主要面向一致性。集群环境主要被使用在LAN(局域网)环境。不建议在WAN(广域网)环境下运行集群环境。通过使用Shovel或者Federation插件可以很好的在WAN环境中连接服务。Note that Shovel and Federation are not equivalent to clustering.
磁盘和内存节点
一个节点可以是一个磁盘节点或者一个内存节点。(备注:磁盘之间可以进行相互转换)。在大多数情况下你希望你的所有节点都是磁盘节点;内存节点是在特殊情况下为了提升集群中队列,交换器,绑定等性能使用的,如果你有疑问的话,可以只使用磁盘节点。
集群操作示范
下面是一份建立和操控 RabbitMQ cluster 的示范。其中包括 3 台机器 - rabbit1,rabbit2,rabbit3.
我们假设用户能够登陆到这三台机器上,RabbitMQ也已经安装在机器上了,并且rabbitmq-server和rabbitmqctl脚本命令在用户的PATH环境变量中配置好了。
这个示例可以被修改来在单个主机上运行,详细的细节在后面说明。
节点(和CLI工具)怎么认证其他节点: 使用ErlangCookie方式
RabbitMQ节点和CLI工具(比如:rabbitmqctl)使用cookie值来确定 node 间是否允许相互通信,两个 node 能够相互通信的前提是他们必须拥有相同的 Erlang cookie值。 这个cookie是一个字母数字组成的字符串。它的长度可以是长的或者短的。
每个集群节点必须具有相同的cookie。
在RabbitMQ服务启动的时候Erlang虚拟机会自动的创建一个随机的cookie文件。并且把它复制到集群环境里的所有其他节点中。
在Unix系统中,这个cookie文件通常位于 /var/lib/rabbitmq/.erlang.cookie 或者 $HOME/.erlang.cookie.
在Windows系统中,这个文件路径在 C:\Users\Current User.erlang.cookie (%HOMEDRIVE% + %HOMEPATH%.erlang.cookie) 或者 C:\Documents and Settings\Current User.erlang.cookie, and C:\Windows.erlang.cookie for RabbitMQ Windows service. 如果windows服务启动了,那么cookie应该在两个地方都有。
另一种方法, 你可以使用在脚本命令 rabbitmq-server 和 rabbitmqctl 中使用选项 “ -setcookie cookie” 来设置cookie.
当cookie配置错误 (例如,不完全相同), RabbitMQ 会记录错误的日志信息像这样 “Connection attempt from disallowed node” and “Could not auto-cluster”.
启动独立节点
要想建立一个 Cluster ,你就必须对每一个已经存在的 RabbitMQ node 按照 cluster 配置的方式重新进行配置。所以第一步要做的就是在每一个 node 上都常规启动 RabbitMQ 服务:
1 | rabbit1$ rabbitmq-server -detached |
这样就创建了 3 个独立的 RabbitMQ broker ,在每一个 node 上面,可以通过 cluster_status 命令来确认:
1 | rabbit1$ rabbitmqctl cluster_status |
通过 rabbitmq-server 脚本命令创建的 RabbitMQ broker 对应的 node 的名字是 rabbit@shorthostname 样式,其中 short node 名字在 Linux 下是小写字母形式(如 rabbit@rabbit1)。如果您是在 Windows 上使用 rabbitmq-server.bat 批处理来执行的上述命令,short node 名字会是大写字母形式(如 rabbit@RABBIT1)。所以, 当你要使用 node 名字时,要注意大小写的问题,因为匹配时要求完全一致。
创建集群
为了将我们创建的 3 个 node 连接成一个 cluster ,需要将其中两个 node(rabbit@rabbit2 和 rabbit@rabbit3)加入到第三个 node( rabbit@rabbit1)。
我们首先把rabbit@rabbit2加入到rabbit@rabbit1集群环境中。停止rabbit@rabbit2机器的RabbitMQ应用并且加入到rabbit@rabbit1集群中,然后重启RabbitMQ应用。注意:加入 cluster 的过程隐式包含了重置 node 的动作,即移除了当前 node 上之前存放的所的资源和数据。
1 | rabbit2$ rabbitmqctl stop_app |
我们可以从 rabbit@rabbit1 或者 rabbit@rabbit2 上通过命令 cluster_status 看到两个 node 已经加入到同一个 cluster 中了:
1 | rabbit1$ rabbitmqctl cluster_status |
现在我们把rabbit@rabbit3加入到相同的集群环境中,步骤和上面的相同。这次我们将加入 rabbit2 所在的 cluster (其实也是 rabbit1 所在的 cluster)以证明在这种情况下通过哪一个 node 加入 cluster 都是一样的。即只要我们提供了处于某个 cluster 中的可被其他人访问的 node ,那么该 node 所在的 cluster 就可以被其他 node 加入。
1 | rabbit3$ rabbitmqctl stop_app |
我们可以从任意一个 node 上通过命令 cluster_status 看到三个 node 已经加入到同一个 cluster 中了:
1 | rabbit1$ rabbitmqctl cluster_status |
按照上面的步骤,我们可以在任意时间添加新的 node 到 cluster 中,只要 cluster 处于运行状态。
重启集群节点
cluster 中的 node 在任何时候都可以被停止。 同样地如果他们崩溃了也是没有任何问题的。在上述两种情况中,cluster 中的其他 node 都可以不受任何影响的继续运行,这些“非正常” node 重新启动后会自动地与 cluster 中的其他 node 取得联系。
我们手动关闭 rabbit@rabbit1 和 rabbit@rabbit3 后,通过命令查看 cluster 的状态:
1 | rabbit1$ rabbitmqctl stop |
现在我们重新启动 node ,并查看 cluster 的状态:
1 | rabbit1$ rabbitmq-server -detached |
有一些重要的注意事项:
- 当整个 cluster 不能工作了,最后一个失效的 node 必须是第一个重新开始工作的那一个。如果这种情况得不到满足,所有 node 将会为最后一个磁盘 node 的恢复等待 30 秒。如果最后一个离线的 node 无法重新上线,我们可以通过命令 forget_cluster_node 将其从 cluster 中移除 - 具体参考 rabbitmqctl 的使用手册。
- 如果所有的节点在不受控的情况下停止(比如断电),在这种情况下,可以使用force_boot命令使它再次启动 - 具体参考 rabbitmqctl 的使用手册。
拆分集群
当 node 不应该继续存在于一个 cluster 中时,我们需要显式的将这些 node 移除。我们首先从 cluster 中移除 rabbit@rabbit3 ,将其还原为独立运行状态。具体做法为,在 rabbit@rabbit3 上先停止 RabbitMQ 应用,再重置 node ,最后重新启动RabbitMQ 应用。
1 | rabbit3$ rabbitmqctl stop_app |
值得注意的是,此时仍旧可以通过 list 命令发现 rabbit@rabbit3 仍然作为 node 显示出来。
在 node 上运行 cluster_status 命令可以发现 rabbit@rabbit3 已经不再是 cluster 中的一员,且已经处于独立运行状态:
1 | rabbit1$ rabbitmqctl cluster_status |
我们还可以利用远端移除 node 的操作,这在有些情况下是很有用的,比如对无任何反应的 node 的 处理 。例如,我们可以在 rabbit@rabbit2 上执行移除 rabbit@rabbit1 的操作。
1 | rabbit1$ rabbitmqctl stop_app |
注意到,rabbit1 仍旧会认为自己与 rabbit2 处于同一个 cluster 中,但是此时在 rabbit1 上执行 start_app 操作会提示相应错误信息。我们可以将 rabbit1 重置后让它重新运行。
1 | rabbit1$ rabbitmqctl start_app |
此时执行 cluster_status 命令可以显示出当前所有 3 个 node 均是作为独立的 RabbitMQ broker 处于运行状态:
1 | rabbit1$ rabbitmqctl cluster_status |
注意到 rabbit@rabbit2 会保有 cluster 的残余状态信息,而 rabbit@rabbit1 和 rabbit@rabbit3 却可以看成是新初始化的 RabbitMQ broker 。如果我们想要重新初始化 rabbit@rabbit2 ,我们可以按照下面的方式执行:
1 | rabbit2$ rabbitmqctl stop_app |
升级集群
当RabbitMQ从一个版本升级到另一个版本的时候(比如:从3.0.x到3.1.x,或者从2.x.x到3.x.x),或者升级Erlang环境,整个集群必须停止后进行升级(集群不能够运行混合版本)。这里指的不是升级补丁版本的情况(比如:从3.0.x到3.0.y),除非在发行版本中有特殊说明,这些版本是可以在一个集群中混合使用。因此在升级之前强烈建议查看发行版本的说明。
Some patch releases known to require a cluster-wide restart:
- 3.0.0 cannot be mixed with later versions from the 3.0.x series
- 3.6.6 and later cannot be mixed with earlier versions from the 3.6.x series
当 RabbitMQ 从一个版本升级到另一个版本时,如果必要,RabbitMQ 会自动升级持久化数据结构。在 cluster 中,上述工作会由第一个被启动的磁盘 node 进行(即“负责升级的” node )。所以,当你升级一个 RabbitMQ cluster 的时候,不可以首先启动任何内存 node ,任何内存 node 的启动将产生一条错误消息并且启动失败。
尽管不是一定必要,但是建议你事先决定好使用哪个磁盘 node 作为升级点(upgrader),然后在升级过程中,最后停止那个 node ,最先启动那个 node 。否则,在 升级点 node 停止和最后停止的 node 之间所做的对于 cluster 配置的修改将会被丢失掉。
自动升级的功能仅在 RabbitMQ 2.1.1 和之后的版本中才具有。如果你使用了更早版本的 cluster ,你讲需要通过重新构建的方式来升级。
单机上的集群
在一些情况下,在单机上运行 RabbitMQ node 的 cluster 可能对你很有实用价值。其中之一是,你可以在你的台式机或者笔记本上运行 cluster 而不用额外跑多个虚拟机。
为了在单个主机上运行多个RabbitMQ节点,确保节点具有不同的节点名称,数据存储位置,日志文件位置,并且绑定到不同的端口,包含那些使用的插件,在配置指南中查看RABBITMQ_NODENAME, RABBITMQ_NODE_PORT, 和 RABBITMQ_DIST_PORT的介绍,以及在文件和目录位置指南中的查看RABBITMQ_MNESIA_DIR, RABBITMQ_CONFIG_FILE, and RABBITMQ_LOG_BASE描述。
你可以反复调用rabbitmq-server脚本在同一个主机上启动多个节点(Windows里面是rabbitmq-server.bat文件),比如:
1 | $ RABBITMQ_NODE_PORT=5672 RABBITMQ_NODENAME=rabbit rabbitmq-server -detached |
上述步骤将创建2个节点的集群,二者都是作为磁盘节点,注意 如果你想开放其他端口,你可以通过配置那些不冲突的端口运行,通过命令完成:
1 | $ RABBITMQ_NODE_PORT=5672 RABBITMQ_SERVER_START_ARGS="-rabbitmq_management listener [{port,15672}]" RABBITMQ_NODENAME=rabbit rabbitmq-server -detached |
上述命令同样建立了两个 node 的 cluster ,但是使用了管理插件。
改变主机名
RabbitMQ 节点通过主机名来进行通信。因此,所有的节点必须能够解析来自集群中的其他节点。rabbitmqctl也是在这种情况使用。
除此之外,数据库目录使用当前的主机名在系统中使用。如果主机名发生了改变,一个新的空数据库将被创建。为了避免数据丢失创建一个固定的主机名是很重要的。当主机名发生了改变你需要重启RabbitMQ:
1 | $ /etc/init.d/rabbitmq-server restart |
A similar effect can be achieved by using rabbit@localhost as the broker nodename. The impact of this solution is that clustering will not work, because the chosen hostname will not resolve to a routable address from remote hosts. The rabbitmqctl command will similarly fail when invoked from a remote host. A more sophisticated solution that does not suffer from this weakness is to use DNS, e.g. Amazon Route 53 if running on EC2. If you want to use the full hostname for your nodename (RabbitMQ defaults to the short name), and that full hostname is resolveable using DNS, you may want to investigate setting the environment variable RABBITMQ_USE_LONGNAME=true.
查看hostname resolution guide获取更多的信息。
防火墙后的节点
这种情况是指数据中心或者可靠网络上的 cluster 中的 node 彼此之间存在防火墙的情况。再一次重申,不建议在 WAN 或者 node 之间的网络连接不可靠的情况下创建 cluster 。
在最常见的配置中,您将需要打开多个标准端口:
- 4369 (epmd)
- 5672, 5671 (AMQP 0-9-1 and 1.0 without and with TLS)
- This port used by Erlang distribution for inter-node and CLI tools communication and is allocated from a dynamic range (limited to a single port by default, computed as AMQP port + 20000). See networking guide for details.
- 15672 (if management plugin is enabled)
- 61613, 61614 (if STOMP is enabled)
- 1883, 8883 (if MQTT is enabled)
查看 RabbitMQ Networking guide获取更详细的信息.
集群中的Erlang版本
所有节点都必须运行在相同版本的erlang环境。
从客户端连接到集群
客户端可以透明地连接到 cluster 中的任意一个 node 上。 如果当前与客户端处于连接状态的那个 node 失效了,但是 cluster 中的其他 node 正常工作,那么客户端应该发现当前连接的关闭,然后应该可以重新连接到 cluster 中的其他正常的 node 上。一般来讲,将 node 的主机名或者 IP 地址 硬编码 到客户端应用程序中是非常不明智的:这会导致各种坑爹问题的出现,因为一旦 cluster 的配置改变或者 cluster 中的 ndoe 数目改变,客户端将面临重新编码、编译和重新发布的问题。作为替代,我们建议一种更加一般化的方式:采用 动态 DNS 服务 ,其具有非常短的 TTL 配置,或者 普通 TCP 负载均衡器 ,或者通过随机行走或者类似技术实现的某种形式的 mobile IP 。通常来讲,关于如何成功连接 cluster 中的 node 已经超出了 RabbitMQ 本身要说明的范畴,我们建议你使用其他的专门用于处理这方面问题的技术来解决这种问题。
带内存节点的集群
RAM nodes keep their metadata only in memory. As RAM nodes don’t have to write to disc as much as disc nodes, they can perform better. However, note that since persistent queue data is always stored on disc, the performance improvements will affect only resource management (e.g. adding/removing queues, exchanges, or vhosts), but not publishing or consuming speed.
RAM nodes are an advanced use case; when setting up your first cluster you should simply not use them. You should have enough disc nodes to handle your redundancy requirements, then if necessary add additional RAM nodes for scale.
A cluster containing only RAM nodes is fragile; if the cluster stops you will not be able to start it again and will lose all data. RabbitMQ will prevent the creation of a RAM-node-only cluster in many situations, but it can’t absolutely prevent it.
The examples here show a cluster with one disc and one RAM node for simplicity only; such a cluster is a poor design choice.
创建内存节点
我们可以声明一个内存节点加入到集群环境中,我们在使用rabbitmqctl join_cluster命令之,加上 –ram 标签即可:
1 | rabbit2$ rabbitmqctl stop_app |
内存节点在cluster status中显示效果如下:
1 | rabbit1$ rabbitmqctl cluster_status |
改变节点类型
我们可以改变 node 的类型,如磁盘 node 到内存 node ,或者相反。比如将 rabbit@rabbit2 和 rabbit@rabbit3 的 node 类型都变成和之前不同的种类。我们可以使用命令 change_cluster_node_type 来进行转换,但是首先需要将 node 停止。
1 | rabbit2$ rabbitmqctl stop_app |