Pub/Sub:可靠性简介

本指南介绍了 Pub/Sub 的可靠性功能,并提供了相关概览。

为什么选择 Pub/Sub?

作为一种消息传递范式,发布-订阅旨在将消息的生产者与消息的消费者分离开来。提供方不会直接向使用方发送包含数据的请求,而是将数据发布到 Pub/Sub 等 Pub/Sub 服务。该服务会以异步方式将这些消息传递给已订阅的感兴趣的消费者。

这样一来,服务便可吸收所有与寻找对数据感兴趣的消费者相关的复杂细节。该服务还会根据消费者的容量来管理消费者接收数据的速率。这种解耦可让数据生产者以低延迟大规模写入消息,而无需考虑消费者的行为。

Pub/Sub 可提供高度可伸缩且可靠的消息传递服务。虽然该服务会自动处理大部分此类问题,但您可以控制发布者和订阅者的不同方面,从而影响可用性和性能。本指南的其余部分将详细介绍这些方面。

隔离

默认情况下,Pub/Sub 是一项全球服务:主题和订阅并非与特定区域固有相关,并且消息会在 Pub/Sub 服务中根据需要在区域之间流动。使用全局端点 pubsub.googleapis.com 时,发布者和订阅者会连接到运行 Pub/Sub 的网络最近区域。使用区域端点(例如 us-central1-pubsub.googleapis.com)或位置端点(例如 pubsub.us-central1.rep.googleapis.com)时,发布方和订阅方会连接到指定区域中的 Pub/Sub。在 Google Cloud之外运行发布者或订阅者时,最好使用区域端点或位置端点,以确保消息在预期区域之间稳定流动。

区域隔离

为了最大限度地减少发布和订阅操作所依赖的单个区域之外的基础设施,并确保所有数据都隔离在该区域中,请按以下步骤操作:

  1. 为每个区域创建一个主题。

    虽然 Pub/Sub 的命名空间是全局的,并且您无法将主题和订阅绑定到特定区域,但所有资源的元数据都会复制到区域内的本地数据存储区。因此,一旦您创建了资源,即使其他区域出现问题,该资源的配置也会可用。请注意,如果发生中断,主题或订阅配置的更新可能不会立即传播。

  2. 避免使用全球端点。

    请改用区域端点(如果可用)和位置端点(如果区域端点不可用)。区域端点可提供更高的区域隔离度,但目前仅在部分区域提供。

  3. 使用消息存储政策,并将 enforceInTransit 设置为 True。

    如果启用强制执行传输中,数据绝不会离开相应区域,并且连接到特定区域中主题的所有客户端都会将消息存储政策设置为该区域。

通过这种方式配置主题,您可以确保所有发布和订阅操作仅在相应区域内写入和读取数据。如果单个区域中出现发布者、订阅者或 Pub/Sub 故障,消息传送会在该区域中停止。其他区域的主题和订阅的消息传送不受影响。

如果您还需要对主题和订阅的管理操作和命名空间进行区域隔离,请考虑使用 Managed Service for Apache Kafka。

故障切换

如果您不需要区域隔离,不妨利用 Pub/Sub 能够跨多个区域高效传送消息的功能,以实现多区域故障切换功能。本部分的其余内容将介绍如何创建主题和订阅,以及如何放置发布方和订阅方,以支持不同类型的故障切换和数据冗余。

默认故障切换语义

假设只有一个主题和订阅。发布商位于美国和澳大利亚的区域,订阅者位于 Google Cloud 欧洲和澳大利亚的地区。如果所有订阅者都有足够的容量来接收消息,则消息流如下所示:

图 1. 所有订阅者都有足够的容量来接收消息。
图 1. 所有订阅者都有足够的容量来接收消息。

P 表示发布商,S 表示订阅者。蓝色六边形表示 Pub/Sub 服务。圆柱体表示消息的存储位置(消息始终会持久保存到发布区域中的多个可用区)。如果订阅者可用,Pub/Sub 会优先在消息发布所在的同一区域内发送消息。否则,它会将消息发送到距离网络最近且具有容量的订阅方所在的区域。因此,如上图所示,在美国发布的消息会传递给欧洲的订阅者,而在澳大利亚发布的消息则会留在澳大利亚。

以下部分讨论了在不同故障场景下会发生什么情况。

欧洲的订阅者无法使用

假设欧洲的订阅者被拒绝或经常崩溃,并且无法保持与 Pub/Sub 的连接。如果发生这种情况,该服务将开始向澳大利亚的订阅者传送消息:

图 2.欧洲的订阅者不可用。
图 2. 欧洲订阅者无法使用。

欧洲和澳大利亚的订阅者无法使用

如果所有订阅者都不可用,Pub/Sub 会将消息存储到配置的消息保留时长。

图 3. 欧洲和澳大利亚的订阅者无法使用。
图 3. 欧洲和澳大利亚的订阅者无法使用。

订阅者重新连接后,系统会传送消息,除非中断时间超过配置的消息保留时长。默认情况下,订阅消息保留时长设置为 7 天。您还可以将主题中的消息保留时间配置为最多 31 天。请勿选择短于您预期或愿意容忍的最长中断时长的消息保留时长。

Pub/Sub 在欧洲不可用

虽然这种情况很少见,但您可能还需要处理 Pub/Sub 本身不可用的情况。Pub/Sub 不可用表现为发布或订阅请求长时间出现意外错误,或者无法将已发布的消息传送给订阅者。例如,如果欧洲某个区域的 Pub/Sub 服务出现故障,则该场景与订阅者出现故障时的场景非常相似:

图 4. Pub/Sub 在欧洲不可用。
图 4. Pub/Sub 在欧洲不可用。

请注意,在这种情况下,即使使用全球端点,欧洲的订阅者也不会故障转移到其他区域。Pub/Sub 不会故意自动故障转移。假设是订阅者本身导致了 Pub/Sub 中出现意外问题,进而导致不可用。此类问题会被视为严重中断。不过,服务中断的影响范围可以限制在订阅者所连接的区域。如果服务允许订阅者故障转移到其他区域,订阅者也可能会导致该区域的服务不可用,从而导致整个服务出现级联故障。

澳大利亚境内的发布商不可用

如果一个区域中的发布方变得不可用,已发布的消息仍会传送给最近的订阅方:

图 5. 澳大利亚境内的发布商无法使用。
图 5. 澳大利亚境内的发布商无法使用。

最终,所有消息都会被订阅者使用并确认。发送消息时,Pub/Sub 会尽量缩短网络距离。因此,如果欧洲的订阅方有足够的容量来处理美国发布的所有消息,那么澳大利亚相应区域的订阅方可能会停止接收消息。

Pub/Sub 在美国不可用

Pub/Sub 会将消息同步写入到区域内的多个可用区。因此,可用区级服务中断不足以阻止消息的传送;整个区域都必须不可用。如果 Pub/Sub 在发布者发送消息的区域中变得不可用,则该区域中的消息可能无法传送,直到服务完全恢复:

图 6. Pub/Sub 在美国不可用
图 6. Pub/Sub 在美国不可用。

消息最终仍会传送(假设消息保留期限尚未结束),但会因中断而延迟。请注意,与订阅方类似,美国境内的发布方在服务发生故障时也不会故障转移到其他区域。此行为有助于防止因发布者或订阅者出现故障而导致跨区域的级联故障。

客户控制的故障切换和冗余

如果发布者和订阅者之间的任何位置发生中断,Pub/Sub 的默认故障切换语义可能无法完全保证消息始终可以从发布者流向订阅者。中断可能发生在多个不同的位置,包括您的客户端、发布商或订阅者运行的服务、网络,甚至很少情况下发生在 Pub/Sub 本身中。 如果您需要服务能够应对此类中断,则必须自行实现冗余。通常,这些冗余包括使用多个发布者和订阅者客户端实例,其中每个实例使用不同的位置端点。

您可能希望在两种不同的影响范围(可用区级或区域级)内实现弹性。 以下是每种设备的设置选项。

可用区级弹性

Pub/Sub 具有内置的跨可用区级复制功能。您无需采取任何特殊步骤来应对影响服务本身的单区域中断。不过,为了让客户端或网络在发生中断时仍能正常运行,最好在区域内的多个可用区中运行具有足够容量的发布者和订阅者。如果单个可用区发生故障,其他可用区中的客户端能够接管流量并处理消息。最佳实践是不要同时向这些客户端发布更改,这样一来,即使引入了 bug,其他未触及的区域也可以继续处理消息。

区域弹性

为了应对区域级故障,请在发布者和订阅者中设置额外的冗余。您可以在多个区域中运行发布者和订阅者,以应对这些客户端或网络中可能出现的中断。

如果您希望能够应对某个区域中可能发生的 Pub/Sub 故障,则必须准备好故障切换机制来应对此类中断。可能的方法需要在端到端消息传递延迟时间和成本之间进行权衡。

如果费用不是问题,为了最大限度地缩短延迟时间,最佳策略是在不同区域中始终同时发布和订阅。首先,选择要实现冗余的区域数量。 接下来,虽然不是绝对必要,但您可以为每个区域设置主题和订阅。

每个发布商创建的发布商客户端数量与区域数量相同(每个区域一个),并使用不同的地理位置端点来确保消息定向到不同的区域。如果使用单独的主题,每个发布商客户端都必须发布到相应的区域性主题。对于每条消息,发布者都会在每个客户端上调用发布。通过冗余发布,即使任何单个发布失败,也无需重试发布。

同样,每个订阅方都会创建相应数量的订阅方客户端(每个区域一个),并使用位置端点连接到不同的区域。如果为每个区域使用不同的订阅,则每个订阅方客户端都必须使用相应的订阅。请注意,发布者和订阅者使用的区域不一定需要相同。订阅者通过这三个订阅接收消息并对其进行处理。

此设置具有以下几项关键功能和要求:

  1. 任何单区域服务中断都不会影响已发布的消息以及在服务中断期间发布的消息的处理。由于消息已发布到多个区域,因此即使某个区域出现故障,其他区域中的消息仍可正常使用。在服务中断期间,发布调用在受影响的区域中会失败,但在其他区域中会成功。
  2. 只要消息流经的任何区域可用,消息处理延迟时间就不会受到影响。
  3. 消息处理必须具有幂等性。由于每条消息都会多次传送,因此消息处理必须能够应对重复消息。如果发生区域性服务中断,这些重复消息的到达时间可能会比首次递送消息的时间晚得多。这些重复项可能来自未发生服务中断的其他区域。

以这种冗余方式运行可提供最高的弹性,以应对任何类型的服务中断。对于依赖 Pub/Sub 且需要最高可用性的内部 Google 服务,建议采用此设置。不过,这种设置的缺点是,消息传递费用会乘以所用区域的数量。此外,必须跨区域移动的消息还会产生额外的区域间网络使用费用。

另一种冗余方法是仅在请求失败或消息未按预期从发布者流向订阅者时进行故障转移。在这种情况下,您有一个主要区域,您可以通过位置端点将出版商和订阅者定向到该区域。与之前一样,这些区域不必相同。这样一来,您还可以为发布者和订阅者提供一个后备区域,以便在主区域不可用时使用。

如果发布商的请求发送成功,则只会发布到主要区域(通过位置端点)。如果确定某个区域出现故障,发布商会改为向备用区域发布内容。确定区域是否发生故障并进行故障切换可以通过两种方式完成。这可以通过手动流程完成,并且配置会在发布商中动态更新。如果发布请求中的错误率足够高,发布商也可以自行更新配置。

订阅者必须始终通过位置端点连接到主区域。您可以决定,当出现以下一个或多个触发条件时,订阅者可以使用备用区域:

  1. 始终订阅回退区域。在这种情况下,订阅者始终与主要区域和回退区域保持连接。发布者和订阅者都可以使用相同的区域作为主要区域和回退区域。在这种情况下,如果发布者发生故障转移,订阅者必须仅通过备份区域接收消息。
  2. 通过配置手动检测订阅者并将他们切换到备用区域。如果您检测到服务中断,可以故障切换到备用区域,然后在服务中断缓解后移回主要区域。
  3. 在订阅者出现错误时进行故障转移。如果订阅者请求返回错误,则表明您必须故障切换到备用区域。请注意,Pub/Sub 客户端库会在出现暂时性错误时在内部重试流式拉取请求,因此您可能无法检测到长时间的意外错误。此外,即使在正常运行期间,流式拉取错误率也预计为 100%。
  4. 如果订阅者在很长一段时间内未收到消息,则进行故障转移。假设消息发布始终保持一致,订阅者始终可以接收消息。如果订阅方长时间未收到任何消息,则可能是主要区域中的 Pub/Sub 存在订阅方端问题。此问题可通过故障切换到备用区域来解决。

在所有四个选项中,第一个选项是理想之选。如果没有消息在订阅者连接上流动,则该连接不会产生任何费用。唯一的成本是订阅者客户端库的额外实例的占用空间,这可以忽略不计。您还必须注意每个区域内打开的流式拉取连接数配额。

第二种模式的优势在于,由于消息只发布一次,因此 Pub/Sub 费用中没有乘数。不过,代价是,对于某些类型的中断,在中断开始之前发布的消息可能要等到中断解决后才能使用。存储在不可用区域中的消息可能无法传送给订阅者,无论订阅者连接到哪个区域。在服务中断期间发布到回退区域的消息可能可用。此外,发布商或订阅者可能会遇到一段时间的不可用情况,且错误率会增加。这取决于用于检测中断的方法以及故障转移到后备区域的时间。

无论您选择哪种选项,都要注意这可能会如何与 Pub/Sub 的功能互动。按序传送和仅传送一次均可在区域内提供保证。例如,如果您使用故障切换冗余技术,则只有在同一区域中发布的消息才能保证按顺序传送。即使消息先发布到主要区域,订阅者也可能会先收到发布到回退区域的消息,然后再收到发布到主要区域的消息。

微调发布商

无论您选择哪种故障切换选项,都需要在发布商本身内执行一些额外的调整步骤。调整发布者行为可确保在高负载下实现最佳性能。批量处理消息是一种以延迟换取成本降低的方式,但它不是可靠性问题,因此本文不作介绍。相反,请重点关注一些有助于调整可靠性的其他参数,包括重试设置和流控制设置。

发布可能会因各种原因而失败,包括网络不可用等暂时性原因,或需要用户干预(例如更改权限)的原因。Pub/Sub 客户端库会使用重试设置中指定的参数重试暂时性错误。这些设置控制因暂时性原因而失败的发布 RPC 重试的指数退避算法行为。虽然默认设置通常在大多数情况下都能正常运行,但在某些情况下,您可能需要调整这些值。

您最可能需要调整的两个属性是初始 RPC 超时和总超时。初始 RPC 超时是指第一个发布 RPC 完成的时间。如果任何 RPC 失败或超时,系统会尝试使用更长的超时时间再次执行 RPC,直到超出请求总数或总超时时间。

如果发布者受网络限制或距离运行 Pub/Sub 的最近 Google Cloud 数据中心较远,则可以调整初始超时时间。网络限制可能是发布商所用机器的吞吐量限制,也可能是同一机器上运行的其他网络密集型服务造成的。如果超时时间设置得过短,初始 RPC 可能会反复失败,导致需要进行更多尝试(使用更长的超时时间)才能成功发布。反复需要重试会增加发布延迟时间。在这种情况下,增加初始超时时间可能会加快发布速度。

如果网络连接不稳定,增加总超时时间以及初始超时时间可能会有所帮助。增加总超时时间可让发布 RPC 有更多时间成功完成。如果发布 RPC 始终因超出截止时间而失败,请考虑调整这些值。

发布时持续出现截止期限超出错误,可能也表明需要调整发布者流控制。借助这些设置,您可以确保发布商能够应对传入流量的峰值,从而生成更多要发送到 Pub/Sub 的消息。出站请求的大幅增加可能会使发布商的 CPU、内存或网络容量过载。当发布过载时,它无法在超时之前处理发布请求或响应。这会导致发布请求更多,最终达到总超时时间。发布商流量控制功能可限制在未收到发布请求的响应之前可处于未完成状态的消息或字节数。通过这种方式限制请求数量,即使在高峰时段也能将资源利用率保持在可管理的水平。 根据发布商的运作方式,您可以通过允许发布操作阻止进一步的请求,从而允许后续的发布 RPC 等待容量。或者,您也可以通过让流控制在达到容量时返回错误,来向服务的调用方推送回压。您可以配置发布者客户端库如何响应超出限制的行为。

微调订阅者

可能还需要进行订阅者调整,以确保其可靠运行。与发布商类似,您可以调整订阅者的流控制设置,以确保他们不会不堪重负。订阅者客户端库使用流式拉取,其中客户端会打开与服务器的持久流,而服务器会在消息可用时发送消息。如果发布的消息数量大幅增加,订阅者可能会收到超出其处理能力的消息。启用流控制后,客户端一次性接收的未确认消息数量会受到限制。这样可以减少同时处理的消息数量,并延长消息处理时间。分散负载可让订阅者在不超出影响消息处理的任何资源限制的情况下处理消息,这可能会产生连锁效应,最终导致无法处理任何消息。

如果您预计要处理的数据量只会出现峰值,最终会回落,那么仅使用流控制就足够了。如果流量通常会随着时间的推移而增加(因为使用量增加),则流控制会保护订阅者。不过,这可能会导致积压的消息不断增加,并导致消息在保留期限到期之前无法传送。在这种情况下,您可能还需要设置自动扩缩,以便在未确认消息数量不断增加时增加订阅者数量。具体设置方式取决于您为订阅者使用的计算平台。例如,Compute Engine 的自动扩缩器可让您根据未传送消息数量等指标进行扩缩。同时使用自动扩缩和流控制功能,可确保订阅方能够应对消息吞吐量的其他短期峰值以及需要更多算力的长期增长。请务必遵循使用 Pub/Sub 指标作为伸缩信号的最佳实践。

使用积压指标的区域版本,而不是全球版本

在监控订阅积压、配置提醒政策或自动扩缩订阅者时,Pub/Sub 会提供每个积压指标的两个版本。请务必使用带有 by_region 后缀的版本:

如果您希望监控、提醒和自动扩缩功能能够应对单区域中断,请勿使用这些指标的全局版本(subscription/num_undelivered_messages 和 subscription/oldest_unacked_message_age)。这些指标的全局版本需要计算已知有消息的所有区域的积压,这意味着单个区域的不可用会导致数据缺口。相比之下,by_region 版本的指标会按区域计算和报告积压情况。如果无法计算单个区域的积压,该指标仍会报告其他区域的值。

使用快照和搜索功能进行安全部署

消息丢失通常是灾难性事件。Pub/Sub 可确保所有发布的消息至少传送一次。不过,这些消息的正确处理取决于订阅者的行为。如果消息已成功确认,Pub/Sub 不会重新传送这些消息。因此,如果您部署的新订阅者代码中引入了一个 bug,该 bug 会在未正确处理消息的情况下确认消息,则可能会导致订阅者引发的消息丢失。Pub/Sub 提供快照和还原功能,可帮助您确保正确处理每条消息,即使遇到订阅者 bug 也是如此。

每个订阅者部署的模式必须如下所示:

图 7. 订阅者部署的模式。
图 7. 订阅者部署模式。

在确定新订阅者是否正常工作之前需要等待的时间长短可能因您的使用情形而异。只有当订阅者被视为正常工作时,才能退出步骤流程,此时可以删除快照。

使用快照和搜索并不意味着可以取代以下最佳实践:先在非生产环境中运行软件,然后逐步部署到生产环境。它们可提供额外的保护,确保可靠地处理数据。但代价是,还原到快照可能会导致订阅者已成功处理的消息被重复传送。不过,鉴于 Pub/Sub 默认具有至少一次传送语义,您的订阅方已能够应对消息重新传送。