请求抢先 (Request Hedging)

概述

抢先是 gRPC 支持的两种可配置重试策略之一。在抢先策略中,gRPC 客户端向不同的后端发送相同请求的多个副本,并使用第一个收到的响应。随后,客户端取消任何未完成的请求,并将响应转发给应用程序。

image 2024 12 24 11 23 43 823

使用场景

抢先是一种减少大规模分布式系统尾延迟(tail latency)的技术。虽然初步的实现可能会给后端服务器增加大量负载,但实际上,通过适当配置,仍然能够显著减少延迟,并且仅适度增加负载。

要深入了解尾延迟,您可以参考 Jeff Dean 和 Luiz André Barroso 撰写的经典文章 The Tail At Scale

在 gRPC 中配置抢先

抢先可以通过 gRPC 服务配置(Service Config)进行配置,支持按方法粒度进行设置。配置包含以下选项:

"hedgingPolicy": {
  "maxAttempts": INTEGER,
  "hedgingDelay": JSON proto3 Duration type,
  "nonFatalStatusCodes": JSON array of grpc status codes (int or string)
}
  • maxAttempts: 在等待成功响应期间最多允许的并发请求数量。此字段为必填项,必须指定。如果指定的值大于 5,gRPC 会将值限制为 5

  • hedgingDelay: 客户端在等待成功响应时,发送下一个请求前需要等待的时间。此字段是可选的,如果未指定,maxAttempts 数量的请求会同时发送。

  • nonFatalStatusCodes: 可选的 grpc 状态码列表。如果任何一个抢先请求返回的状态码不在此列表中,所有未完成的请求将会被取消,并将错误响应返回给应用程序。

抢先策略

当应用程序发起包含 hedgingPolicy 配置的 RPC 调用时,原始 RPC 会立即发送,就像标准的非抢先调用一样。在 hedgingDelay 过期且没有收到成功响应的情况下,第二个 RPC 会被发送。如果第二个 RPC 仍未收到响应,再过 hedgingDelay 后,会发送第三个 RPC,以此类推,直到达到 maxAttempts。gRPC 调用的截止时间适用于整个抢先请求链。一旦截止时间到达,无论当前有多少个 RPC 正在进行,操作都会失败,且不管是否使用抢先配置。

当收到成功响应(来自任何一个抢先请求)时,所有未完成的抢先请求会被取消,响应将被返回给客户端应用层。

如果某个抢先请求返回了非致命状态码(由 nonFatalStatusCodes 字段控制),则会立即发送下一个抢先请求,跳过其抢先延迟。如果收到任何其他状态码,所有未完成的 RPC 会被取消,错误响应将返回给客户端应用层。

如果所有抢先请求都失败,则不会进行额外的重试。实际上,抢先可以被视为在接收到失败响应之前就对原始 RPC 进行重试。

如果服务器返回了指示不进行重试的推送回退(pushback),则不应再为该调用发送进一步的抢先请求。

限制抢先 RPC

gRPC 提供了一种方式来限制抢先 RPC 的数量,以防止服务器过载。可以通过服务配置(Service Config)中的 RetryThrottlingPolicy 消息来进行配置。限制配置包含以下内容:

"retryThrottling": {
  "maxTokens": 10,
  "tokenRatio": 0.1
}

对于每个服务器名称,gRPC 客户端会维护一个 token_count,初始值为 max_tokens。每个外发的 RPC(无论调用的是哪个服务或方法)都会改变 token_count,其规则如下:

  • 每个失败的 RPC 会使 token_count 减少 1。

  • 每个成功的 RPC 会使 token_count 增加 token_ratio

在抢先策略中,第一次请求始终会被发送,但后续的抢先请求只有在 token_count 大于阈值(定义为 max_tokens / 2)时才会被发送。如果 token_count 小于或等于阈值,抢先请求会被取消,如果没有其他已经发送的抢先 RPC,则失败会返回给客户端应用层。

仅失败状态码为非致命状态码,或者收到推送回退响应要求不重试的请求,会被计入限制策略中的失败。这可以避免将服务器故障与格式错误请求(如 INVALID_ARGUMENT 状态码)混淆。

服务器推送回退

服务器可以通过在响应中设置元数据来明确指示推送回退。如果推送回退指示不重试,则不再发送进一步的抢先请求。如果推送回退指示在给定的延迟后重试,则下一次抢先请求(如果有)将在给定延迟过后发送。

服务器推送回退使用元数据键 grpc-retry-pushback-ms 来指定。其值为一个 ASCII 编码的有符号 32 位整数,表示在发送下一个抢先请求之前需要等待的毫秒数。如果推送回退的值为负数或无法解析,则视为服务器要求客户端不进行重试。

语言支持

语言 示例

Java

Java 示例

C++

暂未支持

Go

暂未支持