排查拉取订阅问题

本文档提供了一些针对 Pub/Sub 拉取订阅的常见问题排查提示。如需详细了解拉取订阅,请参阅拉取订阅者指南。

为了有效监控 Pub/Sub 订阅,建议您先查看传送延迟时间健康状况得分 (subscription/delivery_latency_health_score),了解哪些因素可能会导致延迟时间意外增加。

最早的未确认消息的存在时间持续增加

oldest_unacked_message_age 是监控 Pub/Sub 订阅运行状况的关键指标。它衡量订阅积压中尚未被订阅者确认 (acked) 的最早消息的存在时长(以秒为单位)。该指标可提供有关潜在处理延迟或瓶颈的宝贵数据洞见。

监控消息积压情况可确保及时高效地处理消息。通过跟踪最早的未确认消息的存在时间,您可以主动识别消费者落后的情况。这样,您就可以及早干预,解决与性能下降相关的潜在问题。

您可以调查的一些常见积压问题包括:

客户端配置问题

当 oldest_unacked_message_age 和 num_undelivered_messages 指标同时增加时,可能意味着订阅者无法跟上消息量。在这种情况下,请重点调查订阅者组件:

  • 客户端运行状况:分析托管订阅者客户端的机器上的资源利用率,例如 CPU、内存和网络带宽。寻找可能影响处理效率的压力点。

  • 客户端代码:查看近期的代码更改并检查错误日志。订阅者代码中的 bug 或低效问题可能会显著影响消息处理速率。请注意,特定消息中可能存在问题。例如,多条消息可能需要同时访问数据库中的同一行。这种行为可能会导致争用和高延迟。

  • 配额限制:验证您是否未超出任何 Pub/Sub 配额或托管服务施加的限制。如果订阅者托管在 Google Cloud中,请查看 Compute Engine 或 GKE 资源配额,以防出现潜在瓶颈。

订阅者对消息进行了否定确认

当订阅者否定确认 (nack) 某条消息时,即向 Pub/Sub 发出信号,表明该消息无法成功处理。然后,Pub/Sub 会尝试重新传送同一消息。重复对某条消息发送 nack 会导致消息重复,并可能导致消息传送出现严重延迟。

请注意,对消息进行否定确认并不能保证下次拉取会提取不同的消息。Pub/Sub 的重新传送政策可能会继续在传送新消息之前重新传送已确认的消息。因此,请勿依赖 NACK 作为过滤或跳过特定消息的方法。而是应设置重试政策(最好是指数退避算法),以便对可能在稍后可处理但需要更长时间才能重新传送的个别消息进行退避。

如果您需要有意跳过某些消息,建议您确认这些消息,即使您不会处理它们。这样可以将其从订阅中移除,避免不必要的重新传送,并减少资源消耗。无论有意还是无意,不确认消息都会导致积压问题和重复传送。

高传送延迟时间

Pub/Sub 中的传送延迟时间是指消息从发布者到达订阅者所需的时间。以下部分介绍了导致传送延迟时间较长的一些可能原因。

订阅者数量不足

对于使用 StreamingPull 的客户端,为了实现持续低延迟,请保持与订阅的多个打开的 StreamingPull 连接。如果没有活跃的订阅者连接,Pub/Sub 将无法及时传送消息。单个数据流可能会成为单点故障,从而增加延迟风险。subscription/open_streaming_pulls 指标可让您了解活跃流式传输连接的数量。使用此方法可确保您始终有足够的流来处理收到的消息。

对于使用一元拉取的客户端,为了实现始终如一的低延迟,请维护对订阅的多个未完成拉取请求。不频繁的请求可能会导致消息在积压中累积,并增加延迟时间。这种方法有助于最大限度地减少连接性中断并缩短传送延迟时间。

如果您需要高吞吐量和低延迟,同时希望尽可能降低运营开销和处理费用,建议使用高级客户端库。默认情况下,高级客户端库使用 StreamingPull API,因为该 API 往往是最大限度缩短延迟时间的更佳选择。高级客户端库包含预构建的函数和类,用于处理身份验证、吞吐量和延迟时间优化、消息格式设置以及其他功能的底层 API 调用。

客户端配置问题

请参阅客户端配置问题。

积压高

请注意,Pub/Sub 订阅中未确认消息的消息积压会自然增加端到端延迟时间,因为订阅者不会立即处理消息。

排序键和“仅传送一次”

排序键和“正好一次”传送是非常有用的功能,但它们需要在 Pub/Sub 中进行额外的协调,以确保正确传送。这种协调可能会降低可用性并增加延迟时间。虽然在稳定状态下差异很小,但任何必要的协调步骤都可能导致延迟时间暂时增加或错误率增加。如果启用了排序功能,则在具有相同排序键的较早消息被确认之前,无法传递具有排序键的消息。

考虑消息排序或仅传送一次功能是否对您的应用至关重要。如果低延迟是您的首要任务,那么尽量减少使用这些功能有助于减少消息处理延迟。

邮件大小增加

消息大小突然增加可能会增加 Pub/Sub 与客户端之间的传输时间,并减慢客户端中消息的处理时间。

如果您发现传送延迟时间增加,可以使用 topic/message_sizes 指标(按 topic_id 分组)检查消息大小。将消息大小的任何峰值与观察到的性能问题相关联。

缺失邮件

如果您怀疑消息未成功传递给订阅者,则可能是以下原因之一。

具有多个使用者的 Pub/Sub 订阅中的消息分发

在 Pub/Sub 中,消息可能会不均匀地分布在同一订阅的消费者之间。Pub/Sub 会在所有活跃客户端之间对消息进行负载均衡,但其主要目标是最大限度地提高整体消息处理吞吐量并最大限度地减少积压,而不是确保完全均匀的分布。系统会动态地将更多消息定向到处理能力更强的消费者,即那些能够快速确认消息且不受流量控制设置限制的消费者。以不同速度处理消息或在不同条件下(例如资源可用性、网络延迟)运行的消费者可能会处理不同数量的消息。通常,速度更快、响应更及时的消费者将接收和处理更大比例的消息负载。

这种跨多个使用方的分发方法意味着,消息的接收和处理顺序可能会有所不同。因此,除非为订阅启用了消息排序,并且发布者包含排序键,否则无法保证消费者按照消息发布顺序处理消息。

请注意,消息可能已经发送给客户端,而未确认消息的积压并不一定意味着您会在下一次拉取请求中收到这些消息。请注意,消费者可能是指在 Google Cloud 控制台或 Google Cloud CLI 中使用拉取功能的用户,也可能是指在本地运行自定义订阅者来检查消息的用户。

对于一元拉取客户端,您可能会观察到一些拉取请求返回零条消息。如订阅者数量不足部分所述,建议您保持多个未完成的拉取请求,因为即使存在积压,某些请求返回的消息数量也可能少于配置的消息数量上限,甚至为零。这是因为其他使用方可能持有可用消息的租约。

如果消费者未能在确认时限内确认消息,或者对消息进行了否定确认 (NACK),Pub/Sub 将尝试重新传送该消息。此重新传送尝试可以针对订阅中的任何活跃消费者进行,不一定是最初收到该消息的消费者。因此,在消息的生命周期内,系统可能会将特定消息 ID 递送给连接到订阅的多个不同使用方,并尝试由这些使用方进行处理,直到其中一个使用方成功确认该消息。

如果您怀疑存在意外的消息分发行为,请调查是否有多个使用方同时连接到订阅,并检查其各自的效果指标和配置。

按订阅过滤

检查订阅是否附加了过滤条件。如果是,您只会收到与过滤条件匹配的消息。Pub/Sub 服务会自动确认与过滤条件不匹配的消息。考虑过滤条件如何影响待办事项指标。

使用 returnImmediately 选项

如果您的客户端使用的是一元拉取,请检查 returnImmediately 字段是否设置为 true。这是一个已弃用的字段,用于指示 Pub/Sub 服务立即响应拉取请求,即使返回的消息为空也是如此。这可能会导致拉取请求返回 0 条消息,即使存在积压也是如此。

客户端配置问题

如果您使用的是 Java 客户端库,并且正在使用 setChannelProvider() 方法通过自定义 gRPC 渠道初始化订阅者,则应确保在构建 TransportChannelProvider 时,maxInboundMessageSize 设置至少为 20MB(与库的默认值匹配)。如果此值小于 10MB,则大于 maxInboundMessageSize 的邮件将无法正常接收。配置此设置的一些常见方法包括 InstantiatingGrpcChannelProvider.Builder.setMaxInboundMessageSize() 和 ManagedChannelBuilder.maxInboundMessageSize()。

处理重复内容

在以下情况下,订阅者可能会收到重复的消息:

  • 重新提交。客户端对消息进行了否定确认,或确认截止时间已到期。这是预期行为。

  • 重复投放。在成功确认后或确认截止时间到期之前重新发送消息。启用了仅传送一次的订阅不会收到重复传送的消息。

  • 来自发布者的重复消息。由于发布者的应用逻辑,发布者发送了两次消息。Pub/Sub 服务会将这些消息视为不同的 Pub/Sub 消息,并为其分配唯一的消息 ID。

如果消息 ID 相同,则表示 Pub/Sub 重新发送或重新传送了该消息。如果消息 ID 不同,则表示重复发生在发布者上游。

已到期确认时限

当订阅者无法在确认时限内确认消息时,Pub/Sub 中通常会出现消息重复。这会导致消息重新传送,从而产生重复消息。您可以使用 subscription/expired_ack_deadlines_count 指标来衡量订阅者错过确认截止期限的比率。详细了解如何监控确认时限到期。

如需降低重复率,请延长消息时限。

  • 客户端库会自动处理截止时间延长,但您可以配置的最大延长截止时间存在默认限制。
  • 如果您要构建自己的客户端库,请使用 modifyAckDeadline 方法来延长确认时限。

如果订阅者拉取消息的速度快于处理和确认消息的速度,则某些消息可能会过期,需要延长截止期限。不过,如果订阅者仍然不堪重负,重复的截止时间延长最终会失败。在最坏的情况下,这可能会导致订阅者收到大量重复消息,从而加剧积压问题。过期的重复项会生成新的重复项。

为避免订阅者不堪重负,请减少订阅者一次拉取的消息数量。这样,订阅者在截止期限内需要处理的消息就会减少。过期的消息和重新传送的消息更少。

如需减少订阅者一次拉取的消息数量,您需要减少订阅者流控制配置中的未完成消息数量上限设置。没有一刀切的值,因此您必须根据吞吐量和订阅者容量调整未完成消息数上限。请注意,每个应用处理消息的方式不同,确认消息所需的时间也不同。

强制重试

如需强制 Pub/Sub 重试消息,请发送 nack 请求。如果您未使用高级客户端库,请发送 modifyAckDeadline 请求,并将 ackDeadlineSeconds 设置为 0。

排序键

当 Pub/Sub 重新传送带有排序键的消息时,它也会重新传送带有相同排序键的所有后续消息,即使这些消息之前已确认。这样做是为了保留序列的顺序。但是,无法严格保证仅在成功确认序列中的先前消息后才发送依赖消息。

订阅者正在对消息进行否定确认

请参阅订阅者正在对消息进行 NACK。

还原操作

当您对时间戳或快照执行跳转操作时,Pub/Sub 会重新传送在时间戳之后发布或包含在快照中的消息。这种重新传送是预期行为,即使在订阅上启用了仅传送一次,也会发生这种情况。如需了解详情,请参阅使用“仅传送一次”功能进行跳转。

下游去重

订阅者和其他下游系统可能会对消息进行去重。例如,Dataflow 支持仅传送一次。如果您发现下游意外出现重复消息,原因可能在于订阅者,而不是 Pub/Sub。

排查 StreamingPull 订阅问题

请求延迟时间指标与端到端传送延迟时间之间的关系

对于 StreamingPull,指标 serviceruntime.googleapis.com/api/request_latencies 表示数据流打开的时间。该指标无助于确定端到端传送延迟时间。

请勿使用请求延迟时间指标,而应使用递送延迟时间健康状况得分来检查哪些因素导致端到端递送延迟时间增加。

StreamingPull 连接以不正常状态关闭

StreamingPull 流始终以不正常状态关闭。与一元 RPC 的错误状态不同,StreamingPull 的此状态仅表示流已断开连接。请求未失败。因此,虽然 StreamingPull API 的错误率可能高达 100%,但此行为是设计使然。

由于 StreamingPull 流始终会因错误而关闭,因此在诊断错误时检查流终止指标没有帮助。而是应重点关注 StreamingPull 响应指标 subscription/streaming_pull_response_count(按 response_code 或 response_class 分组)。

请查找以下错误:

  • 如果订阅积压消息中有使用已停用的 Cloud KMS 密钥加密的消息,则可能会发生前提条件失败错误。如需恢复拉取,请恢复对密钥的访问权限。

  • 当 Pub/Sub 无法处理请求时,可能会发生不可用错误。这很可能是暂时性情况,客户端库会重试请求。如果您使用的是客户端库,则无需执行任何操作。

  • 如果订阅已被删除或根本不存在,则可能会发生未找到错误。如果您提供了无效的订阅路径,则会出现后一种情况。

其他参考资料