本章,我们讨论有关RabbitMQ的容错性,消息一致性及高可用性。RabbitMQ可以作为集群节点来运行,因此RabbitMQ通常被归为分布式消息系统,对于分布式消息系统,我们的关注点通常是一致性与可用性。
我们为什么要讨论分布式系统的一致性与可用性,本质在于两者描述的是系统在失败的情况下表现如何。在实际应用中,网络连接失败、服务器宕机,硬盘损坏,服务器由于GC暂时不可用,网络连接丢失或速度慢,所有这些异常都会导致数据中断、丢失或冲突等问题。事实证明,在所有的这些故障模式下,我们是无法同时兼顾最终一致性(无数据丢失,无数据差异)和可用性。系统一致性与可用性就像光的两端,你必须选择其中一种作为首要关注点。
本章,我们将深入讨论什么样的配置会导致确认写场景下的数据丢失。之前篇幅中我们有讨论,在生产者、代理节点和消费者之间存在一个消息传递的责任链关系,一旦消息被传递到代理节点,那么代理节点就要负责消息的安全性。当代理节点发送确认消息给生产者之后,我们期待的是消息不再丢失,但事实上,即便生产者收到了消息确认,消息依然存在丢失的可能性,这依赖于代理与生产者的实际配置如何。
单节点持久化原语
持久化消息队列/交换器
RabbitMQ支持两种类型的消息队列:持久化队列和非持久化队列,所有的队列都是将消息保存到Mnesia数据库中,区别在于在RabbitMQ服务节点启动时,持久化队列会重新声明,因此当节点重启、系统宕机或者系统异常失败时,只要数据仍在,那么队列仍然存在。相反的,非持久化队列和交换器在节点启动时会被删除。
持久化消息
声明了持久化队列并不意味着当节点重启时消息仍旧可以正常保存除非生产者将消息声明为持久化的。尽管持久化消息会加重消息代理负担,但如果实际业务场景无法接受消息丢失,那么,持久化消息也是不二之选。
服务集群与队列镜像
为了避免单个消息代理异常出现的消息丢失,我们可以冗余处理。我们可以在一个服务集群中添加多个RabbitMQ节点,并通过跨多个服务节点复制队列实现消息冗余。在这种架构下,即便出现单个节点失败的情况也不会导致数据丢失的问题发生。
一个镜像队列包含以下内容:
-
一个主队列负责接收所有读和写
-
一个或多个队列镜像,镜像负责从主队列中接收所有的消息和元数据,镜像并不是为了扩展消息队列的读取性能,只是单纯的数据冗余而存在。
我们可以简单的通过设置策略即可实现队列镜像。比如,可以选择复制要素,甚至指定镜像队列所在节点: -
ha-mode: all
-
ha-mode: exactly, ha-params: 2(one master and one mirror)
-
ha-mode: nodes, ha-params:rabbit@node1, rabbit@node2
生产者确认
为了达到一致性写的目的,我们需要生产者确认,否则可能会导致消息丢失。一旦消息被写到磁盘中,消息确认就会发送给生产者。注意,RabbitMQ并不是收到消息就执行消息写操作,而是基于定时(每几百毫秒之内)的消息写逻辑。对于镜像队列来说,只有所有的镜像都完成了消息写入时,才会发送确认消息,这也就是说,使用生产者确认机制会导致消息延迟,但如果业务场景关注的是消息的安全性,那么使用生产者确认也是非常必须的。
消息队列故障转移
当某个消息代理关闭或者宕机,位于当前节点上的所有主队列都会随之消失。接下来,集群会从剩下的镜像中选取新的队列,并将其提升为主队列。
消息代理Broker 3宕机之后,Queue C在代理Broker 2上的镜像被提升为了主队列,同时在代理B