7 RabbitMQ 运维
7 RabbitMQ 运维
概述
对于服务端的异常来说,虽然不能完全杜绝,但是可以采取一些有效的手段去检测、管控,当某些指标超过阈值时能够迅速采取一些措施去修正,以防止发生不必要的故障(比如单点故障、集群故障等),而当真正发生故障时也要能够迅速修复。
集群搭建
RabbitMQ 集群不能保证消息的万无一失,即将消息、队列、交换器等都设置为可持续化,生产端和消费端都正确地使用了确认方式。当集群中一个 RabbitMQ 节点崩溃时,该节点上的所有队列中的消息也会丢失。RabbitMQ 集群中的所有节点都会备份所有的元数据信息,包括以下内容:
- 队列元数据:队列的名称及属性;
- 交换器:交换器的名称及属性;
- 绑定关系元数据:交换器与队列或者交换器与交换器之间的绑定关系;
- vhost 元数据:为 vhost 内的队列、交换器和绑定提供命名空间及安全属性。
但是不会备份消息(通过特殊的配置如镜像队列可以解决这个问题。)基于存储空间和性能的考虑,在 RabbitMQ 集群中创建队列,集群只会在单个节点而不是在所有节点上创建队列的进程并包含完整的队列信息(元数据、状态、内容)。这样只有队列的宿主节点,即所有者节点知道队列的所有信息,所有其他非所有者节点只知道队列的元数据和指向该队列存在的那个节点的指针。因此当集群节点崩溃时,该节点的队列进程和关联的绑定都会消息。附加在那些队列上的消费者也会丢失其所订阅的信息,并且任何该队列绑定信息的新消息也都会消失。
不同于队列那样拥有自己的进程,交换器其实只是一个名称和绑定列表。当消息发布到交换器时,实际上是由所连接的信道将消息的路由键同交换器的绑定列表进行比较,然后再路由消息。当创建一个新的交换器时,RabbitMQ 所要做的就是将绑定列表添加到集群中的所有节点上。这样,每个节点上的每条信道都可以访问到新的交换器上了。
多机多节点配置
主要是指在每台机器中部署一个 RabbitMQ 服务节点,进而由多台机器组成一个 RabbitMQ 集群。需要先安装好单机版 RabbitMQ,然后再配置集群。 RabbitMQ 集群对延迟非常敏感,应当只在本地局域网内使用。在广域网中不应该使用集群,而应该使用 Federation 或者 Shovel 来代替。
按照以下步骤执行,参考书籍梳理步骤,未实际配置,可能与版本关系会有些许差异:
- 配置 hosts 文件
192.168.0.1 node1
192.168.0.2 node2
192.168.0.3 node3
编辑 RabbitMQ 的cookie 文件
确保各个节点的 cookie 文件使用的是同一个值。可以读取 node1 节点的 cookie 值,然后将其复制到 node2 和 node3 节点中。cookie 文件默认路径为 /var/lib/rabbitmq/.erlang.cookie。cookie 相当于密钥令牌,集群中的 RabbitMQ 节点需要通过密钥令牌以获得相互认证。如果节点的密钥令牌不一致,那么在配置节点时就会报错。配置集群
配置集群有三种方式:通过 rabbitmqctl 工具配置;通过 rabbitmq.config 配置文件配置;通过 rabbitmq-autocluster 插件配置。其中第 1 种是最常用的方式。
- 启动 node1、node2 和 node3 这三个节点的 RabbitMQ 服务。
$ rabbitmq-server -detached
,启动后目前都是以独立节点存在的单个集群,可以通过$ rabbitmqctl cluster_status
命令来查看各个节点的状态 - 将三个节点组成一个集群,我们以 node1 节点为基准,将 node2 和 node3 节点加入 node1 节点的集群中。
// 将 node2 节点加入 node1 节点的集群中,需要在 node2 上执行下面4个命令,node3 加入同理。
$ rabbitmqctl stop_app
$ rabbitmqctl reset
$ rabbitmqctl join_cluster rabbitmq@node1
$ rabbitmqctl start_app
执行上述步骤后就完整了集群的搭建。如果在 node2 节点上执行 $ rabbitmqctl stop_app
,那么$ rabbitmqctl cluster_status
命令来查看 running_nodes 这一选项种已经没有了 rabbitmq@node2 这一节点。
如果关闭了集群种的所有节点,则需要确保在启动的时候最后关闭的那个节点是第一个启动的。如果第一个启动的不是最后关闭的节点,那么这个节点会等待最后关闭的节点启动。这个等待时间是 30 秒,如果没有等到,那么这个先启动的节点也会失败。在较新的版本中会有重试机制,默认重试 10 次 30 秒以等待最后关闭的节点启动。在重试失败之后,当前节点也会因失败而关闭自身的应用。
如果最后一个关闭的节点最终由于某些异常而无法启动,则可以通过 $ rabbitmqctl forget_cluster_node
命令来将此节点剔出当前集群。如果集群中的所有节点由于某些非正常因素,比如断电而关闭,那么集群中的节点都会认为还有其他节点在它后面关闭,此时需要调用 $ rabbitmqctl force_boot
命令来启动一个节点,之后集群才能正常启动。
集群节点类型
在使用$ rabbitmqctl cluster_status
命令查看集群状态时会有{nodes, [{disc, ...}]}这一项信息,其中的 disc 标注了 RabbitMQ 节点的类型。 集群的节点类型有两种类型:
- 内存节点:所有的队列、交换器、绑定关系、用户、权限和 vhost 的元数据定义都存储在内存中。
- 磁盘节点:将上述信息存储到磁盘中。
单节点的集群中必然只有磁盘类型的节点,否则当重启 RabbitMQ 之后,所有关于系统的配置信息都会丢失。不过在集群中,可以选择配置部分节点为内存节点,这样可以获得更高的性能。例如将 node2 节点加入 node1 节点的时候可以指定 node2 节点的类型为内存节点,默认为磁盘节点,如下:$ rabbitmqctl join_cluster rabbitmq@node1 --ram
如果集群已经搭建好了,那么也可以使用$ rabbitmqctl change_cluster_node_type {disc,ram}
命令来切换节点的类型,其中 disc 表示磁盘节点,而 ram 表示内存节点。修改步骤为 停止 node2 -> change -> 启动 node2 -> 修改成功 在集群中创建队列、交换器或者绑定关系的时候,这些操作知道所有集群节点都成功提交元数据变更后才会返回。 内存节点可以提供出色的性能,磁盘节点能够保证集群配置信息的高可靠性,如果在这两者之间进行抉择呢?RabbitMQ 只要求在集群中至少有一个磁盘节点,所有其他节点可以是内存节点。当节点加入或者离开集群时,它们必须将变更通知到至少一个磁盘节点。如果只有一个磁盘节点,而且不凑巧的是它刚好崩溃了,那么集群可以继续发送和接收消息,但是不能执行创建队列、交换器、绑定关系、用户,以及更改权限、添加或删除集群节点的操作了。所以在建立集群的时候应该保证有两个或者多个磁盘节点的存在。
在内存节点重启后,它们会连接到预先配置的磁盘节点,下载当前集群元数据的副本。当在集群中添加内存节点时,确保告知其所有的磁盘节点。只要内存节点可以找到至少一个磁盘节点,那么它就能在重启后重新加入集群中。
除非使用的是 RabbitMQ 的 RPC 功能,否则创建队列、交换器和绑定关系的操作做确是甚少,大多数的操作就是生产或者消费消息。为了确保集群信息的可靠性,或者在不确定使用磁盘节点或者内存节点的时候,建议全部使用磁盘节点。
TIP
Why:为什么是可以找到至少一个磁盘节点,那么它就能在重启后重新加入集群中呢,如果找到的这个节点没有保存该内存节点的信息呢?我猜是因为持久化的问题,一定会找到有该内存节点的磁盘节点,直到找到为止。
剔除单个节点
有两种方式将 node2 剥离出当前集群。
第一种方式
首先在 node2 节点上执行$ rabbitmqctl stop_app
或者$ rabbitmqctl stop
命令来关闭 RabbitMQ 服务。之后再在 node1 节点或者 node3 节点上执行$ rabbitmqctl forget_cluster_node rabbitmq@node2 --offline
命令将 node2 节点剔除出去。这种方式适合 node2 节点不再运行 RabbitMQ 的情况。注意--offline
参数的添加让其可以在非运行状态下将 node2 剥离出当前集群。第二种方式
在 node2 上执行$ rabbitmqctl reset
命令。如果不是像上面由于启动顺序的缘故而不得不删除一个集群节点,建议采用这种方式,步骤如下。
# node2 节点上执行
$ rabbitmqctl stop_app
$ rabbitmqctl reset
$ rabbitmqctl start_app
如果从 node2 节点上检查集群的状态,会发现它现在是独立的节点。同样在集群中剩余的节点 node1 和 node3 上看到 node2 已不再是集群中的一部分了。 rabbitmqctl reset 命令将清空节点的状态,并将其恢复到空白状态。当重设的节点是集群中的一部分时,该命令也会和集群中的磁盘节点进行通信,告诉他们该节点正在离开集群。不然集群会认为该节点除了故障,并期望其最终能够恢复过来。
集群节点的升级
如果 RabbitMQ 集群由单独的一个节点组成,那么升级版本很容易,只需关闭原来的服务,然后解压新的版本再运行即可。不过要确保原节点的 Mnesia 中的数据不被变更,且新节点中的 Mnesia 路径的只想要与原节点的相同。或者说保留原节点 Mnesia 数据,然后解压新版本到相应的目录,再将新版本的 Mnesia 路径指向保留的 Mnesia 数据的路径(也可以直接复制到保留的 Mnesia 数据到新版本中相应的目录),最后启动新版本的服务即可。
如果 RabbitMQ 集群由多个节点组成,那么也可以参考单个节点的情形。具体步骤:
- 关闭所有节点的服务,注意采用 rabbitmqctl stop 命令关闭。
- 保存各个节点的 Mnesia 数据。
- 解压新版本的 RabbitMQ 到指定的目录。
- 指定新版本的 Mnesia 路径为步骤 2 中保存的 Mnesia 数据路径。
- 启动新版本的服务,注意先重启原版本中最后关闭的那个节点。
- RabbitMQ 的版本有很多,也会有数据格式不兼容的现象,最好先测试两个版本互通的可能性,然后再在线上环境中实地操作。
- 如果原集群上的配置和数据都可以舍弃,则可以删除原版本的 RabbitMQ,然后在重新安装配置即可;如果配置和数据不可丢弃,则按后续节点保存元数据,之后再关闭所有生产者并等待消费者消费完队列中的所有数据,紧接着关闭所有消费者,然后重新安装 RabbitMQ 并重建元数据。
- 当前如果有个新版本的集群,那么从旧版本迁移到新版本的集群中也是一个好的升级办法。
单机多节点配置
在一台机器上部署多个 RabbitMQ 服务节点,需要确保每个节点都有独立的名称、数据存储位置、端口号(包括插件的端口号)等。
我们在主机名称为 node1 的机器上创建一个由 rabbit1@node1、rabbit2@node1、rabbit3@node1 这 3 个节点组成 RabbitMQ 集群。
首先需要确保机器上已经安装了 Erlang 和 RabbitMQ 的程序。其次,为每个 RabbitMQ 服务节点设置不同的端口号和节点名称来启动相应的服务。
# node1 执行,执行完后服务就启动了
$ RABBITMQ_NODE_PORT=5672 RABBITMQ_NODENAME=rabbit1
$ RABBITMQ_NODE_PORT=5673 RABBITMQ_NODENAME=rabbit2
$ RABBITMQ_NODE_PORT=5674 RABBITMQ_NODENAME=rabbit3
在启动 rabbit1@node1 节点的服务之后,继续启动 rabbit2@node1 和 rabbit3@node1 服务节点会遇到启动失败的情况。这种情况大多数是由于配置发生了冲突而造成后面的服务节点启动失败,需要进一步确认是否开启了某些功能,比如 RabbitMQ Management 插件。如果开启了 RabbitMQ Management 插件,就需要为每个服务节点配置一个对应插件端口号,具体内容如上执行脚本。
启动各节点服务之后,将 rabbit2@node1 节点加入 rabbitmq@node1 的集群之中:
# node1 执行
$ rabbitmqctl -n rabbit2@node1 stop_app
$ rabbitmqctl -n rabbit2@node1 reset
$ rabbitmqctl -n rabbit2@node1 join_cluster rabbit1@node1
$ rabbitmqctl -n rabbit2@node1 start_app
紧接着执行相似的操作将 rabbit3@node1 也加入进来。 RabbitMQ 的单机多节点配置大多用于实验性论证,如果需要在真实生产环境中使用,最好还是搭建一个多机多节点的集群。搭建集群不仅可以扩容,也能有效地进行容灾。
查看服务日志
RabbitMQ 日志中包含各种类型的时间,比如连接尝试、服务启动、插件安装即解析请求时的错误等。
RabbitMQ 的日志默认存放在 $RABBITMQ_hOME/var/log/rabbitmq 文件夹内。在这个文件夹内 RabbitMQ 会创建两个日志文件:RABBITMQ_NODENAME-sasl.log 和 RABBITMQ_NODENAME.log。 SASL(System Application Support Libraries,系统应用程序支持库)是库的集合。当 RabbitMQ 记录 Erlang 相关信息时,它会将日志写入文件 RABBITMQ_NODENAME-sasl.log 中。举例来说,可以在这个文件中找到 Erlang 的崩溃报告,有助于调试无法启动的 RabbitMQ 节点。
如果想查看 RabbitMQ 应用服务的日志,则需要查阅 RABBITMQ_NODENAME.log 这个文件,即 RabbitMQ 服务文件就是它。
不一一介绍,可以查看日志解决的如下,仅例举部分:
- 启动 RabbitMQ 服务。 注意可配置的打印日志级别,可以看到 RabbitMQ 的版本号、Erlang 的版本号、RabbitMQ 服务节点名称、cookie 的 hash 值、RabbitMQ 配置文件地址、内存限制、磁盘限制、默认账户 guest 的创建及权限配置、显示启动的插件、统计值信息的初始化日志等。
- 关闭 RabbitMQ 服务。
- 建立集群。
- 其他。 如客户端与 RabbitMQ 建立连接。
另外注意一些功能:
- 日志过多后,设置轮换日志。命令为
$ rabbitmqctl rotate_logs {suffix}
。 - RabbitMQ 默认会创建一些交换器,其中 amq.rabbitmq.log 就是用来收集 RabbitMQ 日志的。我们可以创建日志队列来绑定 amq.rabbitmq.log 来收集相关级别的日志。可以收集到所有 info 节点的日志,但是不能区分是哪个具体节点的日志。
单节点故障恢复
单节点故障包括:机器硬件故障、机器掉电、网络异常、服务进程异常。
运维知识,略。
集群迁移
运维知识,略。
另外注意一些要点:
- RabbitMQ 集群迁移包括元数据重建、数据迁移、以及与客户端连接的切换。
- 自动化迁移。
集群监控
Web 管理截面提供了很多的统计值信息:如发送速度、确认速度、消费速度、消息总数、磁盘读写速度、句柄数、Socket 连接数、Connection 数、Channel 数、内存信息等。
运维知识,略。
另外注意一些要点:
- 通过 HTTP API 接口提供监控数据。
- 通过客户端提供监控数据。
- 检测 RabbitMQ 服务是否健康。
- 元数据管理和监控
参考文献
- [RabbitMQ实战指南]