【RabbitMQ】消息队列中间件学习之RabbitMQ(9)

1. 集群搭建

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

但是RabbitMQ集群并不能保证消息的万无一失。当集群中的一个RabbitMQ节点崩溃时,该节点上的所有队列上的消息也会丢失。RabbitMQ集群中的所有节点都会备份所有的元数据信息,包括:
①队列元数据:队列的名称及属性;
②交换器:交换器的名称及属性;
③绑定关系元数据:交换器与队列或者交换器与交换器之间的绑定关系数据;
④vhost元数据:为vhost内的队列、交换器和绑定提供命名空间及安全属性。

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

1.1 多机多节点配置

**多机多节点是指多台机器组成一个RabbitMQ集群。**具体过程如下:
第一步需要配置各个节点的host文件/etc/hosts,让节点之间能够识别对方的存在
第二步需要编辑RabbitMQ的cookie文件,确保各个节点的cookie文件使用的是同一个值。可以读取node1节点的cookie值,将其复制到node2和node3节点中。cookie默认文件路径为/var/lib/rabbitmq/.erlang.cookie或者$HOME/.erlang.cookie。这里的cookie相当于密钥,各节点间通过密钥互相认证;
第三步配置集群,配置集群有三种方式:①通过rabbitmqctl工具配置,②通过rabbitmq.config文件配置,③通过rabbitmq-autocluster插件配置。常用的方式为rabbitmqctl工具配置,具体过程如下:

[root@node2 ~]# rabbitmqctl stop_app
[root@node2 ~]# rabbitmqctl reset
[root@node2 ~]# rabbitmqctl join_cluster rabbit@node1
[root@node2 ~]# rabbitmqctl start_app

如果关闭了集群中的所有节点,需要确保在启动的时候最后关闭的节点是第一个启动的,否则这个不是最后关闭的节点会等待最后关闭的节点启动,等待时间是30秒,如果没有等到,这个先启动的节点也会失败,默认会重试10次30秒,如果重试还是失败,当前节点会因失败关闭自身的应用。

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

1.2 集群节点类型

RabbitMQ中的每一个节点,要么是内存节点,要么是磁盘节点。内存节点是指将所有的队列、交换器、绑定关系、用户、权限和vhost的元数据定义都存储在内存中,而磁盘节点则将这些存储到磁盘中。单节点的集群中只有磁盘类型的节点,这样是为了保证重启RabbitMQ时,所有关于系统的配置信息不会丢失。而在集群中可以配置部分节点为内存节点,以获得更高的性能。例如:

[root@node2 ~]# rabbitmqctl join_cluster rabbit@node1 --ram

默认不添加“–ram”参数则表示此节点为磁盘节点。

如果集群已经搭建好了,那么可以使用rabbitmqctl change_cluster_node_type{disc,ram}命令来切换节点的类型,其中disc表示磁盘节点,而ram表示内存节点。

**RabbitMQ要求在集群中至少有一个磁盘节点,其他节点可以是内存节点。**当节点加入或离开集群时,它们必须将变更通知到至少一个磁盘节点,如果只有一个磁盘节点且其刚好崩溃了,那么集群可以继续发送或接受消息,但是不能执行创建队列、交换器、绑定关系、用户、以及更改权限、添加或删除集权节点的操作,所以在建立集群的时候应该保证有两个或多个磁盘节点的存在。

当内存节点重新启动后,它们会连接到预先配置的磁盘节点,下载当前集群元数据的副本。为确保集群消息的可靠性,或者不确定使用哪个节点类型时,建议全部使用磁盘节点。

1.3 剔除单个节点

如何将一个节点从集群中剔除呢?有两种常用的方式可以将节点剔除出集群

  • 第一种,在需要剔除的节点上执行rabbitmqctl stop_app或stop命令来关闭rabbitmq服务。之后在其他的节点上执行rabbitmqctl forget_cluster_node rabbit@xxx剔除节点,这种方式适合当前节点不再运行RabbitMQ的情况。注意rabbitmqctl forget_cluster_node命令可能会用到的“–offline”参数,如果不添加这个参数,则要保证被剔除的节点中的RabbitMQ服务处于运行状态,使用"–offline"参可以让其在非运行状态下将节点剥离出当前集群。
  • 第二种方式是在需剔除的节点上执行rabbitmqctl reset命令。适应不需要由于启动顺序的缘故而不得不删除一个集群节点的情况。当重设的节点是集群中的一部分时,该命令也会和集群中的磁盘节点进行通信,告诉它们该节点正在离开集群。

1.4 集群节点的升级

如果RabbitMQ集群由单独的一个节点组成,只需关闭原来的服务,然后解压新的版本运行即可。不过需要确保原节点的Mnesia中的数据不被变更,且新节点中的Mnesia路径的指向要与原节点中的相同。

如果RabbitMQ集群由多个节点组成,那么可以参考单个节点的情形:
①使用rabbitmqctl stop命令关闭所有节点的服务;
②保存各个节点的Mnesia数据;
③解压新版本的RabbitMQ到指定的目录;
④指定新版本的Mnesia路径为步骤二中的数据路径;
⑤启动新版本的服务,注意需要启动原版本中最后关闭的节点

如果要保存原始的配置和数据,则需要首先保存元数据,然后关闭所有生产者,并等待消费者消费完队列中的所有数据,然后关闭所有消费者,然后重新安装RabbitMQ并重建元数据。

1.5 单机多节点配置

**在一台机器上部署多个RabbitMQ服务节点,需要确保每个节点都有独立的名称、数据存储位置、端口号等。**如果发生后续节点启动报错,大多数是由于配置问题,比如RabbitMQ Management插件配置了一样的端口号等。启动完各节点的服务之后,将节点添加到集群中就可以了。在实际应用时,建议搭建多机多节点集群,单机多节点可在实试验时应用

2. 查看服务日志

RabbitMQ日志中包含各种类型的事件,比如连接尝试、服务启动、插件安装及解析请求时的错误等。RabbitMQ的日志默认存放在$RABBITMQ_HOME/var/log/rabbitmq文件夹内。在该目录下,RabbitMQ会创建两个文件:RABBITMQ_NODENAME-sasl.log和RABBITMQ_NODENAME.log。

SASL(System Application Support Libraries,系统应用程序支持库)是库的集合,作为Erlang-OTP发行版的一部分。它们帮助开发者在开发Erlang应用程序时提供一系列标准,其中之一是日志记录格式。所以RABBITMQ_NODENAME-sasl.log记录的是Erlang相关信息的日志,例如Erlang的崩溃报告等。而RABBITMQ_NODENAME.log是RabbitMQ的应用服务日志。

2.1 启动RabbitMQ服务

启动RabbitMQ服务可以使用rabbitmq-server -detached命令,这个命令会顺带启动Erlang虚拟机和RabbitMQ应用服务,而rabbitmqctl start_app用来启动RabbitMQ应用服务。注意,RabbitMQ应用服务启动的前提是Erlang虚拟机是运转正常的。

在RabbitMQ中,日志级别有none、error、warning、info、debug这5种,下一层级别的日志输出均包含上一层级别的日志输出,如warning级别的日志包含warning和error级别的日志。none表示不输出日志。日志级别可以通过rabbitmq.config配置文件中的log_levels参数来进行配置。默认为[{connection, info}]。

2.2 关闭RabbitMQ服务

如果使用rabbitmqctl stop命令,会将Rrlang虚拟机一同关闭,而rabbitmqctl stop_app只会关闭RabbitMQ应用服务。

2.3 建立集群

具体过程参考第一节

2.4 其他

RabbitMQ中可以通过rabbitmqctl rotate_logs {suffix}命令来轮换日志,比如:rabbitmqctl rotate_logs .bak会在日志目录下建立新的日志文件,并将老的日志添加.bak的方式区分保存。此外也可以通过程序化的方式查看服务日志,设置相应的逻辑规则,将有用的日志信息过滤并保存起来以便后续的服务应用
例如,RabbitMQ默认会创建一些交换器,其中amq.rabbitmq.log就是用来收集RabbitMQ日志的,集群中所有的服务日志都会发往这个交换器中。这个交换器的类型为topic,可以收集debug、info、warning和error这四个级别的日志。创建4个日志队列queue.debug,queue.info,queue.warning,queue.error,分别采用debug、info、warning和error这4个路由键来绑定amq.rabbitmq.log。

3. 单节点故障恢复

配置数据节点冗余可以有效防止由于单点故障而降低整个集群的可用性、可靠性。

单点节点的故障主要包括:机器硬件故障、断电、网络异常、服务进程异常

**单点硬件故障包括机器硬盘、内存、主板等故障造成的死机,无法从软件层面恢复。**此时需要在集群种的其它节点执行rabbitmqctl forget_cluster_nodes {nodename}命令来将故障节点剔除。如果之前有客户端连接到故障节点,需要将故障节点的IP地址从连接列表种删除,并让客户端重新连接以恢复整个应用。

如果遇到断电故障,需要接通电源后重启机器,此时节点上的RabbitMQ处于Stop状态,此时需要从其它节点执行rabbitmqctl forgrt_cluster_nodes {nodeName}将节点从集群种剔除,然后剔除当前故障RabbitMQ种的Mnesia数据,然后重启RabbitMQ服务,最后将其作为新节点加入到当前集群种,否则可能会引起网络分区

网线松动或者网卡损坏都会引起网络故障的法伤,为保险起见,建议先关闭故障机器的RabbitMQ进程,然后对网线进行更换或者修复操作,而网卡故障极易引发网络分区,如发生网卡故障而网络分区尚未发生时,应第一时间关闭此节点上的RabbitMQ进程,等待网卡修复后重启,如果已经发生了网络分区,可以参考后续内容手动恢复网络分区。

对于服务异常时,如果RabbitMQ进程非预期终止,需要判断风险是否可控,如不可控建议抛弃节点,一般情况下重新启动服务进程即可

4. 集群迁移

RabbitMQ种的集群迁移更多是用来解决集群故障不可短时间内修复而将所有的数据、客户端等迁移到新的集群中,以确保服务的可用性。相比于单节点故障,集群故障的危害性更大,例如IDC整体停电、网线被挖断等。
RabbitMQ集群迁移包括元数据重建数据迁移以及与客户端连接切换

4.1元数据重建

元数据重建是指在新的集群中创建原集群的队列、交换器、绑定关系、vhost、用户、权限和Prameter等数据信息。元数据重建后才可以将原集群中的消息及客户端连接迁移过来。

手动创建元数据是十分低效的,可以通过Web管理页面种的Download broker definitions按钮下载集群的元数据信息文件,之后再点击Upload broker definitions按钮上传元数据文件,如果新集群的数据与当前数据有冲突,对于交换器、队列及绑定关系这类非可变对象会报错,对于其它可变对象如Parameter、用户等会被覆盖,如果导入过程种失败会,则只有部分数据加载成功。

上述方式需要考虑三个问题:
第一,如果原集群突发故障,又或者开启RabbitMQ Management插件的那个节点机器故障不可修复,就无法获取原集群的元数据metadata.json。此时,可以采用一个通用的备份任务,在元数据有变更或者达到某个存储周期时将最新的metadata.json备份到另一处安全的地方。
第二个问题时如果新旧集群的RabbitMQ版本不一致出现的异常。一般情况下,RabbitMQ是能够向下兼容的,在高版本的RabbitMQ中可以上传低版本的元数据文件。
第三个问题就是通过上述方法将元数据在新集群中重建后,则所有的队列都只会落到同一个集群节点上,而其他节点处于空置状态。一般有两种方式解决,第一种是通过HTTP API接口在新集群上创建相应的数据,另外一种是随机连接集群中不同节点的IP,然后再创建队列等信息。具体过程是客户端首先通过连接不同的IP地址来创建不同的connection和channel,然后将channel存入一个缓冲池,之后随机从channelList中获取一个channel,再根据queueList中的信息创建相应的队列。每一个channel对应一个connection,而每一个connection又对应一个IP,这样串起来就能保证connectionList中不会遗留任何节点,最终实现与第一种方式相同的功能。

4.2 数据迁移和客户端连接的切换

首先需要将生产者的客户端与原RabbitMQ集群的连接断开,然后再与新的集群建立新的连接,这样就可以将新的消息流入到新的集群中。

之后就需要考虑消费者客户端的事情,一种方式是等待原集群中的所有消息全部消费完成后再将连接断开,再建立新的连接进行消费。但如果出现因服务故障无法等到的情况,此时就不能等待消费完原集群中的消息,需要及时将消费者客户端的连接切换到新的集群中,那么原集群中就会残留部分未消费的消息,此时需要进一步处理。如果原集群损坏,则可以等待修复之后将数据迁移到新集群中,否则会丢失数据。
数据迁移的主要原理是将原集群中的数据消费出来,存入一个缓存区中,用另一个线程读取缓存区中的消息再发布到新的集群中。RabbitMQ提供的FederationShovel插件即可实现类似的功能。

4.3 自动化迁移

要实现集群自动化迁移,需要在使用相关资源时就做好准备。与生产者消费者客户端相关的是交换器、队列及集群的信息,当这3类资源发生改变时需要让客户端迅速感知,以便进行相应的处理。**可以将相应的资源加载到Zookeeper的相应节点中,客户在客户端为对应的资源节点加入watcher来感知变化。**当然这个功能使用etcd(一个分布式一致性k-v存储系统)或者集成到公司层面的资源配置中心会更加标准、高效。

5. 集群监控

RabbitMQ扩展的RabbitMQ Management插件能够提供一定的监控功能,包括:发送速度、确认速度、消费速度、消息总数、磁盘读写速度、句柄数、Socket连接数、Connection数、Channel数、内存信息等。

5.1通过HTTP API接口提供监控数据

RabbitMQ Management插件不仅提供了优秀的Web管理界面,还提供了HTTP API接口以供调用。
集群节点的信息可以通过/api/nodes接口来获取;采集程序通过定时调用HTTP API接口获取JSON数据,然后使用GSON对其进行JSON解析之后再进行持久化处理。对于这种基于时间序列的数据非常适合使用OpenTSDB(基于HBase的分布式,可伸缩的时间序列数据库)来进行存储。监控管理系统可以根据用户的检索条件从OpenTSDB中获取相应的数据并展示到页面之中。同时还可以对实时采集的数据进行分析处理,并报告异常。

对于交换器而言的数据采集可以调用/api/exchange/vhost/name接口(操作默认主机时需要将“/”转义成%2F否则会异常);队列的数据采集接口为/api/queues/vhost/name。

5.2 通过客户端提供监控数据

除了HTTP API接口可以提供监控数据,Java中的Channel接口中也提供了两个方法来获取数据,方法如下:

# 用来查询队列中的消息个数,可以为监控消息堆积的情况提供数据
public long messageCount(String queue) throws IOException {
	com.rabbitmq.client.impl.AMQImpl.Queue.DeclareOk ok = this.queueDeclarePassive(queue);
	return (long)ok.getMessageCount();
}
# 用来查询队列中的消费者个数,可以为监控消费者的情况提供数据
public long consumerCount(String queue) throws IOException {
	com.rabbitmq.client.impl.AMQImpl.Queue.DeclareOk ok = this.queueDeclarePassive(queue);
	return (long)ok.getConsumerCount();
}

除了上述的两个方法外,也可以通过连接的状态进行监控,Java客户端中的Connection接口提供了如下方法:

public void addBlockedListener(BlockedListener listener) {
	this.blockedListeners.add(listener);
}
public void addShutdownListener(ShutdownListener listener) {
	ShutdownSignalException sse = null;
	synchronized(this.monitor) {
		sse = this.shutdownCause;
		this.shutdownListeners.add(listener);
	}

	if (sse != null) {
		listener.shutdownCompleted(sse);
	}
}

其中addBlockedListener(BlockedListener listener)方法用来监听连接阻塞信息,addShutdownListener(ShutdownListener listener)方法用来监听连接关闭信息。
另外,用户客户端还可以自定义一些数据进行埋点,推荐引入metrics工具(比如com.cohadale.metrics.*)来进行埋点。

5.3 检测RabbitMQ服务是否健康

为判断RabbitMQ是否具备服务外部请求的能力,可以使用AMQP协议来构建一个类似于TCP协议中的Ping检测程序,当无法建立TCP协议层面的连接,获取无法构建AMQP协议层面的连接,或者构建连接超时时,可以判定RabbitMQ服务处于异常状态而无法正常为外部应用提供相应的服务。

除了基于上述的行为构建程序外,还可以使用RabbitMQ Management插件提供的/api/aliveness-test/vhost的HTTP API接口,通过3个步骤来验证RabbitMQ服务的健康性:
①创建一个以“aliveness-test”为名称的队列来接收测试消息;
②使用该队列名称作为消息的路由键,将消息发往默认交换器;
③到达队列时就消费消息,否则报错。
这个HTTP API接口背后的检测程序也称之为aliveness-test,其运行在Erlang虚拟机内部,因此它不会受到网络问题的影响,且不会删除创建的队列,还能避免元数据事务对Mnesia数据库造成的压力。如果RanbbitMQ服务完好,调用/api/aliveness-test/vhost接口会返回{“status”:“ok”},HTTP状态码为200。

此外还可以使用/api/healthchecks/node和/api/healthchecks/node/node这两个接口对当前节点或指定节点进行健康检查,包括RabbitMQ应用、信道、队列是否正常运行,是否有告警产生等。

5.4 元数据管理与监控

管控的介入会降低消息中间件的灵活度,但是可以增强系统的可靠性,比如可以建立专用的“元数据审查系统”来配置对应的元数据资源。这里提供一种思路:所有的业务应用都需要通过元数据审查系统来申请创建相应的元数据信息,申请后进行审批,之后再在数据库中存储和在RabbitMQ集群中相应的元数据,在数据库和RabbitMQ集群之间可以添加一个一致性校验程序来检测元数据不一致的记录信息,上传至监控管理系统,最后进行人工介入处理。元数据一致性检测程序可以通过/api/definitions的HTTP API接口来获取集群的元数据信息,通过解析之后与数据库中的记录一一比对,查看是否有不一致的地方。

参考资料:

  1. 《RabbitMQ实战指南》 朱忠华 著
  2. https://www.cnblogs.com/Jscroop/p/14319117.html#_label3_0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

镰刀韭菜

看在我不断努力的份上,支持我吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值