bigywater.tech
微服务 · 86

Envoy 的异常检测、熔断、重试与超时策略

Envoy 的重试该怎么配?重试时要不要避开已经试过的主机?per_try_timeout 能解决慢主机吗?熔断器的阈值溢出时什么样?异常点检测什么时候摘除、为什么摘除不了所有坏主机?这篇用 Envoy 1.39.3 和已知行为的后端逐项量出来。

75.5% → 100.0%
bad 主机有 50% 失败时,不重试与重试 1 次且避开已试过主机的客户端成功率(后端多收到 1.28 倍的请求)
1013 → 229 ms
加上 per_try_timeout 0.2 s 和重试之后,有一个慢主机时的 p99 延迟
163
两个坏主机都被摘除后,Envoy 在 panic 模式下分配的请求数(lb_healthy_panic)

重试

实测 · 本机执行输出
  策略                 | 客户端看到的成功率 | 后端收到的请求数 ÷ 客户端请求数 | Envoy 记录的重试次数 | 重试成功次数 | bad 收到的请求数
  -------------------+-----------+-------------------+---------------+--------+-----------
  (a)不重试             | 75.5%     | 1.00              | 0             | 0      | 1000      
  (b)重试 1 次,避开已试过的主机 | 100.0%    | 1.28              | 570           | 570    | 1157      
  (c)重试 1 次,不避开      | 94.0%     | 1.25              | 500           | 379    | 1250      
  重试把一半请求的失败(轮询到 bad 并且 bad 这次失败,约 25%)变成成功,代价是后端多收到请求;重试时避开已经试过的主机,重试几乎都落在 good 上;不避开时重试仍有机会选到 bad 并再次失败,成功率低一些

重试能把失败变成成功,代价是后端多收到请求;避开已试过的主机比不避开更有效。

per_try_timeout

实测 · 本机执行输出
  配置                                | 成功率  | 延迟 p25(ms) | p50(ms) | p75(ms) | p99(ms) | 延迟超过 150 ms 的请求比例 | slow 后端收到的请求数
  ----------------------------------+------+------------+---------+---------+---------+-------------------+--------------
  (a)只有总超时 2 s                      | 100% | 2          | 505     | 1008    | 1013    | 50%               | 100          
  (b)per_try_timeout 0.2 s + 重试 1 次 | 100% | 3          | 209     | 220     | 229     | 66%               | 133          
  没有 per_try_timeout 时,轮询落到 slow 的请求要等满 1 s;设了 per_try_timeout 之后,第一次尝试 0.2 s 超时就换到 fast 重试,这些请求的延迟变成 0.2 s 多一点,所以 p99 从约 1 s 降到 0.2 s 多;代价是要等满 per_try_timeout 的请求仍然比较多,slow 后端收到的请求数也比 (a) 多——重试本身会让 Envoy 的轮询游标多走几步,我的猜测是这让更多请求第一次落到 slow 上(这个猜测没有另外验证,每次运行具体数字略有不同);超时只是 Envoy 不再等,slow 后端并没有因此少收请求

它缩短了单个请求的等待时间,但不减少慢主机收到的请求。

熔断

实测 · 本机执行输出
  配置                                          | 200 | 503 | 带 x-envoy-overloaded 头的 | 最慢的请求耗时(ms) | upstream_rq_pending_overflow | upstream_cx_overflow | 后端实际收到的请求数
  --------------------------------------------+-----+-----+-------------------------+-------------+------------------------------+----------------------+-----------
  (a)默认阈值                                     | 20  | 0   | 0                       | 316         | 0                            | 0                    | 20        
  (b)max_connections 2、max_pending_requests 2 | 4   | 16  | 16                      | 627         | 16                           | 4                    | 4         
  上游是 HTTP/1.1 时一个连接同时只处理一个请求,超过 max_connections 的请求进入等待队列,等待队列也满了就立即被拒绝(503,响应头带 x-envoy-overloaded);熔断的作用是在上游已经跟不上时快速失败,而不是把更多请求堆在上游前面

连接数和等待队列满了之后,多出的请求立即返回 503(x-envoy-overloaded)。

异常点检测

实测 · 本机执行输出
  (a)h3 坏,max_ejection_percent 50:
  时间段     | 请求数 | 200 | 500 | 500 的比例
  --------+-----+-----+-----+--------
  0~3 s   | 30  | 27  | 3   | 10%    
  3~6 s   | 30  | 30  | 0   | 0%     
  6~9 s   | 30  | 25  | 5   | 17%    
  9~12 s  | 30  | 29  | 1   | 3%     
  12~15 s | 30  | 30  | 0   | 0%     
  15~18 s | 30  | 30  | 0   | 0%     
  18~21 s | 30  | 28  | 2   | 7%     
  21~24 s | 30  | 26  | 4   | 13%    
    Envoy 的异常点检测统计:ejections_enforced_total = 3,ejections_active(结束时)= 1,ejections_consecutive_5xx = 3;lb_healthy_panic(进入 panic 模式后被选主机的请求数)= 0
  (b)h2、h3 都坏,max_ejection_percent 100:
  时间段     | 请求数 | 200 | 500 | 500 的比例
  --------+-----+-----+-----+--------
  0~3 s   | 30  | 11  | 19  | 63%    
  3~6 s   | 30  | 10  | 20  | 67%    
  6~9 s   | 30  | 13  | 17  | 57%    
  9~12 s  | 30  | 12  | 18  | 60%    
  12~15 s | 30  | 10  | 20  | 67%    
  15~18 s | 30  | 12  | 18  | 60%    
  18~21 s | 30  | 11  | 19  | 63%    
  21~24 s | 30  | 12  | 18  | 60%    
    Envoy 的异常点检测统计:ejections_enforced_total = 6,ejections_active(结束时)= 2,ejections_consecutive_5xx = 6;lb_healthy_panic(进入 panic 模式后被选主机的请求数)= 163
  (a)刚开始 h3 收到约三分之一的请求并且全部失败,连续 3 次 5xx 之后 h3 被摘除,失败消失;6 s 的摘除时间到了之后 h3 被放回来,又收到请求、失败,再被摘除;这一次摘除之后要到约 18 s 才再有失败,比 6 s 的 base_ejection_time 长得多(摘除时间随摘除次数增加是我对文档的了解,表里的先后是实测)。(b)两个坏主机被摘除之后只剩 1 个健康主机,健康比例低于默认的 panic 阈值,按 lb_healthy_panic 的计数看 Envoy 进入了 panic 模式——忽略摘除状态,把请求分给所有主机,坏主机重新收到流量,失败的比例和没有异常点检测时差不多(约三分之二)
ERRORS {}

连续 5xx 会摘除主机;健康主机比例低于 panic 阈值时,Envoy 会忽略摘除状态。

分步演示

Envoy 的重试、熔断与异常点检测
本步的对象

    本文用到的工具与边界

    • 本机 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,数字每次运行会有小幅抖动。Envoy 1.39.3(--concurrency 1),静态配置;后端是本机的 Python 服务(固定的失败率和延迟);各节只跑了一次(第二节 200 个请求、第三节 20 个并发请求),数字每次运行略有不同(例如熔断那节开发中的一次运行是 3 个成功、17 个 503;重试那节成功率相差一个百分点);第二节 slow 后端收到的请求比不重试时多,我的解释(重试推进轮询游标)没有另外验证
    • 没有验证:retry_budget(用百分比限制重试);HTTP/2 上游时 max_requests 的作用;outlier detection 的 success_rate 和 local_origin 类型;重试的退避(retry_back_off)与 retriable_status_codes;熔断器的 track_remaining;更多主机时 panic 阈值的边界(默认 50% 来自我对文档的了解,本节只观察到了触发)
    京ICP备2026067502号-1京公网安备11010502063487号