请求对冲机制
说明什么是请求对冲以及如何进行配置
概述
对冲是 gRPC 支持的两种可配置重试策略之一。启用对冲后,gRPC 客户端会向不同后端发送多份相同请求 ,并使用最先收到的响应。客户端随后会取消所有未完成的请求,并将响应传递给应用层。

基础对冲流程
- 客户端应用发起 gRPC 调用
- gRPC 客户端库向多个后端发送 RPC
- 响应最快的后端返回成功结果
- 客户端接收响应并向其他后端发送取消指令
- 将响应与状态码返回给应用层
使用场景
对冲是一种用于降低大规模分布式系统长尾延迟 的技术。虽然简单实现可能会大幅增加后端服务器负载,但可以在仅小幅增加负载的前提下,获得大部分延迟降低效果。
关于长尾延迟的深入讨论,可参考 Jeff Dean 与 Luiz André Barroso 的经典文章《The Tail At Scale》。
在 gRPC 中配置对冲机制
对冲可通过 gRPC 服务配置 按方法粒度进行配置,配置包含以下参数:
"hedgingPolicy": {
"maxAttempts": 整数,
"hedgingDelay": JSON proto3 Duration 类型,
"nonFatalStatusCodes": gRPC 状态码数组(整数或字符串)
}
- maxAttempts :等待成功响应时,最大并发请求数。必填项,必须指定;若大于 5,gRPC 会按 5 处理。
- hedgingDelay :等待成功响应期间,客户端发送下一个请求前的等待时间。可选项,未指定则所有
maxAttempts个请求会同时发送。 - nonFatalStatusCodes :可选项,gRPC 状态码列表。若某个对冲请求失败的状态码不在此列表中,客户端会取消所有未完成请求并将响应返回应用。
对冲策略
当应用发起包含 hedgingPolicy 配置的 RPC 调用时:
- 原始 RPC 会立即发送,与普通非对冲调用一致。
- 经过
hedgingDelay仍未收到成功响应,则发送第二个 RPC。 - 再经过
hedgingDelay仍无响应,则发送第三个 RPC,依此类推,直至达到maxAttempts。 - gRPC 调用的超时时间作用于整个对冲请求链;超时后无论是否有请求在执行,均判定失败。
收到成功响应时:
- 取消所有未完成的对冲请求,将响应返回客户端应用层。
收到非致命状态码错误时:
- 立即发送下一个对冲请求,跳过剩余
hedgingDelay。
收到其他状态码时:
- 取消所有未完成 RPC,将错误返回应用层。
若所有对冲请求均失败:
- 不再额外重试。对冲本质可理解为在收到失败前就提前发起重试。
若收到服务端回退(禁止重试):
- 本次调用不再发送后续对冲请求。
对冲 RPC 的限流
gRPC 提供对冲 RPC 限流机制,防止服务端过载。限流同样通过服务配置的 RetryThrottlingPolicy 配置:
"retryThrottling": {
"maxTokens": 10,
"tokenRatio": 0.1
}
- 客户端为每个服务名维护一个
token_count,初始值为maxTokens。 - 每次 RPC 失败:
token_count减 1。 - 每次 RPC 成功:
token_count增加tokenRatio。
对冲限流规则:
- 第一个请求始终发送。
- 后续对冲请求仅当
token_count > maxTokens / 2时才发送。 - 若
token_count小于等于阈值,后续对冲请求直接取消;若无已发送的对冲 RPC,则将失败返回应用。
仅非致命状态码 或服务端回退禁止重试的失败,会计入限流统计,避免将非法参数等客户端错误与服务端故障混淆。
服务端回退机制
服务端可通过在响应元数据中显式设置回退指令:
- 回退指令为禁止重试:不再发送后续对冲请求。
- 回退指令为指定延迟后重试:等待指定时间后再发送下一个对冲请求。
服务端回退使用元数据键:grpc-retry-pushback-ms
- 值为 ASCII 编码的 32 位有符号整数,表示等待毫秒数。
- 值为负数或无法解析:视为服务端要求完全禁止重试。
参考资源
语言支持
| Language | Example |
|---|---|
| Java | Java example |
| C++ | Not yet available |
| Go | Not yet supported |