Pub/Sub:Google 级消息传递服务

概览

Pub/Sub 是一种异步消息传递服务,旨在实现极高的可靠性和可扩缩性。该服务以十多年来许多 Google 产品都在依赖的核心 Google 基础架构组件为基础而构建。包括 Google Ads、Google 搜索和 Gmail 在内的 Google 产品使用此基础架构每秒发送超过 5 亿条消息,数据总量超过 1TB。本文介绍使 Pub/Sub 能够可靠地实现此类服务规模的重要设计特性。

发布/订阅服务的基础知识

Pub/Sub 是一种发布/订阅 (Pub/Sub) 服务,即消息的发送者与消息的接收者分离的消息传递服务。Pub/Sub 服务包含几个关键概念:

  • 消息:通过服务移动的数据。

  • 主题:代表消息源的命名实体。

  • 订阅:代表有兴趣接收特定主题的消息的命名实体。

  • 发布者(也称为生产者):创建消息并将其发送(发布)到指定主题上的消息传递服务。

  • 订阅者(也称为使用者):接收指定订阅的消息。

下图展示了 Pub/Sub 的基本消息流:

发布者 A 和 B 将消息推送到一个主题,两个订阅侦听消息并将消息推送给多个订阅者。

在此方案中,有两个发布者针对单个主题发布消息。该主题有两个订阅。第一个订阅有两个订阅者,这意味着消息将在这两个订阅者之间进行负载平衡,每个订阅者会收到一部分消息。第二个订阅有一个订阅者,该订阅者将收到所有消息。粗体字母代表消息。消息 A 来自发布者 1,通过订阅 1 发送给订阅者 2,通过订阅 2 发送给订阅者 3。消息 B 来自发布者 2,通过订阅 1 发送给订阅者 1,通过订阅 2 发送给订阅者 3。

评判消息传递服务的性能

可以从三个方面来评判 Pub/Sub 等消息传递服务的性能:可扩缩性、可用性和延迟时间。这三个方面往往互相冲突,需要牺牲一个才能改善其他两个。

术语“可扩缩性”、“可用性”和“延迟时间”可以指代系统的不同属性,以下各部分介绍它们在 Pub/Sub 中的定义。

可扩缩性

可扩缩的服务应当能够从容地应对负载增加,而不会明显增加延迟时间或降低可用性。“负载”可以指代 Pub/Sub 中的各种使用维度:

  • 主题数量

  • 发布者数量

  • 订阅数量

  • 订阅者数量

  • 消息数量

  • 消息大小

  • 发布或使用消息的速率(吞吐量)

  • 任何给定订阅的积压大小

可用性

在分布式系统中,问题的类型和严重程度可能会有很大差异。系统可用性的衡量方法是看它能否应对不同类型的问题并且以最终用户察觉不到的方式从容进行故障转移。故障可能因硬件(例如硬盘无法正常使用网络连接问题)和软件造成,也可能因负载问题而造成。如果服务中(或者同一硬件上运行的其他软件组件中或软件依赖项中)的流量突然增加,导致资源短缺,则可能会因负载问题而发生故障。可用性也可能会因人为错误(有人在构建或部署软件或配置时出错)而降低。

延迟时间

延迟时间是一种基于时间衡量系统性能的方式。服务通常需要尽可能缩短延迟时间。对于 Pub/Sub,两个最重要的延迟时间指标是:

  1. 确认发布的消息所用的时间。

  2. 向订阅者传送已发布消息所用的时间。

Pub/Sub 基本架构

本部分介绍 Pub/Sub 的设计,说明该服务如何在保持可用性的同时实现可扩缩性和较短的延迟时间。该系统设计为可横向扩缩,也就是可通过增加运行服务器的实例数量来应对主题、订阅或消息数量的增加。

Pub/Sub 服务器可在全球所有 GCP 区域运行。这允许服务提供快速的全球数据访问,同时可让用户控制消息的存储位置。Cloud Pub/Sub 在该发布者中提供全球数据访问,而订阅者客户端不知道它们所连接的服务器的位置或这些服务如何路由数据。

Pub/Sub 的负载平衡机制将发布者流量定向到允许存储数据的最近 GCP 数据中心,如 IAM 和管理控制台资源位置限制部分中所定义。这意味着多个区域中的发布者可将消息发布到单个主题并缩短延迟时间。任何个别消息都存储在单个区域中。但是,一个主题可能会将消息存储在多个区域中。 当订阅者客户端请求发布到此主题的消息时,它会连接到最近的服务器,该服务器汇总了发布到此主题以传送到该客户端的所有消息中的数据。

Pub/Sub 分为两个主要部分:数据平面(负责在发布者和订阅者之间移动消息)和控制平面(负责将发布者和订阅者分配给数据平面上的服务器)。数据平面中的服务器称为转发器,而控制平面中的服务器称为路由器。当发布者和订阅者连接到分配的转发器时,它们不需要来自路由器的任何信息(前提是这些转发器保持可访问)。因此,可以在不影响已连接并且正在发送或接收消息的任何客户端的情况下升级 Pub/Sub 的控制平面。

控制平面

Pub/Sub 控制平面将客户端分发给转发器,并且为所有客户端保证可扩缩性、可用性和较短的延迟时间。任何转发器都能提供客户端来处理任何主题或订阅。当客户端连接到 Pub/Sub 时,路由器会根据最短的网络距离决定客户端应该连接的数据中心。网络距离是衡量两点之间的连接的延迟时间的一种方式。在任何给定的数据中心内,路由器会尝试在所有可用转发器中分配整体负载。在执行此分配时,路由器必须平衡两个不同目标:(a) 负载均匀性(理想情况下,每个转发器的负载均衡);(b) 分配稳定性(理想情况下,若负载发生变化或可用的转发器发生变化,只需改动最少量的现有分配)。路由器使用由 Google Research 开发的一种一致性哈希变体来实现一致性和均匀性之间的可调平衡。路由器向客户端提供它可以考虑连接的转发器的有序列表。此有序列表可能会根据转发器的可用性以及客户端的负载情况而变化。

客户端获取此转发器列表并连接到其中的一个或多个转发器。客户端首选连接到路由器优先推荐的转发器,但也会考虑发生的任何故障,例如,如果多次尝试连接到最近数据中心内的转发器失败,那么它可能决定尝试连接到另一个数据中心内的转发器。为了让 Pub/Sub 客户端摆脱这些实现细节,客户端和转发器之间有一个服务代理可代表客户端执行此连接优化。

数据平面 - 消息的生命周期

数据平面接收来自发布者的消息并将它们发送给客户端。要了解 Pub/Sub 数据平面,最佳方式或许是查看一条消息的生命周期,即从服务接收消息到服务中不再存在该消息。我们来追溯一下处理消息的步骤。就本部分而言,我们假设消息发布到的主题至少有一个订阅。一般来讲,一条消息会经历以下步骤:

  1. 发布者发送消息。

  2. 消息写入到存储空间。

  3. Pub/Sub 向发布者发送确认,表明它已收到该消息并保证将其传送到所有附加的订阅。

  4. 在消息写入到存储空间的同时,Pub/Sub 将其发送给订阅者。

  5. 订阅者向 Pub/Sub 发送确认,表明它们已处理该消息。

  6. 在每个订阅的至少一个订阅者确认消息之后,Pub/Sub 将从存储空间中删除该消息。

首先,发布者将主题的消息发送到 Pub/Sub。消息将由代理层加密并发送至发布转发器(即发布者所连接的转发器)。为了确保传送,该消息将立即被写入到存储空间。转发器最初将该消息写入到 N 个集群(其中 N 是一个奇数),在消息被写入到至少 ⌈N/2⌉ 个集群时,转发器即认为该消息已保存。消息一旦被保存,发布转发器就会向发布者确认已收到消息,此时,Pub/Sub 会保证将该消息传送给所有附加的订阅。后台进程会定期将并非全部 N 个集群中都存在的任何消息写入到缺少该消息的集群中。

在每个集群中,系统将消息写入到 M 个独立磁盘(其中 M 是一个奇数),数据必须已存储到 ⌈M/2⌉ 个磁盘上,才会被认为已保存在该集群中。总的来说,当发布的任何消息写入到 ⌈N/2⌉ 个集群中至少 ⌈M/2⌉ 个独立磁盘时,才会被认为是已保存,而该消息最终将被复制到 N*M 个磁盘上。

发布转发器具有附加到主题的所有订阅的列表。它负责保存已发布消息和描述每个订阅已确认哪些消息的元数据。发布转发器为特定主题接收和存储的消息集以及对已确认消息的跟踪称为“发布消息源”。根据主题的吞吐量要求,单个发布者可以将其消息发送给多个发布转发器,并将消息存储在多个发布消息源中。同一主题的不同发布者也可以向不同发布转发器发送消息。每条消息只发送给一个发布转发器。随着吞吐量变化,Pub/Sub 会动态调整为特定主题接收消息的发布转发器的数量。

订阅者通过连接到订阅转发器(消息通过转发器从发布者流向订阅者)来接收消息。对于拉取订阅者,“连接”意味着发出拉取请求。对于推送订阅者,“连接”意味着在 Pub/Sub 中注册推送端点。创建订阅后,即可保证自此时起发布的任何消息都将被传送至该订阅,我们称之为同步点保证。

每个订阅转发器都需要向具有主题的发布消息源的发布转发器请求消息。与发布者一样,订阅者可以连接到多个订阅转发器来接收消息。这样,并非所有订阅转发器都需要知道主题的每个发布消息源或者从其接收消息 - 这是 Pub/Sub 能够横向扩缩的一个重要属性。根据传送给订阅者的消息的吞吐量,Pub/Sub 会随着吞吐量变化动态调整订阅者用于为特定主题接收消息的订阅转发器的数量。

订阅转发器向具有主题的发布消息源的一个或多个发布转发器发出请求,索取它需要的消息。发布转发器将未确认的消息发送给订阅转发器,订阅转发器然后将消息传递给订阅者。

订阅者处理消息后,即会向订阅转发器发送回确认。订阅转发器将此确认传递给发布转发器,而发布转发器将确认存储在发布消息源中。当某个主题上的所有订阅都已确认消息后,系统将从发布消息源和存储空间中异步删除该消息。

单个主题和订阅的不同消息可以流向许多发布者、订阅者、发布转发器和订阅转发器。发布者可以同时发布到多个转发器,订阅者可以连接到多个订阅转发器来接收消息。因此,经过发布者、订阅者和转发器之间的连接的消息流可能很复杂。下图显示了单个主题和订阅的消息流动方式,其中不同的颜色代表消息从发布者到订阅者可能采取的不同路径:

来自多个发布者的消息通过发布和订阅转发器发送给订阅者。

使 Pub/Sub 保持正常运行

要确保像 Pub/Sub 这样的分布式系统能够正常运行并有效地为所有客户服务,必须深入了解和控制系统。我们的网站可靠性工程师 (SRE) 将负责维护服务。这些工程师遍布于世界各地的多个位置,以便为 Pub/Sub 提供全天候服务。

环境

维护像 Pub/Sub 这样的系统的首要条件是能够在客户使用软件之前先对其进行测试。为了实现这一目标,需要用到三种 Pub/Sub 环境:测试、预演和生产。测试和预演不含任何客户流量;它们只包含我们持续运行的测试和监控,这些信息可帮助查明各版本的任何问题。这些环境会在软件投入生产之前收到软件新版本。测试和预演之间的区别在于后者是目前存在于(或短期内将投放至)生产环境的版本的精确副本,包括软件版本和命令行标志。前者可能会启用开发者目前正在开发并计划在未来的某一时间发布的功能。

推出

推出和测试 Pub/Sub 的过程旨在最大限度降低可能会造成的影响。我们来看看推出 Pub/Sub 新版本的典型步骤:

  1. 确保所有单元测试和集成测试都通过。

  2. 构建所有服务器的新版本。

  3. 将新服务器部署到测试和预演环境。

  4. 在测试和预演环境中运行服务器数天时间。

  5. 如果未发现已知问题,则将服务器发布到 canary(这是生产环境的一个子集,包含少量客户流量)。

  6. 如果在 canary 中未检测到任何问题,则在数天内逐步将服务器推出到更多生产环境中,直到它们在所有位置发布。

由于 Pub/Sub 通过分离控制平面和数据平面等设计实现了故障恢复,因此服务器新版本的推出对客户来说是无缝的,不会影响他们体验到的性能。

监控

使 Pub/Sub 保持正常运行的关键是在客户注意到问题之前自动检测到并消除问题。要实现这一目的,需要对系统进行广泛监控。SRE 维护着一组服务等级指标 (SLI),也就是描述系统行为的明确定义指标。指标可能包括“完成 CreateSubscription 请求所需的时间量”或“Publish 请求产生的错误率”。这些指标的衡量方式多种多样。其中一些指标仅在内部针对我们的转发器和路由器。例如,它们衡量将消息写入到磁盘需要多长时间。总共有 10 个 SLI 和数百个其他指标用于监控 Pub/Sub 的运行状况。

所有这些措施都有助于定义内部服务等级目标 (SLO),也就是 SLI 的具体目标。例如,“完成 CreateSubscription 请求用时不得超过五秒钟”。SRE 会收到 SLO 违规提醒,并且必须在五分钟内针对此类提醒做出回应。

服务等级协议 (SLA) 列出的 SLO 定义了我们为客户提供的性能保证以及不符合性能保证时的后果。您可以阅读 Pub/Sub 的 SLA

我们维护着一系列充当客户端并以可预测的方式发布和订阅的任务,称为“探测器”。数据平面和控制平面中都有探测器。我们的 10 种探测器中的每一种都会按照客户的意愿执行特定操作,并衡量操作所用时间。例如,我们有一个探测器创建了一个新订阅并发布了一条消息,随后查看创建订阅和接收消息需要多长时间。如果探测器确定 30 个衡量指标中的任何一个未达到预期,则向 SRE 发出提醒。

许多内部信息中心上都汇总了我们的服务器和探测器的指标,每当诊断问题时,SRE 都会首先检查内部信息中心。通过这些页面,您可以快速访问整个服务的统计数据和图表,如下图所示。它们也可以按主题、数据中心或单个任务进行细分。

显示发布者信息的图表,包括实体数、字节、批处理消息、用户消息和未完成字节。

Google Cloud Monitoring 显示服务用户最感兴趣的指标。事实上,甚至我们自己的探测器也有类似图表:

显示一段时间内发布的消息的图表。

控件

我们可以使用多种控制措施来帮助优化 Pub/Sub 的性能。其中一些控制措施旨在帮助解决数据中心或机器故障。我们可以对部分或所有主题设定路由限制,路由限制实际上就是一些规则,指定哪些客户端可以和/或不能连接到哪些转发器。我们使用路由限制来阻止流量进入个不能按预期运行的个别转发器任务或整个数据中心。

另一个可调特性是流控制。利用这一特性,我们能够最大限度提高吞吐量,同时防止服务过载。流控制是一种流量调整形式,可以随时间推移调整突发性负载激增,从而让服务更稳定。流控制在系统范围内运行,也能以每个主题或每个订阅者为基础运行,从而限制传输或未完成的消息数量或字节数。在这种情况下,“未完成”意味着消息已传送给客户端,但尚未收到确认。借助流控制和路由限制,我们能优化 Pub/Sub 的性能,而客户无需担心这些低级别细节。

总结

对于像 Pub/Sub 这样的服务,其可扩缩性、可用性和延迟时间优势正是考虑迁移到托管云服务的客户所看重的价值主张。任何异步消息传递服务从构建之初就必须考虑这些特性。凭借十多年快速可靠地传送海量消息的经验,Pub/Sub 团队构建并维护了一项服务来满足 Google 大部分基本产品的需求。现在,世界各地所有想要发送消息的外部客户都可以使用该服务,而无需担心他们的消息传递系统是否能够处理其当前负载的 2 倍、10 倍或 100 倍的负载。

术语表

术语 说明
集群 通常共享同一故障网域(例如共享本地网络和共享电源)的机器的逻辑分组。
控制平面 负责将发布者和订阅者分配到数据平面上的服务器的 Pub/Sub 层。
数据平面 负责在发布者和订阅者之间移动消息的 Pub/Sub 层。
转发器 数据平面中的服务器。
全球数据访问 Pub/Sub 发布者和订阅者客户端不知道数据的位置。根据位置限制政策,所有路由和存储均由服务本身完成。
可横向扩展 服务通过增加服务组件的实例数量来无缝处理更多负载的能力。
消息 通过 Pub/Sub 移动的数据。
网络距离 一种衡量两点之间连接延迟时间的方式。
探测器 充当客户端并且以可预测的方式在 Pub/Sub 服务器上执行一项或多项操作的任务。
发布消息源 由发布转发器接收和存储的一组消息以及由所有附加的订阅确认的消息的 ID 集。
发布/订阅 (Pub/Sub) 服务 一种消息传递服务,其中消息的发送者与消息的接收者相分离
发布者 创建消息并将其发送(发布)到指定主题的 Pub/Sub 客户端。
路由器 控制平面中的服务器。
路由限制 规则列表,指示路由器应该或不应该将哪些转发器作为可能的连接端点发送给客户端。
服务等级协议 (SLA) 定义系统为客户提供的性能保证并概述未达到性能要求时的后果的 SLO 列表。
服务等级指标 (SLI) 明确定义的、描述系统行为的指标。
服务等级目标 (SLO) 服务等级指标的具体目标。
订阅者 接收指定订阅的消息的 Pub/Sub 客户端。
订阅 代表接收特定主题的消息的意向的命名实体。
同步点保证 创建订阅者的时间,发布的所有后续消息都将被传送给该订阅者。
主题 代表消息 Feed 的命名实体。