运维

本文是《RabbitMQ实战指南》(朱忠华 著)学习笔记,仅供本人学习研究之用,如若喜欢请购买正版书籍。如有侵权,请联系删除。


集群搭建

前面的学习都是基于单机的 RabbitMQ,无法满足真实项目。如果遇到内存崩溃、机器掉电或者主板故障等情况该怎么办?单台 RabbitMQ 服务器可以满足每秒 1000 条消息的吞吐量,那么如果应用需要 RabbitMQ 服务满足每秒 10 万条消息的吞吐量呢?在任何时候都不要想通过更换性能强劲的服务器解决问题,因为这样只会捉襟见肘,搭建一个 RabbitMQ 集群才是解决实际问题的关键。

RabbitMQ 集群允许消费者和生产者在 RabbitMQ 单个节点崩溃的情况下继续运行,它可以通过添加更多的节点来线性地扩展消息通信的吞吐量。当失去一个 RabbitMQ 节点时,客户端能够重新连接到集群中的任何其他节点并继续生产或者消费。

不过 RabbitMQ 集群也不能保证消息的万无一失,即使将消息、队列、交换器等都设置为可持久化,生产者和消费者都正确地使用了确认方式。当集群中一个 RabbitMQ 节点崩溃时,该节点上的所有队列中的消息也会丢失。RabbitMQ 集群中的所有节点都会备份所有的元数据信息,包括以下内容:

• 队列元数据:队列的名称及属性;

• 交换器:交换器的名称及属性;

• 绑定关系元数据:交换器与队列或者交换器与交换器之间的绑定关系;

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

但是不会备份消息(通过特殊的配置,比如镜像队列可以解决这个问题)。基于存储空间和性能的考虑,在 RabbitMQ 集群中创建队列,集群只会在单个节点而不是在所有节点上创建队列的进程并包含完整的队列信息(元数据、状态、内容)。这样只有队列的宿主节点,即所有者节点知道队列的所有信息,其他非所有者节点只知道队列的元数据和指向该队列存在的那个节点的指针。因此当集群节点崩溃时,该节点的队列进程和关联的绑定都会消失。附加在那些队列上的消费者也会丢失其所订阅的信息,并且任何匹配该队列绑定信息的新消息也都会消失。

不同于队列那样拥有自己的进程,交换器其实只是一个名称和绑定列表。当消息发布到交换器时,实际上是由所连接的信道将消息上的路由键同交换器的绑定列表进行比较,然后再路由消息。当创建一个新的交换器时,RabbitMQ 所要做的就是将绑定列表添加到集群中的所有节点上。这样,每个节点上的每条信道都可以访问到新的交换器了。

RabbitMQ 集群对延迟非常敏感,应当只在本地局域网内使用。在广域网中不应该使用集群,而应该使用 Federation 或者 Shovel 来代替

多机多节点配置

多机多节点是指在每台机器中部署一个 RabbitMQ 服务节点,进而由多台机器组成一个 RabbitMQ 集群。我这里的机器有两台,一台是在 VMware 虚拟机中安装的 Ubuntu 上的 RabbitMQ,IP 为 192.168.115.128,主机名(并不是RabbitMQ节点名称)为 wuychn;另一台是在我的 Windows 10 上安装的 RabbitMQ(Windows安装参考 Windows下安装RabbitMQ,RabbitMQ 下载地址),IP 为 192.168.1.107,主机名(并不是RabbitMQ节点名称)为 node。两个 RabbitMQ 的版本都是3.6.15。

第一步,配置各个节点的 hosts 文件,让各个节点都能互相识别对方的存在。Ubuntu 编辑 /etc/hosts,Windows 编辑 C:\Windows\System32\drivers\etc\hosts,增加如下配置:

192.168.115.128     wuychn
192.168.1.107       node

第二步,编辑 RabbitMQ 的 cookie 文件,确保各个节点的 cookie 文件使用的是同一个值。可以读取其中一个节点的 cookie 值,然后将其复制其他节点中。Ubuntu 下 cookie 文件默认路径为 /var/lib/rabbitmq/.erlang.cookie(通过rpm、wget等命令安装) 或者 $HOME/.erlang.cookie(通过解压缩方式安装),其中,$HOME具体路径可以通过 echo $HOME 命令查看,我这里是/root。Windows 下cookie 文件路径在 C:\Users\{computername}\.erlang.cookie 和 C:\Windows\.erlang.cookie(反正我两个地方都看到有,也要确保它们的值相同)。cookie 相当于密钥令牌,集群中的 RabbitMQ 节点需要通过交换密钥令牌以获得相互认证。如果节点的密钥令牌不一致,那么在配置节点时就会有如下的报错:

Clustering node rabbit@node with rabbit@wuychn
Error: unable to connect to nodes [rabbit@wuychn]: nodedown

DIAGNOSTICS
===========

attempted to contact: [rabbit@wuychn]

rabbit@wuychn:
  * connected to epmd (port 4369) on wuychn
  * epmd reports node 'rabbit' running on port 25672
  * TCP connection succeeded but Erlang distribution failed

  * Authentication failed (rejected by the remote node), please check the Erlang cookie


current node details:
- node name: 'rabbitmq-cli-40@node'
- home dir: C:\Users\Lenovo
- cookie hash: lYDt4xg0a1WhIZPwkVCvGQ==

这里需要注意的是,cookie 文件默认的权限是只对拥有者可读(-r--------),修改其值前要先修改权限,修改完权限后要把它改为只对拥有者可读的权限,否则会报这样的错误:

Error: Failed to initialize erlang distribution: {{shutdown,
                                                   {failed_to_start_child,
                                                    auth,
                                                    {"Cookie file /root/.erlang.cookie must be accessible by owner only",
                                                     [{auth,init_cookie,0,
                                                       [{file,"auth.erl"},
                                                        {line,286}]},
                                                      {auth,init,1,
                                                       [{file,"auth.erl"},
                                                        {line,140}]},
                                                      {gen_server,init_it,2,
                                                       [{file,
                                                         "gen_server.erl"},
                                                        {line,365}]},
                                                      {gen_server,init_it,6,
                                                       [{file,
                                                         "gen_server.erl"},
                                                        {line,333}]},
                                                      {proc_lib,
                                                       init_p_do_apply,3,
                                                       [{file,"proc_lib.erl"},
                                                        {line,247}]}]}}},
                                                  {child,undefined,
                                                   net_sup_dynamic,
                                                   {erl_distribution,
                                                    start_link,
                                                    [['rabbitmq-cli-14',
                                                      shortnames],
                                                     false]},
                                                   permanent,1000,supervisor,
                                                   [erl_distribution]}}.

第三步,配置集群。配置集群有三种方式:通过 rabbitmqctl 工具配置;通过 rabbitmq.config 配置文件配置 ;通过 rabbitmq-autocluster 插件配置。这里主要讲的是通过 rabbitmqctl 工具的方式配置集群,这种方式也是最常用的方式。其余两种方式在实际应用中用之甚少。

首先通过命令 rabbitmq-server -detached 启动所有节点的 RabbitMQ 服务,目前它们都是以独立节点存在的单个集群。通过rabbitmqctl cluster_status 命令来查看各个节点的状态。Ubuntu 下:

root@wuychn:~# rabbitmqctl cluster_status
Cluster status of node rabbit@wuychn
[{nodes,[{disc,[rabbit@wuychn]}]},
 {running_nodes,[rabbit@wuychn]},
 {cluster_name,<<"rabbit@wuychn">>},
 {partitions,[]},
 {alarms,[{rabbit@wuychn,[]}]}]

Windows 下:

D:\>rabbitmqctl cluster_status
Cluster status of node rabbit@node
[{nodes,[{disc,[rabbit@node]}]},
 {running_nodes,[rabbit@node]},
 {cluster_name,<<"rabbit@node">>},
 {partitions,[]},
 {alarms,[{rabbit@node,[]}]}]

接下来为了将这两个节点组成一个集群,以 wuychn 节点为基准,将 node 节点加入 wuychn 节点的集群中。将 node 节点加入 wuychn 节点的集群中,需要执行如下 4 个命令:

D:\>rabbitmqctl stop_app
Stopping rabbit application on node rabbit@node

D:\>rabbitmqctl reset
Resetting node rabbit@node

D:\>rabbitmqctl join_cluster rabbit@wuychn
Clustering node rabbit@node with rabbit@wuychn

D:\>rabbitmqctl start_app
Starting node rabbit@node

如果还有其他节点(比如 node2、node3...),也要分别将它们加入 wuychn 节点的集群中,加入方式同上(注意要分别在节点所在的机器上执行以上四条命令)。

如此,wuychn 节点和 node 节点便处于同一个集群之中。在 wuychn 节点看集群状态:

root@wuychn:~# rabbitmqctl cluster_status
Cluster status of node rabbit@wuychn
[{nodes,[{disc,[rabbit@node,rabbit@wuychn]}]},
 {running_nodes,[rabbit@node,rabbit@wuychn]},
 {cluster_name,<<"rabbit@wuychn">>},
 {partitions,[]},
 {alarms,[{rabbit@node,[]},{rabbit@wuychn,[]}]}]

在 node 节点查看集群状态:

D:\>rabbitmqctl cluster_status
Cluster status of node rabbit@node
[{nodes,[{disc,[rabbit@node,rabbit@wuychn]}]},
 {running_nodes,[rabbit@wuychn,rabbit@node]},
 {cluster_name,<<"rabbit@wuychn">>},
 {partitions,[]},
 {alarms,[{rabbit@wuychn,[]},{rabbit@node,[]}]}]

现在己经完成了集群的搭建。如果集群中某个节点关闭了,那么集群会处于什么样的状态呢?

在 node 节点上执行 rabbitmqctl stop_app 命令来主动关闭 RabbitMQ 应用。此时在 wuychn 节点上看到的集群状态如下,可以看到在 running_nodes 这一选项中已经没有了 rabbit@node 这一节点:

root@wuychn:~# rabbitmqctl cluster_status
Cluster status of node rabbit@wuychn
[{nodes,[{disc,[rabbit@node,rabbit@wuychn]}]},
 {running_nodes,[rabbit@wuychn]},
 {cluster_name,<<"rabbit@wuychn">>},
 {partitions,[]},
 {alarms,[{rabbit@wuychn,[]}]}]

如果关闭了集群中的所有节点,则需要确保在启动的时候最后关闭的那个节点是第一个启动的。如果第一个启动的不是最后关闭的节点,那么这个节点会等待最后关闭的节点启动。这个等待时间是 30 秒,如果没有等到,那么这个先启动的节点也会失败。在最新的版本中会有重试机制,默认重试 10 次 30 秒以等待最后关闭的节点启动。在重试失败之后,当前节点也会因失败而关闭自身的应用。比如 wuychn 节点最后关闭,那么若先启动 node 节点,node 节点在等待若干时间之后发现 wuychn 节点还是没有启动,则会报错。

如果最后一个关闭的节点最终由于某些异常而无法启动,则可以通过 rabbitmqctl forget_cluster_node 命令来将此节点剔出当前集群。如果集群中的所有节点由于某些非正常因素,比如断电而关闭,那么集群中的所有节点都会认为自己不是最后一个关闭的,此时需要调用 rabbitmqctl force_boot 命令来启动一个节点,之后集群才能正常启动。

集群节点类型

当使用 rabbitmqctl cluster_status 命令查看集群状态时,会有 {nodes,[{disc,[rabbit@node,rabbit@wuychn]}] 这一项信息,其中的 disc 标注了RabbitMQ 节点的类型(disc类型表示磁盘节点,disc 应该是 disk 的意思,disk 就是磁盘的意思)。RabbitMQ 中的每一个节点,不管是单一节点系统或者是集群中的一部分,要么是内存节点,要么是磁盘节点。内存节点将所有的队列、交换器、绑定关系、用户、权限和 vhost 的元数据定义都存储在内存中,而磁盘节点则将这些信息存储到磁盘中。单节点的集群中必然只有磁盘类型的节点,否则当重启 RabbitMQ 之后,所有关于系统的配置信息都会丢失。不过在集群中,可以选择配置部分节点为内存节点,这样可以获得更高的性能。

比如将 node 节点加入 wuychn 节点的时候可以指定 node 节点的类型为内存节点:

rabbitmqctl join_cluster rabbit@wuychn --ram

这样在以 node 和 wuychn 组成的集群中就会有一个磁盘节点(wuychn)和一个内存节点(node)。默认不添加 "-ram" 参数则表示此节点为磁盘节点。将 node 指定为内存节点后,使用 rabbitmqctl cluster_status 命令查看集群信息如下(截取部分):

[{nodes,[{disc,[rabbit@wuychn]}, {ram,[rabbit@node]}]}

如果集群已经搭建好了,那么也可以使用 rabbitmqctl change_cluster_node_type {disc | ram} 命令来切换节点的类型,其中 disc 表示磁盘节点,ram 表示内存节点。例如,将上面 node 节点由磁盘节点转变为内存节点:

C:\>rabbitmqctl stop_app
Stopping rabbit application on node rabbit@node

C:\>rabbitmqctl change_cluster_node_type ram
Turning rabbit@node into a ram node

C:\>rabbitmqctl start_app
Starting node rabbit@node

C:\>rabbitmqctl cluster_status
Cluster status of node rabbit@node
[{nodes,[{disc,[rabbit@wuychn]},{ram,[rabbit@node]}]},
 {running_nodes,[rabbit@wuychn,rabbit@node]},
 {cluster_name,<<"rabbit@wuychn">>},
 {partitions,[]},
 {alarms,[{rabbit@wuychn,[]},{rabbit@node,[]}]}]

需要注意的是,在改变节点类型前,需要使用 rabbitmqctl stop_app 命令停掉 RaabbitMQ 服务,否则会报如下错误:

Error:  Mnesia is still running on node rabbit@node.
        Please stop the node with rabbitmqctl stop_app first.

在集群中创建队列、交换器或者绑定关系的时候,这些操作直到所有集群节点都成功提交元数据变更后才会返回。对内存节点来说,这意味着将变更写入内存,而对于磁盘节点来说,这意味着昂贵的磁盘写入操作。内存节点可以提供出色的性能,磁盘节点能够保证集群配置信息的高可靠性,如何在这两者之间进行抉择呢?

RabbitMQ 只要求在集群中至少有一个磁盘节点,所有其他节点可以是内存节点。当节点加入或者离开集群时,它们必须将变更通知到至少一个磁盘节点。如果只有一个磁盘节点,而且不凑巧的是它刚好崩溃了,那么集群可以继续发送或者接收消息,但是不能执行创建队列、交换器、绑定关系、用户,以及更改权限、添加或删除集群节点的操作了。也就是说,如果集群中唯一的磁盘节点崩溃,集群仍然可以保持运行,但是直到将该节点恢复到集群前,你无法更改任何东西。所以在建立集群的时候应该保证有两个或者多个磁盘节点的存在。

在内存节点重启后,它们会连接到预先配置的磁盘节点,下载当前集群元数据的副本。当在集群中添加内存节点时,确保告知其所有的磁盘节点(内存节点唯一存储到磁盘的元数据信息是集群中磁盘节点的地址)。只要内存节点可以找到至少一个磁盘节点,那么它就能在重启后重新加入集群中。

除非使用的是 RabbitMQ 的 RPC 功能,否则创建队列、交换器及绑定关系的操作很少,大多数的操作就是生产或者消费消息。为了确保集群信息的可靠性,或者在不确定使用磁盘节点或者内存节点的时候,建议全部使用磁盘节点。

剔除单个节点

有两种方式将某个节点剥离出当前集群。

第一种,首先在要剔除的节点上(比如要剔除 node 节点,就在 node 节点上执行)执行 rabbitmqctl stop_app 或者 rabbitmqctl stop 命令来关闭 RabbitMQ 服务。之后再在其余任意一节点(我这里只剩下 wuychn 这个节点了)上执行 rabbitmqctl forget_cluster_node rabbit@node(rabbit@node 就是被剔除那个节点的名字)命令将 node 节点剔除出去。这种方式适合 node 节点不再运行 RabbitMQ 的情况。

比如,在 node 节点上执行命令关闭 RabbitMQ 服务:

C:\>rabbitmqctl stop_app
Stopping rabbit application on node rabbit@node

然后在 wuychn 节点上执行命令,将 node 节点剔除集群:

root@wuychn:~# rabbitmqctl forget_cluster_node rabbit@node
Removing node rabbit@node from cluster

查看集群状态,node 节点已经不再集群中了:

root@wuychn:~# rabbitmqctl cluster_status
Cluster status of node rabbit@wuychn
[{nodes,[{disc,[rabbit@wuychn]}]},
 {running_nodes,[rabbit@wuychn]},
 {cluster_name,<<"rabbit@wuychn">>},
 {partitions,[]},
 {alarms,[{rabbit@wuychn,[]}]}]

前面提到过,在关闭集群中的每个节点之后,如果最后一个关闭的节点最终由于某些异常而无法启动,则可以通过 rabbitmqctl forget_cluster_node 命令来将此节点剔除出当前集群。举例来说,集群中节点按照 node3、node2、node1 的顺序关闭,此时如果要启动集群,就要先启动 node1 节点。如果 node1 节点由于某些原因不能启动,那么可以在 node2 节点中执行如下命令将 node1 节点剔除出当前集群:

rabbitmqctl forget_cluster_node rabbit@nodel -offline

之后启动 node2 即可。注意上面在使用 rabbitmqctl forget_cluster_node 命令的时候用到了 "-offline" 参数,如果不添加这个参数,就需要保证 node2 节点中的 RabbitMQ 服务处于运行状态,而在这种情况下,node2 无法先行启动,"-offline" 参数可以让其在非运行状态下将 node1 剥离出当前集群。

第二种方式是在要剔除的节点上执行 rabbitmqctl reset 命令。如果不是像上面由于启动顺序的缘故而不得不删除一个集群节点,建议采用这种方式。示例如下:

在 node 节点上执行如下命令:

C:\>rabbitmqctl stop_app
Stopping rabbit application on node rabbit@node

C:\>rabbitmqctl reset
Resetting node rabbit@node

C:\>rabbitmqctl start_app
Starting node rabbit@node

查看 node 节点的集群状态,可见 node 节点已经是一个独立节点:

root@wuychn:~# rabbitmqctl cluster_status
Cluster status of node rabbit@wuychn
[{nodes,[{disc,[rabbit@wuychn]}]},
 {running_nodes,[rabbit@wuychn]},
 {cluster_name,<<"rabbit@wuychn">>},
 {partitions,[]},
 {alarms,[{rabbit@wuychn,[]}]}]

同样在集群中剩余的节点上也看到 node 节点已经不再是集群的一部分了:

root@wuychn:~# rabbitmqctl cluster_status
Cluster status of node rabbit@wuychn
[{nodes,[{disc,[rabbit@wuychn]}]},
 {running_nodes,[rabbit@wuychn]},
 {cluster_name,<<"rabbit@wuychn">>},
 {partitions,[]},
 {alarms,[{rabbit@wuychn,[]}]}]

rabbitmqctl reset 命令将清空节点的状态,并将其恢复到空白状态。当被重设的节点是集群中的一部分时,该命令也会和集群中的磁盘节点进行通信,告诉它们该节点正在离开集群。不然集群会认为该节点出了故障,并期望其最终能够恢复过来。

集群节点的升级

如果 RabbitMQ 集群由单独的一个节点组成,那么升级版本很容易,只需关闭原来的服务,然后解压新的版本再运行即可。不过要确保原节点的 Mnesia 中的数据不被变更,且新节点中的 Mnesia 路径的指向要与原节点中的相同。或者说保留原节点 Mnesia 数据,然后解压新版本到相应的目录,再将新版本的 Mnesia 路径指向保留的 Mnesia 数据的路径(也可以直接复制保留的 Mnesia 数据到新版本中相应的目录),最后启动新版本的服务即可。

如果 RabbitMQ 集群由多个节点组成,那么也可以参考单个节点的情形。具体步骤:

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

• 保存各个节点的 Mnesia 数据。

• 解压新版本的 RabbitMQ 到指定的目录。

• 指定新版本的 Mnesia 路径为步骤 2 中保存的 Mnesia 数据路径。

• 启动新版本的服务,注意先启动原版本中最后关闭的那个节点。

单机多节点配置

在一台机器上部署多个 RabbitMQ 服务节点,需要确保每个节点都有独立的名称、数据存储位置、端口号(包括插件的端口号)等。生产环境一般使用的是多机多节点配置,不仅可以扩容,也能有效地进行容灾。

查看服务日志

RabbitMQ 日志中包含各种类型的事件,比如连接尝试、服务启动、插件安装及解析请求时的错误等。

RabbitMQ 的日志默认存放在 $RABBITMQ_HOME/var/log/rabbitmq 文件夹内。在这个文件夹内 RabbitMQ 会创建两个日志文件:RABBITMQ_NODENAME-sasl.log 和 RABBITMQ_NODENAME.log。

SASL(System Application Support Libraries,系统应用程序支持库)是库的集合,作为 Erlang-OTP 发行版的一部分。它们帮助开发者在开发 Erlang 应用程序时提供一系列标准,其中之一是日志记录格式。所以当 RabbitMQ 记录 Erlang 相关信息时,它会将日志写入文件 RABBITMQ_NODENAME-sasl.log 中。举例来说,可以在这个文件中找到 Erlang 的崩溃报告,有助于调试无法启动的 RabbitMQ 节点。

如果想查看 RabbitMQ 应用服务的日志,则需要查阅 RABBITMQ_NODENAME.log 这个文件,RabbitMQ 服务日志指的就是这个文件。

单节点故障恢复

在 RabbitMQ 使用过程中,或多或少都会遇到一些故障。对于集群层面来说,更多的是单点故障。所谓的单点故障是指集群中单个节点发生了故障,有可能会引起集群服务不可用、数据丢失等异常。配置数据节点冗余(镜像队列)可以有效地防止由于单点故障而降低整个集群的可用性、可靠性。单节点故障包括:机器硬件故障、机器掉电、网络异常、服务进程异常等。

集群迁移

对于 RabbitMQ 运维层面来说,扩容和迁移是必不可少的。扩容比较简单,一般向集群中加入新的集群节点即可,不过新的机器节点中是没有队列创建的,只有后面新创建的队列才有可能进入这个新的节点中。

迁移同样可以解决扩容的问题,将旧的集群中的数据(包括元数据信息和消息)迁移到新的且容量更大的集群中即可。RabbitMQ 中的集群迁移更多的是用来解决集群故障不可短时间内修复而将所有的数据、客户端连接等迁移到新的集群中,以确保服务的可用性。相比于单点故障而言,集群故障的危害性就大得多,比如 IDC 整体停电、网线被挖断等。这时候就需要通过集群迁移重新建立起一个新的集群。RabbitMQ 集群迁移包括元数据重建、数据迁移,以及与客户端连接的切换。

集群监控

监控不仅可以提供运行时的数据为应用提供依据参考,还可以迅速定位问题、提供预防及告警等功能,很大程度上增强了整体服务的可靠性。RabbitMQ 扩展的 RabbitMQ Management 插件就能提供一定的监控功能,Web 管理界面提供了很多的统计值信息,如发送速度、确认速度、消费速度、消息总数、磁盘读写速度、句柄数、Socket 连接数、Connection数、Channel数、内存信息等。总体上来说, RabbitMQ Management 插件提供的监控页面是相对完善的,在实际应用中具有很高的使用价值。但是有一个遗憾就是其难以和公司内部系统平台关联,对于业务资源的使用情况、相应的预防及告警的联动无法顺利贯通。如果在人力、物力等条件允许的情况下,自定义一套监控系统非常有必要。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值