bigywater.tech
微服务 · 2

重试的放大效应:多层各重试三次是多少次请求(含网关重试加服务重试)

三层各重试三次,最底下的服务到底会收到多少请求?重试什么时候有用,什么时候只是添乱?加上网关的 proxy_next_upstream 又会怎样?这篇用本机起的小服务和 nginx,把放大倍数量出来,和公式对照。

81 倍
4 层各试 3 次、C 完全不可用时,每个 client 请求让 C 收到的调用数(3 的 4 次方)
1987 对 120
容量有限的 C 在处理变慢的那一秒收到的调用数:3 层各试 3 次 对 没有重试层(成功率 49% 对 48%)
18 倍
client 试 3 次 × nginx 换 2 个上游 × 服务试 3 次时,每个 client 请求让 C 收到的调用数

层数相乘

实测 · 本机执行输出
  重试层数 | 说明        | C 收到的调用 | 每个 client 请求 | 3 的 L 次方
  -----+-----------+---------+--------------+---------
  0    | 没有重试层     | 20      | 1            | 1       
  1    | 1 层各试 3 次 | 60      | 3            | 3       
  2    | 2 层各试 3 次 | 180     | 9            | 9       
  3    | 3 层各试 3 次 | 540     | 27           | 27      
  4    | 4 层各试 3 次 | 1620    | 81           | 81      
  (连接类错误 0 次;不是超时,应为 0)

每一层单独看只多试 2 次,合起来是 3 的层数次方;C 完全不可用时每多一层乘 3。

失败率与放大

实测 · 本机执行输出
  C 的失败率 p | 实测:每个请求 C 收到的调用 | 公式   | 实测:client 拿到成功 | 公式    
  ---------+-----------------+------+----------------+-------
  10%      | 1.11            | 1.11 | 100.0%         | 100.0%
  30%      | 1.45            | 1.43 | 100.0%         | 100.0%
  50%      | 2.06            | 2.00 | 100.0%         | 100.0%
  90%      | 9.88            | 9.42 | 92.8%          | 94.2% 
  (连接类错误 0 次;不是超时,应为 0)
  C 偶发失败时重试确实让成功率变高了(p = 30% 时单次调用成功率 70%,3 层重试后 600 个请求全部成功),代价是 C 多收到的调用;p 越高放大越厉害,而这时 C 恰恰最撑不住

失败率低时重试几乎不增加负载就把成功率拉到 100%;失败率越高放大越大,而这时 C 最撑不住。

容量有限时的放大

实测 · 本机执行输出
            |            | 第 0 秒 | 1    | 2    | 3    | 4    | 5    | 6    | 7   
  ----------+------------+-------+------+------+------+------+------+------+-----
  没有重试层     | 收到的调用/s    | 120   | 120  | 120  | 120  | 120  | 120  | 120  | 120 
            | client 成功率 | 100%  | 100% | 48%  | 71%  | 99%  | 100% | 99%  | 99% 
  3 层各试 3 次 | 收到的调用/s    | 120   | 155  | 1987 | 1202 | 120  | 120  | 120  | 127 
            | client 成功率 | 100%  | 99%  | 49%  | 73%  | 100% | 100% | 100% | 100%
  (连接类错误 0 次;不是超时,应为 0)
  触发的那 1.5 秒里,有重试层时 C 收到的调用是没有重试层时的十几倍;触发结束后的下一秒就接近恢复;这个实验里请求是同步重试的,重试不会在触发结束后继续累积,所以最终能恢复。负载更高(160/s)时,发请求的这个 Python 进程自己撑不住(大量连接错误),那一档的数字不可信,没有放进来

C 容量降低的那一秒,重试把 C 收到的调用乘了十几倍,成功率却没有提高。

网关的重试

实测 · 本机执行输出
  配置                                                    | C 收到的调用 | 每个 client 请求 | client 最终看到的状态码  
  ------------------------------------------------------+---------+--------------+------------------
  client 试 1 次 / nginx 默认                               | 60      | 3.0          | {503: 20}        
  client 试 3 次 / nginx 默认                               | 180     | 9.0          | {503: 20}        
  client 试 1 次 / 加 http_503(上游 server 保持默认 max_fails=1) | 9       | 0.5          | {503: 1, 502: 19}
  client 试 1 次 / 加 http_503 + max_fails=0               | 120     | 6.0          | {503: 20}        
  client 试 3 次 / 加 http_503 + max_fails=0               | 360     | 18.0         | {503: 20}        
  (连接类错误 0 次;不是超时,应为 0)
  每一层的重试次数是相乘的:client 3 × nginx 换上游 2 × 服务 3 = 18;各层单独看都“只试几次”,合起来是放大的根源。第三行的另一个后果:http_503 让 nginx 把每个 503 记成一次失败,默认的 max_fails=1 / fail_timeout=10s 会把两个上游都摘掉,之后的请求是 nginx 自己回的 502(error log:no live upstreams),根本到不了服务

nginx 的 proxy_next_upstream 又乘一层;加上 http_503 还会触发上游被摘除,出现 nginx 自己回的 502。

分步演示

重试的放大效应
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.8 / Go / nginx / Envoy / etcd / Consul / Nacos / OpenTelemetry / Prometheus(按各篇注明用到哪些及版本);所有服务都是本机自己起的进程,端口 9150 以上,只监听 127.0.0.1,不连任何外部服务,不碰本机已有的实例;故障用 kill -9 进程、让连接断开或在代码里注入延迟与错误来制造;计时用 time.perf_counter,数字每次运行会有小幅抖动。本篇的服务是 Python 3.14 http.server(线程化)在本进程里起的,C 的失败用带种子的随机数按概率注入;网关是 Homebrew 的 nginx 1.31.6;client 对同一端口复用 keep-alive 连接(否则几万次短连接会把本机临时端口用光,出现大量连接错误);实验里连接类错误 0 次
    • 没有验证:负载更高(160/s)的情形——发请求的这个 Python 进程自己撑不住(大量连接错误),那一档的数字不可信,没有放进文章;异步重试(重试请求在触发结束后继续堆积)导致的持续过载,这里的重试是同步的,触发结束后下一秒就恢复了;gRPC、Envoy 等其他代理的重试默认值;真实网络上的延迟
    京ICP备2026067502号-1京公网安备11010502063487号