bigywater.tech
gRPC · 19

重试节流:retryThrottling 的令牌桶

重试会在服务端已经不堪重负时再添一份压力。gRPC 的 retryThrottling 用令牌桶限制重试。令牌怎么扣、怎么补?什么时候该开?这篇用一个会按概率失败的服务端逐项验证。

120 → 43
服务端一直失败时,40 个调用让服务端收到的次数(不节流 → 节流)
60 多次
令牌用光之后,连续成功多少次才重新允许重试(tokenRatio 0.1)
0.6% → 20.0%
每次尝试 20% 概率失败时,节流让最终失败率上升

令牌桶

实测 · 本机执行输出
  不节流  前 12 个调用服务端各收到 [3 3 3 3 3 3 3 3 3 3 3 3] 次;40 个调用服务端共收到 120 次
  节流   前 12 个调用服务端各收到 [3 2 1 1 1 1 1 1 1 1 1 1] 次;40 个调用服务端共收到 43 次

桶的容量是 maxTokens,初始是满的。每次可重试错误码的失败扣 1 个;当前令牌数降到 maxTokens/2 及以下,就不再重试。每个成功的调用补 tokenRatio 个,补满为止。

恢复很慢

实测 · 本机执行输出
  令牌用光后成功   0 次 → 再来一个必然失败的调用:服务端收到 1 次
  令牌用光后成功  20 次 → 再来一个必然失败的调用:服务端收到 1 次
  令牌用光后成功  50 次 → 再来一个必然失败的调用:服务端收到 1 次
  令牌用光后成功  60 次 → 再来一个必然失败的调用:服务端收到 1 次
  令牌用光后成功  70 次 → 再来一个必然失败的调用:服务端收到 2 次
  令牌用光后成功  80 次 → 再来一个必然失败的调用:服务端收到 3 次
  令牌用光后成功 100 次 → 再来一个必然失败的调用:服务端收到 3 次

tokenRatio 为 0.1 时,从空桶补到允许重试要连续成功 60 多次。故障刚过去时,重试还在被节流。

保护与代价

实测 · 本机执行输出
  每次尝试失败概率    5% 不节流 :每个调用平均让服务端处理 1.05 次;最终失败 0.0%
  每次尝试失败概率    5% 节流  :每个调用平均让服务端处理 1.05 次;最终失败 0.0%
  每次尝试失败概率   20% 不节流 :每个调用平均让服务端处理 1.23 次;最终失败 0.6%
  每次尝试失败概率   20% 节流  :每个调用平均让服务端处理 1.00 次;最终失败 20.0%
  每次尝试失败概率   50% 不节流 :每个调用平均让服务端处理 1.77 次;最终失败 13.0%
  每次尝试失败概率   50% 节流  :每个调用平均让服务端处理 1.00 次;最终失败 51.8%
  每次尝试失败概率  100% 不节流 :每个调用平均让服务端处理 3.00 次;最终失败 100.0%
  每次尝试失败概率  100% 节流  :每个调用平均让服务端处理 1.00 次;最终失败 100.0%
  按令牌的进出估算的平衡点(每次尝试失败扣 1,每次成功加 0.1):失败概率 p 满足 p = 0.1 × (1 − p) 时令牌既不涨也不跌,p ≈ 9.1%(忽略了重试本身的失败,只是粗算)

彻底故障时节流把服务端被放大的负载压回 1 倍,避免重试风暴;但在部分故障时,它也会让本来能靠重试成功的调用直接失败。

配置检查

实测 · 本机执行输出
  {"maxTokens":0,"tokenRatio":0.1}           → NewClient 失败:grpc: the provided default service config is invalid: invalid retry throttling config: maxTokens (0) out of range (0, 1000]
  {"maxTokens":1001,"tokenRatio":0.1}        → NewClient 失败:grpc: the provided default service config is invalid: invalid retry throttling config: maxTokens (1001) out of range (0, 1000]
  {"maxTokens":10,"tokenRatio":0}            → NewClient 失败:grpc: the provided default service config is invalid: invalid retry throttling config: tokenRatio (0) may not be negative
  {"maxTokens":1000,"tokenRatio":0.1}        → NewClient 成功

maxTokens 取值 (0, 1000],tokenRatio 大于 0。非法配置在创建客户端时就会报错。

分步演示

重试节流
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Go 1.27.1 / grpc-go v1.84.0 / protobuf-go v1.36.12 / golang.org/x/net v0.57.0 / protoc 36.2(Homebrew);服务端和客户端都是本机起的 Go 进程(127.0.0.1,端口 9800 以上,不访问任何外部服务);需要看线上字节时用我自己写的 TCP 抓包代理加 golang.org/x/net/http2 的 Framer 解析 HTTP/2 帧;计时用 time.Now 或 perf_counter,数字每次运行会有小幅抖动。服务端按固定随机种子的概率让每次尝试失败,所以每次运行的统计数字相同;retryPolicy 固定为 maxAttempts 3、很短的退避;令牌的扣与补读自 clientconn.go 的 retryThrottler 与 stream.go 的 shouldRetry,并用实验对照(序列 3 2 1 1…、恢复在成功 70 次时出现都与源码一致)
    • 没有验证:多个方法共用一个桶时的互相影响(每个 ClientConn 一个桶,同一个 ClientConn 上所有方法共享——这一点读自源码,没有单独做实验);并发客户端下的桶的变化;tokenRatio 小数位的精度限制;服务端 pushback 对令牌的影响(源码里 pushback 为负数时会再扣一次);流式 RPC 的重试节流
    京ICP备2026067502号-1京公网安备11010502063487号