查漏补缺之Polly

1. 核心概念:什么是 Polly?为什么需要它?

在分布式环境中(如调用其他微服务、数据库、API),失败是不可避免的。失败可能是瞬态的(短暂的、自恢复的),如:

  • 网络波动
  • 服务暂时过载(HTTP 500、503)
  • 数据库连接瞬间爆满

Polly 的作用 就是帮助你的应用程序优雅地处理这些故障,通过预定义的策略(Policies) 来提高应用的弹性和可用性。

Polly 的两种主要用法:

  1. 弹性策略(Resiliency Policies) :定义当故障发生时该如何反应(如重试、回退、断路)。
  2. 功能策略(Functional Policies) :定义要执行什么(如缓存、超时)。

2. 八大策略详解(核心武器库)

1. 重试(Retry)

用途:处理瞬态故障,简单重试操作。

csharp 复制代码
// 基本重试:重试2次
Policy
  .Handle<HttpRequestException>() // 捕获什么异常
  .Retry(2);

// 带等待时间的重试:重试3次,每次等待一段时间
Policy
  .Handle<HttpRequestException>()
  .WaitAndRetry(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); // 指数退避:2, 4, 8秒

// 带回调的重试:在每次重试时执行一些逻辑(如记录日志)
Policy
  .Handle<HttpRequestException>()
  .WaitAndRetry(3,
      sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
      onRetry: (exception, calculatedWaitDuration, retryCount, context) =>
      {
          logger.LogWarning($"第 {retryCount} 次重试, due to: {exception.Message}. 下次等待 {calculatedWaitDuration}。");
      });

2. 断路器(Circuit Breaker)

用途:防止连续失败导致系统雪崩。当失败超过阈值时,断路器"跳闸",在一段时间内快速拒绝所有请求,给下游服务恢复时间。

csharp 复制代码
// 高级断路器:在连续出现2次异常后,熔断30秒
Policy
    .Handle<HttpRequestException>()
    .CircuitBreaker(
        exceptionsAllowedBeforeBreaking: 2, // 连续失败次数
        durationOfBreak: TimeSpan.FromSeconds(30), // 熔断时长
        onBreak: (ex, breakDelay) => logger.LogWarning($"电路熔断!{breakDelay.TotalSeconds}s 内将快速失败。原因: {ex.Message}"),
        onReset: () => logger.LogInformation("电路重置,恢复正常。"),
        onHalfOpen: () => logger.LogInformation("电路半开,下一次调用将试探性执行。")
    );

3. 超时(Timeout)

用途:保证调用方不会无限期等待,避免资源耗尽。

csharp 复制代码
// 乐观超时:依赖于CancellationToken
Policy.TimeoutAsync(TimeSpan.FromSeconds(10));

// 悲观超时:Polly 会强制中断执行(需要同步支持)
Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic);

4. 回退(Fallback)

用途:当操作失败时,提供一个备用的返回值或执行一个备用动作。

csharp 复制代码
Policy<UserAvatar>
    .Handle<Exception>()
    .FallbackAsync(
        fallbackValue: UserAvatar.GetDefaultAvatar(), // 备用返回值
        onFallbackAsync: async (exception, context, token) =>
        {
            await _logger.LogErrorAsync("获取头像失败,使用默认头像。", exception);
        }
    );

5. 隔板隔离(Bulkhead Isolation)

用途:限制并发执行的数量,防止某个操作的失败耗尽所有资源(如线程池),类似于"舱壁"将故障隔离。

csharp 复制代码
// 最大12个并发,最多允许2个请求在队列中等待
Policy.BulkheadAsync(12, 2);

6. 缓存(Cache)

用途 :将执行结果缓存一段时间,避免重复计算或请求。 (需要 Polly.Caching 包)

csharp 复制代码
// 通常与其他策略组合使用,后面会讲
var cachePolicy = Policy.CacheAsync(redisCacheProvider, TimeSpan.FromMinutes(5));

7. 策略包装(PolicyWrap)

用途 :将多个策略组合成一个强大的"超级策略"。执行顺序是从右到左(从内到外)。 这是 Polly 最强大的功能之一。

csharp 复制代码
// 组合策略:重试 -> 断路器 -> 超时 -> 回退
var retryPolicy = Policy.Handle<HttpRequestException>().WaitAndRetryAsync(...);
var circuitBreakerPolicy = Policy.Handle<HttpRequestException>().CircuitBreakerAsync(...);
var timeoutPolicy = Policy.TimeoutAsync(TimeSpan.FromSeconds(10));
var fallbackPolicy = Policy<UserAvatar>.Handle<Exception>().FallbackAsync(...);

// 构建策略包
var resilientPolicy = Policy.WrapAsync(fallbackPolicy, circuitBreakerPolicy, timeoutPolicy, retryPolicy);
// 执行顺序: Fallback (外层) -> CircuitBreaker -> Timeout -> Retry (内层,最先执行)

8. 无操作(NoOp)

用途:一个什么都不做的策略,常用于测试或禁用策略。

csharp 复制代码
Policy.NoOpAsync();

3. 在 ASP.NET Core 中集成 Polly(与 HttpClientFactory 深度集成)

这是最常见的用法,为出站 HTTP 调用添加弹性策略。

步骤 1:安装 NuGet 包

bash 复制代码
Install-Package Microsoft.Extensions.Http.Polly

步骤 2:在 Program.cs 中配置

csharp 复制代码
var builder = WebApplication.CreateBuilder(args);

// 1. 定义策略
var retryPolicy = HttpPolicyExtensions
    .HandleTransientHttpError() // 内置方法:捕获5xx、408、Network failures
    .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(retryAttempt));

var circuitBreakerPolicy = HttpPolicyExtensions
    .HandleTransientHttpError()
    .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));

// 2. 为命名客户端配置策略
builder.Services.AddHttpClient("RemoteAPI", client =>
    {
        client.BaseAddress = new Uri("https://api.example.com/");
    })
    .AddPolicyHandler(retryPolicy) // 对所有类型的请求应用重试
    .AddTransientHttpErrorPolicy(p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30))) // 快捷方式:直接定义瞬态错误策略
    .AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(10))); // 为HTTP调用添加超时

// 3. 为类型化客户端配置策略
builder.Services.AddHttpClient<IMyService, MyService>()
    .AddTransientHttpErrorPolicy(p => p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));

var app = builder.Build();

步骤 3:在服务中使用 HttpClient

csharp 复制代码
public class MyService : IMyService
{
    private readonly IHttpClientFactory _httpClientFactory;

    public MyService(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    public async Task<Data> GetDataAsync()
    {
        // 从工厂获取配置好策略的客户端
        var client = _httpClientFactory.CreateClient("RemoteAPI");

        // 这个调用会自动受到重试、熔断等策略的保护
        var response = await client.GetAsync("/data");
        response.EnsureSuccessStatusCode();

        return await response.Content.ReadAsAsync<Data>();
    }
}

4. 最佳实践与常见陷阱

  1. 策略应放在调用端:谁发起调用,谁负责处理失败。保护你的出站请求。
  2. 谨慎使用重试
    • 一定要用带退避的重试WaitAndRetry),而不是立即重试,避免加剧下游服务压力。
    • 并非所有错误都要重试404 Not Found400 Bad Request 这种客户端错误重试再多次也没用。使用 HandleTransientHttpError() 可以很好地避免这个问题。
  3. 断路器是服务级别的 :通常一个下游服务对应一个断路器实例,共享状态。确保在依赖注入中以单例形式注册断路器策略(AddPolicyHandler 默认就是单例)。
  4. 策略是单例的 :通过 AddPolicyHandler 添加的策略在容器中是单例的,这意味着它们的状态(如断路器的开闭状态)是共享的。这是期望的行为。
  5. 测试你的策略 :使用单元测试模拟异常和超时,确保你的策略按预期工作。Polly 提供了丰富的回调(onBreak, onRetry)来辅助测试和监控。
  6. 结合日志和监控:在策略的回调中记录日志和发送指标(如重试次数、熔断事件),这对于诊断问题至关重要。

总结:策略选择指南

故障类型 推荐策略 说明
瞬态网络错误 Retry + Circuit Breaker 重试解决瞬时问题,断路器防止雪崩
慢响应/挂起 Timeout 保证调用方不被阻塞
下游服务完全宕机 Circuit Breaker + Fallback 快速失败并提供降级体验
防止资源耗尽 Bulkhead 限制并发,隔离故障
所有以上情况 PolicyWrap 组合多种策略,构建终极弹性方案

通过本指南,你应该已经掌握了 Polly 的核心概念和如何在 ASP.NET Core 中实际使用它。立即行动:从为你最关键的 HTTP 调用添加一个简单的重试策略开始,逐步构建起复杂的弹性模式。

相关推荐
c#上位机7 小时前
wpf中资源的使用
c#·wpf
Zzz_睡不醒10 小时前
JS(正则表达式)
javascript·正则表达式·c#
界面开发小八哥17 小时前
文档控件DevExpress Office File API v25.1新本亮点:重磅升级各类API
c#·.net·界面控件·devexpress·ui开发
周杰伦fans1 天前
C# 代码中的“熵增”概念
大数据·c#
Dm_dotnet1 天前
WPF依赖属性学习
c#
梅坞茶坊1 天前
PHP操作LibreOffice将替换变量后的word文件转换为PDF文件
开发语言·c#
学编程的小白狼1 天前
正运动控制卡学习-网络连接
c#
程序边界2 天前
Unity开发保姆级教程:C#脚本+物理系统+UI交互,3大模块带你通关游戏开发
ui·unity·c#
0wioiw02 天前
C#基础(⑥动态链接库DLL)
开发语言·c#