容灾和高可用性入门
这篇文章写于 2020 年。此后,Tanzu RabbitMQ 添加了许多容灾功能,例如温备复制 (Warm Standby Replication)。
在这篇文章中,我将介绍我在企业中收到的关于 RabbitMQ 最常被问到的问题。
如何才能使 RabbitMQ 高可用,以及推荐哪些架构/实践来容灾?
RabbitMQ 提供了支持高可用性和容灾的功能,但在我们直接深入之前,我想先做一些铺垫。首先,我想回顾一下业务连续性计划 (Business Continuity Planning),并用这些术语来界定我们的需求。然后,我们需要对什么是可能的设定一些预期。有一些基本定律,如光速和 CAP 定理,它们都对我们选择哪种 DR/HA 解决方案有严重影响。
最后,我们将审视可用的 RabbitMQ 功能及其优缺点。
高可用性与灾难恢复有何区别?
高可用性通常指在发生局部故障(例如服务器或磁盘故障,或有限的网络中断)时,从已部署软件的一个实例自动故障转移到另一个实例。故障对可用性的影响应被视为不可见或极小。
灾难恢复通常指对重大事故(灾难)的响应,例如整个数据中心的丢失、大规模数据损坏或任何其他可能导致服务和/或数据完全丢失的故障。灾难恢复旨在避免系统的永久性部分或全部故障或丢失,通常涉及构建一个与主站点在地理位置上分离的冗余系统。
两者都属于业务连续性的范畴。
业务连续性规划 101
最终,我们希望能够从重大事故中快速恢复(灾难恢复),并在较小的事故中保持持续的可用性(高可用性)。
重大事故可能涉及因火灾、停电或极端天气而导致整个数据中心的丢失。较小的事故可能涉及数据中心的部分丢失,或仅仅是磁盘驱动器或服务器的丢失。
实施一个能够从故障和灾难中恢复的系统,无论是在金钱成本上还是性能开销上都可能非常昂贵。归根结底,这种实施将是实施成本与数据丢失及服务中断成本之间的一种权衡。
为了达到这种平衡,我们需要考虑:
- 可用的冗余/可用性工具及其局限性
- 数据类型以及数据丢失对业务造成的相关成本
首先,我们将介绍在发生事故时定义我们可接受的数据丢失和不可用时间窗口的可衡量目标,然后我们将涵盖上述考虑因素。
数据丢失与可用性目标
作为业务连续性计划的一部分,企业必须就灾难恢复确定两个关键目标。
恢复点目标 (RPO) 确定了企业在发生重大事故时可以接受的最长数据丢失时间段。显而易见的答案是 0 秒,意味着没有数据丢失,但这很难实现,即使可能实现,也可能带来严重的负面影响,如成本或性能问题。其他值可能是 1 小时或 24 小时,通常较高的值更容易以较低的实施成本实现。最终,这将是负面影响与数据丢失带来的业务影响之间的权衡。

恢复时间目标 (RTO) 确定了不可用的最长时间段,即恢复并重新投入运营所需的时间。显而易见的答案可能是 0 秒,意味着无缝故障转移并保持持续可用,但这在技术上可能无法实现;即使能实现,其成本也可能远高于企业愿意支付的金额。

这些可用性和数据丢失的窗口可能会也可能不会重叠。

最终,RPO 和 RTO 都是在维持业务运营的收益与各种负面影响(如成本和性能影响)之间进行的权衡。
数据类型与业务影响

持久性数据周而复始地存在。如果企业丢失了其最有价值的持久性数据,可能会导致公司倒闭。瞬态数据的生命周期较短,它可能是两个系统之间传输的数据,或者是随时可以被清理的数据。虽然丢失瞬态数据可能会产生影响,但不太可能导致公司倒闭。
权威数据(Source-of-truth data)要么是主数据,和/或在其他任何地方都不存在的数据。次级数据是权威数据的副本(可能经过过滤和转换),可以从原始持久存储中恢复。次级数据的示例包括:
- 缓存存储的次级数据,可以从持久存储中重新加载。
- 微服务在属于另一个服务的数据库中存储的少量数据。
- 分布式日志将数据库数据修改流式传输到其他系统。
丢失次级数据可能导致:
- 在从权威数据源恢复时,导致可用性丧失。
- 由于丢失“热”数据而导致的性能容量下降。
最具破坏性的数据丢失是权威持久性数据的丢失,而影响最小的是瞬态次级数据。
在持久/瞬态连续体中,持久端是数据库,瞬态端是消息队列(如 RabbitMQ),中间是分布式日志(例如 Apache Kafka)。数据库、消息队列和分布式日志既可以存储权威数据,也可以存储次级数据。缓存和搜索引擎通常存储次级数据。

RabbitMQ 存储瞬态数据。消息仅仅是正在从源传输到一个或多个目的地的数据。读取后,消息即被销毁。如果你希望该消息中的数据能够长期存在,则需要将这些消息数据写入某种持久性存储,如数据库、文件系统或对象存储。虽然消息是瞬态的,但它仍然可能是权威数据,因为它在其他任何地方都还不存在;如果丢失,将永远无法找回。
在定义 RPO 和 RTO 值时,考虑数据在上述连续体中的位置非常重要。因为这些值是实施成本与服务中断成本之间的平衡,请确保为存储和服务的特定类型的数据选择合适的平衡点。
数据冗余工具 - 备份与复制
数据库、缓存和消息队列等数据系统通常提供一种或两种保护数据免受故障影响的方法。
所有数据库系统都提供某种备份功能。可以按计划(例如每晚或每周)进行完整备份,此外还可以提供更高频率的增量备份,例如每 15 分钟一次。
关于备份需要记住的重要一点是,从备份中恢复涉及潜在的数据丢失,因为自上次备份以来可能有新数据进入系统。从备份中恢复也可能需要时间——将备份文件移动到其目的地的时间,以及数据系统从这些文件恢复数据的时间。
另一个常用功能是复制。即将数据修改从一个节点流式传输到另一个节点,意味着数据现在至少存在于两个位置。复制有两种形式:同步或异步,两者之间存在巨大差异。
在同步复制中,客户端只有在操作被复制到其他节点后,才会收到操作成功的确认。这是唯一一种不丢失数据的复制形式,但代价是:
- 等待操作被复制会增加延迟
- 如果节点之间的网络中断,可能会丧失可用性
- 如果辅助节点宕机,可能会丧失可用性
同步复制通常是数据中心或云区域内高可用性的解决方案。故障转移通常是自动且快速的,对客户端的影响极小或没有影响。一些数据系统使用异步复制来实现高可用性,这提供最终一致性(伴随潜在的数据丢失)。
在异步复制中,客户端在本地提交操作后即获得成功确认。操作在后台进行复制,这意味着客户端没有额外的延迟,如果主节点和辅助节点之间的网络中断,也不会丧失可用性。缺点是主节点和辅助节点之间会有滞后,这意味着在主节点丢失的情况下可能会发生数据丢失。
异步复制和备份通常是灾难恢复的解决方案。
基本限制
在设计高可用性和灾难恢复策略时,我们需要考虑一些基本限制,例如光速和 CAP 定理。这些限制会影响我们所选 RPO 和 RTO 值的成本和可行性。
CAP 定理指出,在发生网络分区的情况下,您可以选择一致性或可用性,但不能两者兼得。它使用 C 代表一致性 (Consistency),A 代表可用性 (Availability),P 代表分区容错性 (Partition tolerance)。我们必须始终选择 P,但只能从 A 或 C 中选择其一。
CAP 将系统归类为:
- AP - 分区网络中的可用性
- CP - 分区网络中的一致性
如果由于缺乏与对等节点的连接而无法满足必要的冗余水平,CP 系统将失去可用性。CP 系统同步复制操作,并仅在这些操作安全地提交到多个节点后才确认给客户端。这以更高的延迟为代价避免了数据丢失。
当一致性是最重要的考虑因素时,您会选择 CP 系统。
即使未能达到所需的冗余水平,AP 系统也会保持可用。AP 系统异步复制操作,但这如果节点丢失可能会导致数据丢失,这种丢失是由于复制滞后造成的。其优点是延迟较低,因为操作可以立即向客户端确认。
当可用性和/或延迟是最重要的考虑因素时,您会选择 AP 系统。
单个数据中心
在单个数据中心内,您可以选择 AP 或 CP 系统,许多数据系统都是可配置的,允许您针对可用性或一致性进行调整。
在单个数据中心内,网络可以提供高度的可靠性和低延迟。在这种环境下,CP 系统使用基于法定人数(多数)的复制算法可以使不可用成为一种罕见事件。基于法定人数的 CP 系统可以容忍少数节点的故障或网络隔离,并在不丢失数据的情况下提供可用性,是良好的高可用性解决方案。
多个数据中心
数据中心之间的网络可靠性和延迟远逊于局域网,构建一个具有可接受的可用性和延迟的 CP 系统充其量是具有挑战性的,很可能不可行。
AP 系统可以在多个数据中心之间构建,但它们增加了数据丢失的可能性以及数据丢失窗口的大小。
机架感知 (Rack Awareness)
这是数据系统的一项功能,可确保数据在机架、可用区或数据中心(即基础设施中任何类型的故障域)之间进行复制。其核心思想是,如果数据被复制了,但仍然仅存在于单个机架上,那么丢失整个机架就意味着丢失数据。机架感知是一种额外的弹性功能。

当分布在不同的可用区 (AZ) 时,一个可用区的丢失不会导致数据丢失或可用性丧失。

高可用性与灾难恢复考虑因素概述
- 高可用性是指在面对故障时保持可用的能力
- 灾难恢复是指在发生灾难后以受限的数据丢失和不可用时间恢复的能力
- 恢复点目标 (RPO) 定义了灾难发生时可接受的数据丢失时间段。
- 恢复时间目标 (RTO) 定义了恢复所需的最长时段(即不可用时间)。
- RPO 和 RTO 是实施成本与数据丢失/服务中断成本之间的权衡。
- 同步复制以可用性和额外延迟为代价提供数据安全。这是 RPO 为 0 秒时的唯一选择。通常不适用于多数据中心环境。
- 异步复制提供更高的可用性和更低的延迟,但代价是在故障转移时存在潜在的数据丢失——当 RPO > 0 分钟时是不错的选择。通常兼容多数据中心。
- 备份恢复速度可能较慢,但具有其他优势,例如能够进行时间回溯。
- 机架感知为复制的数据存储增加了额外的弹性。
现在让我们针对我们所涵盖的内容,来看看 RabbitMQ 的功能。
RabbitMQ
RabbitMQ 支持:
- 多个节点的集群
- 同步复制 - 复制队列
- 异步集群间消息路由 - 交换机联邦和 Shovels
- 有限的备份支持
- 有限的机架感知支持
接下来我们将了解如何将这些功能用于高可用性和灾难恢复。
高可用性
为了实现高可用性,我们建议在单个数据中心或云区域内对多个 RabbitMQ 代理进行集群,并使用复制队列。集群需要代理之间高度可靠、低延迟的链路。强烈不建议跨广域网 (WAN) 进行集群。
集群与可用区
AWS、Azure 和 GCP 中的大多数区域都提供多个可用区。可用区本质上是由超可靠、低延迟链路连接但地理位置不分离的数据中心。在这些云中跨可用区集群可以提供比仅在一个可用区托管集群更高的可用性。但是,如果云服务提供商对跨可用区数据传输收费,这可能会增加成本。
多可用区集群是高可用性的一个不错选择,但由于缺乏地理位置上的分离,可能无法满足您对灾难恢复的需求。需要注意的是,可用区的概念并不标准化,其他云平台对该术语的使用可能更为宽松。
RabbitMQ 提供两种类型的复制队列:镜像队列 (HA 队列) 和法定人数队列 (Quorum Queues)。这些队列类型使用同步复制,并在面对服务器、磁盘和网络等故障时提供数据安全和高可用性。
集群与多个数据中心
我们强烈不建议跨广域网进行集群,因为这会受到网络分区的影响。跨公共互联网的隧道链路根本不可行。提供商的租赁链路较好,但也是一个有风险的选择。如果企业拥有连接其数据中心的光纤,且该光纤高度可靠且具有持续的低延迟(类似于本地机房的可用区),那么这可能是一种选择。
仲裁队列
法定人数队列 是 CP 的,只要法定人数(多数)保持正常运行,它们就能容忍代理的丢失。同样,在发生网络分区的情况下,只要队列拥有一个仍能通信的多数派,队列就会继续运行。法定人数队列不使用 RabbitMQ 传统的分区处理策略,而是使用它们自己的故障检测器,该检测器不仅在检测分区和故障方面更快,而且更不容易产生误报,这使它们能够实现复制队列类型中最快的故障转移。
经典镜像队列
经典队列可以被镜像并配置为可用性 (AP) 或一致性 (CP)。使用 auto-heal 或 ignore 分区处理策略将允许集群中的所有代理继续为发布者和消费者服务,即使一个或多个代理被网络分区切断。但是,从分区恢复时,数据可能会丢失。
使用 pause_minority 分区处理策略使镜像队列偏向于一致性。在网络分区的情况下,少数侧的任何代理都会暂停自己,关闭所有网络连接。已确认的写入不会丢失,但少数侧的任何代理都会失去可用性;如果存在多个没有多数派的分区,则整个集群将变为不可用。同样,如果多数节点宕机,剩余的代理将暂停自己。
故障转移不如法定人数队列快,并且对于极大的队列,存在故障转移可能需要几分钟或几小时的极端情况。
请参阅我们最近关于法定人数队列和镜像队列的文章,其中更详细地解释了这一点。
客户端重连
为了实现高可用性,客户端还需要能够在连接失败或代理下线时自动重新连接。大多数 RabbitMQ 客户端提供自动重连功能。通过各个主机名访问集群的代理时,请确保将所有主机名提供给客户端,以便它可以尝试重新连接到集群中的所有节点,直到找到正在运行的代理。使用负载均衡器时,请确保负载均衡没有任何亲和性/粘性,以确保客户端重新连接时不会总是被路由回同一个代理(该代理可能现在已经宕机)。
机架感知 (Rack Awareness)
虽然 RabbitMQ 目前没有机架感知功能,但您可以通过手动指定应跨越的节点来达到同样的结果。对于镜像队列,您可以指定应跨越的节点列表。对于法定人数队列,您目前必须创建一个初始组大小为 1 的队列,然后添加成员到各个节点以实现所需的分布。
容量规划
良好的容量规划与业务连续性规划齐头并进,因为两者都是实现可靠性和弹性所必需的。查看我们的 RabbitMQ 大小调整指南。
灾难恢复
灾难恢复通常需要数据中心之间的异步数据复制。RabbitMQ 目前不支持此功能,但可以利用其他消息路由功能来提供部分解决方案。
RabbitMQ 支持架构(交换机、队列、绑定、用户、权限、策略等)的复制,这允许在不同数据中心的辅助集群成为主集群的空镜像。仅使用架构同步进行的故障转移将提供可用性,但会有一个数据丢失窗口。
架构复制
RabbitMQ 有两个架构复制功能。
定义导入/导出是管理插件的一项功能,允许通过 JSON 文件导出和导入架构。
Tanzu RabbitMQ 提供架构同步插件,该插件主动将架构更改复制到辅助集群。
数据
RabbitMQ 尚未具有适用于所有多数据中心场景的异步数据复制功能。但是,您可以利用交换机联邦或 shovels,它们是跨集群工作的异步消息路由功能。这些功能并非为主动-被动架构而构建,因此确实存在一些缺点。它们是为了在集群之间移动消息以进行主动处理而构建的,而不是为了镜像集群的数据以实现冗余。
复制与跨集群消息路由的区别在于,复制涉及复制入队和确认操作,而消息路由仅涉及复制消息。联邦或 shovels 不包含消息已被消费并从队列中删除的事实(因为这不是这些功能的目的)。
例如,交换机 E1 在数据中心 DC1 有一个到法定人数队列的单一绑定。DC2 有一个到 DC1 的联邦链接和一个联邦交换机 E1,它也有一个到法定人数队列的单一绑定。当消息 m1 和 m2 发布到 DC1 的 E1 时,这些消息被路由到 DC1 的队列,并异步路由到 DC2 的 E1,并入队到那里的队列中。现在消息 m1 和 m2 同时存在于两个数据中心的队列中。

但现在消费者消费并确认了 DC1 上的消息 m1 和 m2,并发布了新消息 m3 和 m4。法定人数队列领导者同步复制新的入队操作和确认操作。联邦将 m3 和 m4 消息路由到 DC2,但不同步确认操作(联邦仅进行消息路由)。现在 m1 和 m2 仅存在于 DC2 上,因为它们已在 DC1 上被消费。如果 DC1 宕机并且我们故障转移到 DC2,m1 和 m2 将被再次消费。

这意味着在从一个 RabbitMQ 集群故障转移到另一个集群时,您将需要容忍重复。此外,随着消息的积累,队列将不断增长。
为了减轻重复问题,您可以在被动集群上应用消息 TTL 策略(在一段时间后删除消息),或者应用队列长度限制(在队列长度达到限制时删除消息)。诚然,这些都是不完美的解决方案,因为它们实际上可能导致消息丢失。假设 TTL 为 24 小时,一条消息在 DC1 的队列中可能已经等待了 25 小时未被消费,当 DC1 宕机时,同一条消息在 DC1 宕机前一小时就已经从 DC2 的同一队列中被丢弃了。
为了应对重复,您的系统要么需要容忍重复(通过保持幂等性),要么需要有重复数据删除解决方案(如在 Redis 中存储消息 ID)。任何至少投递一次的消息队列/总线都会不时导致重复,因此这可能已经是得到处理的问题了。
关于备份的说明
RabbitMQ 支持备份,但支持非常有限,因此不常使用。其限制在于必须完全关闭集群才能备份其数据目录。这使得对于大多数企业而言,备份并非一种可行的灾难恢复策略。
总结
RabbitMQ 通过使用集群和复制队列,为单个数据中心或跨多个可用区提供了出色的高可用性支持。
对于需要在主动-被动架构中进行地理位置分离的多个数据中心的业务连续性计划,存在一些挑战。RPO 为 0 分钟仅能通过单个集群实现,因此在多数据中心(或多区域)场景中是不现实的。对于 RPO 大于 0 分钟的情况,我们可以利用联邦和 shovel,但这也会带来消息重复形式的挑战。这种重复可以通过重复数据删除策略来解决。
RabbitMQ 的路线图上有许多功能,包括真正的灾难恢复异步复制支持、数据恢复工具、机架感知等。所以请保持关注,因为 RabbitMQ 发展很快。