本页介绍了如何使用 Pub/Sub 的“恰好一次”功能接收和确认消息,以便跟踪和防止重复处理消息。启用该功能后,Pub/Sub 会提供以下语义:
订阅者可以确定消息确认是否成功。
消息成功确认后,系统不会重新传送。
消息待处理期间不会重新传送。在确认截止时间到期或消息收到确认之前,消息会被视为未完成。
如果由于确认截止时间到期或客户端发起的否定确认而发生多次有效传送,则只能使用最新的确认 ID 来确认消息。任何使用之前的确认 ID 的请求都会失败。
启用“恰好一次”后,订阅者可以遵循以下准则,确保消息只处理一次:
在确认期限内确认消息。
在成功确认消息之前,保留有关消息处理进度的信息。
使用有关处理消息进度的信息,以防止在确认失败时重复工作。
只有拉取订阅类型支持“正好一次”传送,包括使用 StreamingPull API 的订阅者。推送订阅和导出订阅不支持“仅传送一次”传送方式。
Pub/Sub 支持在一个云区域内根据 Pub/Sub 定义的唯一消息 ID 进行“正好一次”传送。
推荐的客户端库版本
- 为了获得最佳性能,请使用最新版本的客户端库、Python v2.13.6 或更高版本、Java v1.120.11 或更高版本、PHP v1.39.0 或更高版本、C# v3.2.0 或更高版本、C++ v2.1.0、Go v1.25.1 或更高版本、Node v3.2.0 或更高版本 和 Ruby v2.12.1 或更高版本。
重新提交与重复
请务必了解预期重新提交和意外重新提交之间的区别。
重新传送可能是因为客户发起了消息的否定确认,或者客户未在确认截止时间到期前延长消息的确认截止时间。重新提交的内容会被视为有效,并且系统会按预期运行。
如需排查重新提交问题,请参阅处理重复内容。
重复消息是指在成功确认消息后或在确认截止期限到期前重新发送的消息。
重新传送的消息在重新传送尝试期间会保留相同的消息 ID。
启用了“仅传送一次”功能的订阅不会收到重复传送的内容。
客户端库中仅传送一次的传送支持
受支持的客户端库具有用于通过响应确认的接口(例如 Go)。您可以使用此接口检查确认请求是否成功。如果确认请求成功,则保证客户端不会收到重新传送。如果确认请求失败,客户端可以预期重新传送。
客户端也可以在不使用确认接口的情况下使用受支持的客户端库。不过,在这种情况下,确认失败可能会导致消息静默重新传送。
受支持的客户端库具有用于设置最短租约续期时间的接口(例如 Go)。您必须将最短租约续期时间的值设置为较大的数字,以避免任何与网络相关的确认过期。最大值设为 600 秒。
与精确一次传送相关的变量的默认值和范围,以及变量的名称可能会因客户端库而异。例如,在 Java 客户端库中,以下变量用于控制“恰好一次”传送。
变量 | 说明 | 值 |
---|---|---|
setEnableExactlyOnceDelivery |
启用或停用“仅传送一次”。 | true 或 false(默认为 false) |
minDurationPerAckExtension |
用于延长修改确认期限的最短时间(以秒为单位)。 | 范围:0 到 600;默认值:无 |
maxDurationPerAckExtension |
用于延长修改确认期限的最大时间(以秒为单位)。 | 范围:0 到 600;默认值:无 |
对于“正好一次”传送,如果确认 ID 已过期,发送给 Pub/Sub 的 modifyAckDeadline
或 acknowledgment
请求将失败。在这种情况下,由于系统可能已经在传送较新的传送内容,因此该服务会将过期的确认 ID 视为无效。这是出于精确传送的设计目的。然后,您会看到 acknowledgment
和 ModifyAckDeadline
请求返回 INVALID_ARGUMENT
响应。停用仅传送一次功能后,如果确认 ID 已过期,这些请求会返回 OK
。
为确保 acknowledgment
和 ModifyAckDeadline
请求具有有效的确认 ID,不妨将 minDurationPerAckExtension
的值设置为较大的数字。
地区注意事项
仅传送一次保证仅适用于订阅者在同一区域内连接到服务的情况。如果订阅者应用分布在多个区域,则可能会导致重复的消息传递,即使启用了“仅传送一次”功能也是如此。发布商可以向任何区域发送消息,并且仍能保证消息只会发送一次。
当您在 Google Cloud 中运行应用时,默认情况下,应用会连接到同一区域中的 Pub/Sub 端点。因此,在 Google Cloud 内的单个区域中运行应用通常可确保您与单个区域进行交互。
在 Google Cloud 之外或在多个区域运行订阅者应用时,您可以在配置 Pub/Sub 客户端时使用位置端点,从而确保连接到单个区域。Pub/Sub 的所有位置端点都指向单个区域。如需详细了解位置端点,请参阅 Pub/Sub 端点。如需查看 Pub/Sub 的所有位置端点列表,请参阅位置端点列表。
创建采用“仅传送一次”传送模式的订阅
您可以使用 Google Cloud 控制台、Google Cloud CLI、客户端库或 Pub/Sub API 创建具有“exactly-once”传送模式的订阅。
拉取订阅
控制台
如需创建采用“恰好一次”传送模式的拉取订阅,请按以下步骤操作:
在 Google Cloud 控制台中,进入订阅页面。
点击创建订阅。
输入订阅 ID。
从下拉菜单中选择或创建一个主题。
订阅将接收来自该主题的消息。
在仅传送一次部分,选择启用仅传送一次。
点击创建。
gcloud
如需创建具有“恰好一次”传送模式的拉取订阅,请使用带有 --enable-exactly-once-delivery
标志的 gcloud pubsub subscriptions create
命令:
gcloud pubsub subscriptions create SUBSCRIPTION_ID \ --topic=TOPIC_ID \ --enable-exactly-once-delivery
替换以下内容:
- SUBSCRIPTION_ID:要创建的订阅的 ID
- TOPIC_ID:要附加到订阅的主题的 ID
REST
如需创建采用“恰好一次”传送模式的订阅,请使用 projects.subscriptions.create
方法。
PUT https://pubsub.googleapis.com/v1/projects/PROJECT_ID/subscriptions/SUBSCRIPTION_ID Authorization: Bearer $(gcloud auth print-access-token)
替换以下内容:
- PROJECT_ID:要在其中创建订阅的项目的 ID
- SUBSCRIPTION_ID:要创建的订阅的 ID
如需创建采用“恰好一次”传送模式的拉取订阅,请在请求正文中指定这一点:
{ "topic": "projects/PROJECT_ID/topics/TOPIC_ID", "enableExactlyOnceDelivery": true, }
替换以下内容:
- PROJECT_ID:包含主题的项目的 ID
- TOPIC_ID:要附加到订阅的主题的 ID
C++
在尝试此示例之前,请按照《快速入门:使用客户端库》中的 C++ 设置说明进行操作。如需了解详情,请参阅 Pub/Sub C++ API 参考文档。
C#
在尝试此示例之前,请按照《快速入门:使用客户端库》中的 C# 设置说明进行操作。 如需了解详情,请参阅 Pub/Sub C# API 参考文档。
Go
在尝试此示例之前,请按照《快速入门:使用客户端库》中的 Go 设置说明进行操作。 如需了解详情,请参阅 Pub/Sub Go API 参考文档。
Java
在尝试此示例之前,请按照《快速入门:使用客户端库》中的 Java 设置说明进行操作。 如需了解详情,请参阅 Pub/Sub Java API 参考文档。
Python
在尝试此示例之前,请按照《快速入门:使用客户端库》中的 Python 设置说明进行操作。 如需了解详情,请参阅 Pub/Sub Python API 参考文档。
Node.js
在尝试此示例之前,请按照《快速入门:使用客户端库》中的 Node.js 设置说明进行操作。如需了解详情,请参阅 Pub/Sub Node.js API 参考文档。
Node.js
在尝试此示例之前,请按照《快速入门:使用客户端库》中的 Node.js 设置说明进行操作。如需了解详情,请参阅 Pub/Sub Node.js API 参考文档。
Ruby
在尝试此示例之前,请按照《快速入门:使用客户端库》中的 Ruby 设置说明进行操作。 如需了解详情,请参阅 Pub/Sub Ruby API 参考文档。
PHP
在尝试此示例之前,请按照《快速入门:使用客户端库》中的 PHP 设置说明进行操作。如需了解详情,请参阅 Pub/Sub PHP API 参考文档。
使用“仅传送一次”消息传送方式进行订阅
以下是使用客户端库以“exactly-once”方式提交订阅的一些代码示例。
拉取订阅
Go
在尝试此示例之前,请按照 Pub/Sub 快速入门:使用客户端库中的 Go 设置说明进行操作。 如需了解详情,请参阅 Pub/Sub Go API 参考文档。
如需向 Pub/Sub 进行身份验证,请设置应用默认凭据。 如需了解详情,请参阅为本地开发环境设置身份验证。
Java
在尝试此示例之前,请按照 Pub/Sub 快速入门:使用客户端库中的 Java 设置说明进行操作。 如需了解详情,请参阅 Pub/Sub Java API 参考文档。
如需向 Pub/Sub 进行身份验证,请设置应用默认凭据。 如需了解详情,请参阅为本地开发环境设置身份验证。
Node.js
在尝试此示例之前,请按照 Pub/Sub 快速入门:使用客户端库中的 Node.js 设置说明进行操作。 如需了解详情,请参阅 Pub/Sub Node.js API 参考文档。
如需向 Pub/Sub 进行身份验证,请设置应用默认凭据。 如需了解详情,请参阅为本地开发环境设置身份验证。
PHP
在尝试此示例之前,请按照 Pub/Sub 快速入门:使用客户端库中的 PHP 设置说明进行操作。 如需了解详情,请参阅 Pub/Sub PHP API 参考文档。
如需向 Pub/Sub 进行身份验证,请设置应用默认凭据。 如需了解详情,请参阅为本地开发环境设置身份验证。
Python
在尝试此示例之前,请按照 Pub/Sub 快速入门:使用客户端库中的 Python 设置说明进行操作。 如需了解详情,请参阅 Pub/Sub Python API 参考文档。
如需向 Pub/Sub 进行身份验证,请设置应用默认凭据。 如需了解详情,请参阅为本地开发环境设置身份验证。
Ruby
在尝试此示例之前,请按照 Pub/Sub 快速入门:使用客户端库中的 Ruby 设置说明进行操作。 如需了解详情,请参阅 Pub/Sub Ruby API 参考文档。
如需向 Pub/Sub 进行身份验证,请设置应用默认凭据。 如需了解详情,请参阅为本地开发环境设置身份验证。
C++
在尝试此示例之前,请按照 Pub/Sub 快速入门:使用客户端库中的 C++ 设置说明进行操作。 如需了解详情,请参阅 Pub/Sub C++ API 参考文档。
如需向 Pub/Sub 进行身份验证,请设置应用默认凭据。 如需了解详情,请参阅为本地开发环境设置身份验证。
C#
在尝试此示例之前,请按照 Pub/Sub 快速入门:使用客户端库中的 C# 设置说明进行操作。 如需了解详情,请参阅 Pub/Sub C# API 参考文档。
如需向 Pub/Sub 进行身份验证,请设置应用默认凭据。 如需了解详情,请参阅为本地开发环境设置身份验证。
Node.js (TypeScript)
在尝试此示例之前,请按照《Pub/Sub 快速入门:使用客户端库》中的 Node.js 设置说明执行操作。 如需了解详情,请参阅 Pub/Sub Node.js API 参考文档。
如需向 Pub/Sub 进行身份验证,请设置应用默认凭据。 如需了解详情,请参阅为本地开发环境设置身份验证。
监控“仅传送一次”传送订阅
subscription/exactly_once_warning_count
指标用于记录可能导致重新提交(有效或重复)的事件数量。此指标用于统计 Pub/Sub 处理与确认 ID 关联的请求(ModifyAckDeadline
或 acknowledgment
请求)失败的次数。失败的原因可能是服务器端或客户端问题。例如,如果用于维护“正好一次”传送信息的持久化层不可用,则属于基于服务器的事件。如果客户端尝试使用无效的确认 ID 确认消息,则会被视为基于客户端的事件。
了解该指标
subscription/exactly_once_warning_count
会捕获可能或不可能导致实际重新提交的事件,并且可能会因客户行为而产生噪声。例如:重复发送包含无效确认 ID 的 acknowledgment
或 ModifyAckDeadline
请求会导致该指标反复递增。
以下指标也有助于了解客户行为:
subscription/expired_ack_deadlines_count
指标显示确认 ID 过期次数。确认 ID 过期可能会导致ModifyAckDeadline
和acknowledgment
请求都失败。在请求到达 Google Cloud 但未到达 Pub/Sub 的情况下,
service.serviceruntime.googleapis.com/api/request_count
指标可用于捕获ModifyAckDeadline
或acknowledgment
请求的失败情况。此指标不会捕获某些失败情况,例如客户端与 Google Cloud 断开连接时。
在大多数可重试的失败事件中,受支持的客户端库会自动重试请求。
配额
仅传送一次订阅需要满足额外的配额要求。这些配额适用于:
- 每个区域从启用了仅传送一次递送模式的订阅中消耗的消息数。
- 使用启用了“仅传送一次”功能的订阅时,每个区域内已确认或已延长截止期限的消息数量。
如需详细了解这些配额,请参阅配额主题中的表格。
仅传送一次和有序订阅
Pub/Sub 支持“正好一次”传送和有序传送。
将有序传送与“正好一次”传送模式搭配使用时,Pub/Sub 会预期确认会按顺序进行。如果确认不按顺序,服务会因临时错误而拒绝请求。如果确认时限在有序传送确认之前到期,客户端将收到消息的重新传送。因此,当您将有序传送与“恰好一次”传送方式搭配使用时,客户端吞吐量会限制在每秒数千条消息的数量级。
仅传送一次和推送订阅
Pub/Sub 仅在拉取订阅的情况下支持“正好一次”传送。
从推送订阅中使用消息的客户端通过使用成功响应回复推送请求来确认消息。不过,客户端不知道 Pub/Sub 订阅是否收到了响应并对其进行了处理。这与拉取订阅不同,在拉取订阅中,确认请求由客户端发起,并且在 Pub/Sub 订阅成功处理请求时做出响应。因此,“正好传送一次”语义与推送订阅不太契合。
注意事项
如果未在 CreateSubscription 时指定确认期限,启用了“仅传送一次”功能的订阅的默认确认期限为 60 秒。
延长默认确认期限有助于避免因网络事件而导致的重新提交。受支持的客户端库不会使用默认的订阅确认时限。
与常规订阅相比,“仅传送一次”订阅的发布到订阅延迟时间要长得多。
如果您需要高吞吐量,则“正好一次”提交客户端还必须使用流式拉取。
由于发布端重复,订阅可能会收到同一消息的多个副本,即使启用了“仅传送一次”功能也是如此。发布端重复项可能是由发布客户端或 Pub/Sub 服务多次重试发布唯一内容所致。发布客户端在多次重试中进行多次唯一发布会导致使用不同的消息 ID 进行重新提交。Pub/Sub 服务为响应客户端发布请求而进行的多次唯一发布会导致重新传送具有相同消息 ID 的消息。
您可以在
subscription/exactly_once_warning_count
中重试失败的请求,受支持的客户端库会自动重试这些请求。但是,与无效确认 ID 相关的失败无法重试。