重试会在服务端已经不堪重负时再添一份压力。gRPC 的 retryThrottling 用令牌桶限制重试。令牌怎么扣、怎么补?什么时候该开?这篇用一个会按概率失败的服务端逐项验证。
不节流 前 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。非法配置在创建客户端时就会报错。