You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Polly熔断与重试策略异常:重试未达配置次数即终止

Polly重试与熔断器组合时未达到配置最大重试次数的问题

问题概述

配置了MaxRetryAttempts = 100的Polly重试策略,结合熔断器后,实际重试在约80次左右就终止,未达到预期的100次。预期重试应持续到以下两种情况之一:

  • 熔断器处于打开状态且达到最大重试次数
  • 熔断器恢复闭合后请求成功返回200状态码

但日志显示第82次重试后熔断器重新打开,随后重试直接终止,无后续重试记录。

代码示例

services.AddHttpClient<SomeAppClient>()
    .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://localhost:7143/"))
    // .AddStandardResilienceHandler();
    .AddResilienceHandler("app-pipeline", pipelinebuilder =>
    {
        pipelinebuilder.AddRetry(new HttpRetryStrategyOptions
        {
            BackoffType = DelayBackoffType.Constant,
            Delay = TimeSpan.FromMilliseconds(100),
            MaxRetryAttempts = 100,
            OnRetry = static args =>
            {
                Serilog.Log.Logger.Information($"Retry {args.AttemptNumber}, after {args.RetryDelay.TotalMilliseconds:F2}ms, due to: {args.Outcome.Result?.StatusCode.ToString() ?? args.Outcome.Exception?.GetType().Name} ");
                return default;
            },
            ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
                .Handle<HttpRequestException>()
                .Handle<BrokenCircuitException>(),
            UseJitter = false
        });

        pipelinebuilder.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
        {
            BreakDuration = TimeSpan.FromSeconds(5),
            FailureRatio = 0.1,
            MinimumThroughput = 2,
            OnClosed = static args =>
            {
                Serilog.Log.Logger.Information("Closing circuit now");
                return default;
            },
            OnOpened = static args =>
            {
                Serilog.Log.Logger.Information("Opening circuit now");
                return default;
            },
            OnHalfOpened = static args =>
            {
                Serilog.Log.Logger.Information("Circuit half opened now");
                return default;
            },
            SamplingDuration = TimeSpan.FromSeconds(2),
            ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
                .Handle<Exception>()
                .HandleResult(r => r.StatusCode == System.Net.HttpStatusCode.InternalServerError)
        });

        pipelinebuilder.AddTimeout(TimeSpan.FromSeconds(1));
    });

日志片段

[08:21:39 INF] Retry 37, after 100.00ms, due to: BrokenCircuitException 
[08:21:40 INF] Retry 36, after 100.00ms, due to: BrokenCircuitException 
[08:21:40 INF] Circuit half opened now
[08:21:40 INF] Opening circuit now
[08:21:40 ERR] Server returned a 500
[08:21:40 INF] Retry 37, after 100.00ms, due to: BrokenCircuitException 
...
[08:21:45 INF] Retry 82, after 100.00ms, due to: BrokenCircuitException 
[08:21:45 INF] Circuit half opened now
[08:21:45 INF] Opening circuit now
[08:21:45 ERR] Server returned a 500

原因分析

核心问题在于重试策略的ShouldHandle未包含对500状态码的处理:

  1. 当熔断器处于打开状态时,每次重试都会触发BrokenCircuitException,该异常被重试策略捕获,因此会持续重试。
  2. 当熔断器的BreakDuration(5秒)到期后,进入半开状态,允许一次实际请求尝试。这次请求返回500状态码,触发熔断器重新打开,但这个500结果不在重试策略的ShouldHandle范围内(重试仅处理HttpRequestException和BrokenCircuitException)。
  3. 由于重试策略不认为500状态码需要重试,因此直接终止整个重试流程,不会继续执行剩余的重试次数。

解决方案

修改重试策略的ShouldHandle配置,加入对500状态码的处理,确保与熔断器的失败判定逻辑对齐:

ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
    .Handle<HttpRequestException>()
    .Handle<BrokenCircuitException>()
    .HandleResult(r => r.StatusCode == System.Net.HttpStatusCode.InternalServerError);

这样,当半开状态下的请求返回500时,重试策略会将其视为需要重试的失败,继续执行剩余的重试次数,直到达到MaxRetryAttempts限制或请求成功。

内容的提问来源于stack exchange,提问作者onefootswill

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.03 21:15:53