地球人家 發表於 2026-5-3 12:29:00

.NET Polly 重试、熔断、降级

<div>
<div dir="ltr">
<div>
<h2>一、安装依赖</h2>
<div>&nbsp;</div>
<div>
<div dir="ltr">
<div>
<pre><code># 基础包
Install-Package Polly</code></pre>
</div>
</div>
</div>
<pre><code># HTTP 集成(推荐) Install-Package Microsoft.Extensions.Http.Polly </code></pre>
</div>
<div>&nbsp;</div>
</div>
</div>
<hr>
<div>&nbsp;</div>
<h2>二、策略与代码</h2>
<div>&nbsp;</div>
<h3>1. 重试(Retry):自愈瞬时故障</h3>
<div>&nbsp;</div>
<div>作用:网络抖动、偶发超时、5xx 等临时错误自动重试,避免单次失败影响业务。
<div>&nbsp;</div>
常用模式:固定次数、指数退避(重试间隔越来越长)、带抖动防风暴(前两者基础上加随机时间)。</div>
<div>&nbsp;</div>
<h4>示例:HTTP 请求指数退避重试(3 次)</h4>
<div>&nbsp;</div>
<div>
<div dir="ltr">
<div>
<pre><code>using Polly;
using Polly.Retry;
using System.Net.Http;

// 1. 定义重试策略(异步)
var retryPolicy = Policy&lt;HttpResponseMessage&gt;
    .Handle&lt;HttpRequestException&gt;()          // 捕获网络异常
    .OrResult(r =&gt; !r.IsSuccessStatusCode)// 捕获非成功状态码
    .WaitAndRetryAsync(
      retryCount: 3,                      // 最多重试3次
      sleepDurationProvider: attempt =&gt;   // 指数退避:1s→2s→4s + 随机抖动
            TimeSpan.FromSeconds(Math.Pow(2, attempt))
            + TimeSpan.FromMilliseconds(new Random().Next(100, 300)),
      onRetryAsync: (ex, span, attempt, _) =&gt;
      {
            Console.WriteLine($"第{attempt}次重试,等待{span.TotalSeconds:F1}s,异常:{ex?.Message}");
            return Task.CompletedTask;
      }
    );

// 2. 执行受保护的调用
var httpClient = new HttpClient();
var response = await retryPolicy.ExecuteAsync(async () =&gt;
{
    var res = await httpClient.GetAsync("https://api.example.com/data");
    res.EnsureSuccessStatusCode();
    return res;
});
</code></pre>
</div>
<div>&nbsp;</div>
</div>
</div>
<hr>
<div>&nbsp;</div>
<h3>2. 熔断(Circuit Breaker):防雪崩</h3>
<div>&nbsp;</div>
<div>作用:连续失败达阈值后,主动切断请求(快速失败),熔断期内不发起真实调用;到期进入半开试探,成功则恢复。
<div>&nbsp;</div>
状态:Closed(正常)→ Open(熔断)→ Half-Open(试探)→ Closed/Open。</div>
<div>&nbsp;</div>
<h4>示例:连续 5 次失败熔断 10 秒</h4>
<div>&nbsp;</div>
<div>
<div dir="ltr">
<div>
<pre><code>using Polly.CircuitBreaker;

// 定义熔断策略
var circuitPolicy = Policy&lt;HttpResponseMessage&gt;
    .Handle&lt;HttpRequestException&gt;()
    .OrResult(r =&gt; r.StatusCode == HttpStatusCode.InternalServerError)
    .CircuitBreakerAsync(
      exceptionsAllowedBeforeBreaking: 5,// 连续5次失败触发熔断
      durationOfBreak: TimeSpan.FromSeconds(10), // 熔断10秒
      onBreak: (ex, span) =&gt; Console.WriteLine($"【熔断】断开{span.TotalSeconds}s,异常:{ex.Message}"),
      onReset: () =&gt; Console.WriteLine("【熔断】恢复正常"),
      onHalfOpen: () =&gt; Console.WriteLine("【熔断】半开,试探请求1次,成功则恢复正常,失败则继续熔断10秒")
    );

// 执行
try
{
    var response = await circuitPolicy.ExecuteAsync(async () =&gt;
    {
      var res = await httpClient.GetAsync("https://api.example.com/data");
      res.EnsureSuccessStatusCode();
      return res;
    });
}
catch (BrokenCircuitException)
{
    Console.WriteLine("熔断器已打开,拒绝请求");
}
</code></pre>
</div>
<div>&nbsp;</div>
</div>
</div>
<hr>
<div>&nbsp;</div>
<h3>3. 降级(Fallback):兜底响应</h3>
<div>&nbsp;</div>
<div>作用:所有重试 / 熔断都失败后,返回预设降级结果(默认值、缓存、简化数据),保证流程不中断。</div>
<div>&nbsp;</div>
<h4>示例:HTTP 失败返回空数据降级</h4>
<div>&nbsp;</div>
<div>
<div dir="ltr">
<div>
<pre><code>using Polly.Fallback;

// 定义降级策略
var fallbackPolicy = Policy&lt;HttpResponseMessage&gt;
    .Handle&lt;Exception&gt;()
    .OrResult(r =&gt; !r.IsSuccessStatusCode)
    .FallbackAsync(
      fallbackAction: _ =&gt;
      {
            // 降级响应:与原接口同结构、同 Content-Type
            var fallback = new HttpResponseMessage(HttpStatusCode.OK)
            {
                Content = new StringContent(
                  "{\"code\":0,\"data\":null,\"msg\":\"服务降级,稍后重试\"}",
                  Encoding.UTF8,
                  "application/json"
                )
            };
            return Task.FromResult(fallback);
      },
      onFallbackAsync: (ex, _) =&gt;
      {
            Console.WriteLine($"【降级】触发,异常:{ex?.Message}");
            return Task.CompletedTask;
      }
    );
</code></pre>
</div>
<div>&nbsp;</div>
</div>
</div>
<div>&nbsp;</div>
<hr>
<div>&nbsp;</div>
<h2>三、策略组合</h2>
<div>&nbsp;</div>
<h3>1. 组合顺序</h3>
<div>&nbsp;</div>
<div>外层:熔断 → 中层:重试 → 内层:降级
<div>&nbsp;</div>
熔断控制整体开关,重试处理瞬时故障,降级兜底所有失败。</div>
<div>&nbsp;</div>
<h4>示例:熔断 + 重试 + 降级组合(PolicyWrap)</h4>
<div>&nbsp;</div>
<div>
<div dir="ltr">
<div>
<pre><code>// 组合策略(Wrap)
var resilientPolicy = Policy.WrapAsync(
    circuitPolicy,   // 外层:熔断(先判断是否熔断)
    retryPolicy,   // 中层:重试(熔断关闭时才重试)
    fallbackPolicy   // 内层:降级(都失败则兜底)
);

// 最终调用
var finalResponse = await resilientPolicy.ExecuteAsync(async () =&gt;
{
    var res = await httpClient.GetAsync("https://api.example.com/data");
    res.EnsureSuccessStatusCode();
    return res;
});
</code></pre>
</div>
<div>&nbsp;</div>
</div>
</div>
<h3>2. ASP.NET Core + HttpClientFactory 集成</h3>
<div>&nbsp;</div>
<div>
<div dir="ltr">
<div>
<pre><code>// Program.cs
var builder = WebApplication.CreateBuilder();

// 1. 定义策略
static IAsyncPolicy&lt;HttpResponseMessage&gt; GetRetryPolicy()
{
    return HttpPolicyExtensions
      .HandleTransientHttpError()
      .WaitAndRetryAsync(3, attempt =&gt; TimeSpan.FromSeconds(Math.Pow(2, attempt)));
}

static IAsyncPolicy&lt;HttpResponseMessage&gt; GetCircuitPolicy()
{
    return HttpPolicyExtensions
      .HandleTransientHttpError()
      .CircuitBreakerAsync(5, TimeSpan.FromSeconds(10));
}

static IAsyncPolicy&lt;HttpResponseMessage&gt; GetFallbackPolicy()
{
    return Policy&lt;HttpResponseMessage&gt;
      .Handle&lt;Exception&gt;()
      .OrResult(r =&gt; !r.IsSuccessStatusCode)
      .FallbackAsync(_ =&gt; Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
      {
            Content = new StringContent("{\"data\":null}", Encoding.UTF8, "application/json")
      }));
}

// 2. 注册带策略的 HttpClient
builder.Services.AddHttpClient("ResilientClient")
    .AddPolicyHandler(GetCircuitPolicy())
    .AddPolicyHandler(GetRetryPolicy())
    .AddPolicyHandler(GetFallbackPolicy());

var app = builder.Build();
app.Run();
</code></pre>
</div>
<div>&nbsp;</div>
</div>
</div>
<hr>
<div>&nbsp;</div>
<h2>四、实践</h2>
<div>&nbsp;</div>
<ol>
<li>重试:只用于瞬时、可自愈故障(网络、5xx);不用于业务错误(400/404);加抖动防重试风暴。</li>
<li>熔断:全局单例 Policy,避免多实例导致计数失效;阈值按业务调整(敏感易抖动,宽松易雪崩)。</li>
<li>降级:降级响应与原接口结构 / Content-Type 一致,避免上游解析异常;记录降级日志用于监控。</li>
<li>组合:熔断在外、重试在内,防止熔断打开后仍做无效重试。</li>
<li>监控:监听 <code>onBreak/onReset/onFallback</code>,接入 Prometheus/Grafana 告警。</li>
</ol><br><br>
来源:https://www.cnblogs.com/chuansheng/p/19916139
頁: [1]
查看完整版本: .NET Polly 重试、熔断、降级