【进阶】C#.NET 处理瞬态故障 polly库 详解

简介

Polly 是处理 .NET 应用中瞬态故障的强大工具,通过提供丰富的弹性策略,帮助开发者构建更健壮、更具弹性的应用程序。合理使用 Polly 可以显著提高应用在面对网络波动、服务临时不可用等问题时的稳定性。

Polly 核心策略详解

Polly 提供七种策略应对不同故障场景,均支持流畅 API 链式调用

  • 重试 (Retry):短暂故障(如网络抖动)

  • 熔断 (Circuit Breaker):持续故障避免系统过载

  • 超时 (Timeout):防止长时间阻塞

  • 隔板隔离 (Bulkhead):限制并发资源消耗

  • 回退 (Fallback):失败时提供降级响应

  • 缓存 (Cache):高频数据加速访问

  • 策略包装 (PolicyWrap):组合多策略应对复杂故障

应用场景:

  • 调用远程服务(REST API、微服务)

  • 数据库操作

  • 文件系统访问

  • 任何可能出现暂时性故障的操作

示例 - 熔断+重试组合策略

csharp 复制代码
var policy = Policy.Wrap(
    Policy.Handle<HttpRequestException>()
        .CircuitBreaker(5, TimeSpan.FromSeconds(30)),
    Policy.Handle<TimeoutException>()
        .WaitAndRetry(3, attempt => TimeSpan.FromSeconds(attempt))
);
policy.Execute(() => CallExternalService());

Polly 基本使用

安装 NuGet 包

shell 复制代码
dotnet add package Polly

基本策略使用模式

csharp 复制代码
// 创建策略
var policy = Policy
    .Handle<HttpRequestException>()  // 处理特定异常
    .Retry(3);  // 重试3次

// 执行受策略保护的操作
await policy.ExecuteAsync(async () =>
{
    // 可能会失败的操作(如HTTP请求)
    var response = await httpClient.GetAsync("https://api.example.com/data");
    response.EnsureSuccessStatusCode();
    return await response.Content.ReadAsStringAsync();
});

核心策略详解

重试策略(Retry)

csharp 复制代码
// 简单重试3次
var retryPolicy = Policy
    .Handle<Exception>()
    .Retry(3);

// 带退避算法的重试(指数退避)
var retryWithBackoffPolicy = Policy
    .Handle<Exception>()
    .WaitAndRetryAsync(
        retryCount: 5,
        sleepDurationProvider: retryAttempt => 
            TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),  // 2, 4, 8, 16, 32秒
        onRetry: (exception, sleepDuration, retryCount, context) =>
        {
            Console.WriteLine($"重试 {retryCount}: 等待 {sleepDuration.TotalSeconds}s 后重试,异常: {exception.Message}");
        });

断路器策略(Circuit Breaker)

csharp 复制代码
// 简单断路器:连续3次失败后断开,断开30秒
var circuitBreakerPolicy = Policy
    .Handle<Exception>()
    .CircuitBreaker(
        exceptionsAllowedBeforeBreaking: 3,
        durationOfBreak: TimeSpan.FromSeconds(30),
        onBreak: (ex, breakDelay) => Console.WriteLine($"断路器断开: {ex.Message}"),
        onReset: () => Console.WriteLine("断路器重置"),
        onHalfOpen: () => Console.WriteLine("断路器半开状态")
    );

// 高级断路器:基于失败比例(统计窗口内)
var advancedCircuitBreaker = Policy
    .Handle<Exception>()
    .AdvancedCircuitBreaker(
        failureThreshold: 0.5,  // 50%的请求失败
        samplingDuration: TimeSpan.FromSeconds(30),  // 统计30秒内的请求
        minimumThroughput: 10,  // 至少10次请求才开始统计
        durationOfBreak: TimeSpan.FromSeconds(30)
    );

超时策略(Timeout)

csharp 复制代码
// 超时策略:操作超过5秒则终止
var timeoutPolicy = Policy
    .Timeout(TimeSpan.FromSeconds(5), TimeoutStrategy.Pessimistic);

// 使用悲观超时策略并处理超时异常
try
{
    await timeoutPolicy.ExecuteAsync(async () =>
    {
        // 可能耗时较长的操作
        await Task.Delay(10000);
        return "操作完成";
    });
}
catch (TimeoutRejectedException ex)
{
    Console.WriteLine($"操作超时: {ex.Message}");
}

隔离舱策略(Bulkhead Isolation)

csharp 复制代码
// 隔离舱:限制并发执行的操作数量
var bulkheadPolicy = Policy
    .Bulkhead(
        maxParallelization: 10,  // 最多10个并发操作
        maxQueuingActions: 5,    // 最多5个操作排队等待
        onBulkheadRejected: context => 
            Console.WriteLine("隔离舱已满,请求被拒绝")
    );

// 使用隔离舱策略
await bulkheadPolicy.ExecuteAsync(async () =>
{
    // 受隔离舱保护的操作
    await DoWorkAsync();
});

降级策略(Fallback)

csharp 复制代码
// 降级策略:操作失败时提供备选值
var fallbackPolicy = Policy
    .Handle<Exception>()
    .FallbackAsync(
        fallbackValue: "默认值",
        fallbackAction: async ct => // 降级操作
        {
            return await CacheService.GetLatestDataAsync();
        },
        onFallbackAsync: ex =>
        {
            Console.WriteLine($"执行失败,使用降级策略: {ex.Exception.Message}");
            return Task.CompletedTask;
        }
    );

// 使用降级策略
var result = await fallbackPolicy.ExecuteAsync(async () =>
{
    // 可能失败的操作
    return await GetDataFromServiceAsync();
});

缓存策略 (Cache)

csharp 复制代码
// 内存缓存策略
var cachePolicy = Policy.Cache(
    new MemoryCacheProvider(myMemoryCache),
    TimeSpan.FromMinutes(5)); // TTL=5分钟

// 使用示例
var result = cachePolicy.Execute(context => 
    ExpensiveOperation(), new Context("cache_key"));

策略包装(Policy Wrap)

csharp 复制代码
var policyWrap = Policy.WrapAsync(fallbackPolicy, retryPolicy, circuitBreakerPolicy, timeoutPolicy);
var result = await policyWrap.ExecuteAsync(ct => _context.Users.FirstOrDefaultAsync(ct), cancellationToken);

健康检查策略

csharp 复制代码
// 定义健康检查策略
var healthPolicy = Policy
    .Handle<Exception>()
    .AdvancedCircuitBreaker(...)
    .WrapAsync(Policy.TimeoutAsync(TimeSpan.FromSeconds(3)));

// 健康检查端点
app.MapGet("/health", async () => 
{
    var healthResult = await healthPolicy.ExecuteAsync(async () => 
    {
        await db.CheckHealthAsync();
        await queue.CheckHealthAsync();
    });
    
    return healthResult.IsHealthy ? 
        Results.Ok() : Results.StatusCode(503);
});

组合策略

Polly 允许将多个策略组合成一个策略链(Policy Wrap):

csharp 复制代码
// 组合重试、断路器和超时策略
var policyWrap = Policy.WrapAsync(
    circuitBreakerPolicy,    // 最外层:断路器
    retryWithBackoffPolicy,  // 中间层:带退避的重试
    timeoutPolicy            // 最内层:超时
);

// 使用组合策略执行操作
await policyWrap.ExecuteAsync(async () =>
{
    // 受多重策略保护的操作
    var response = await httpClient.GetAsync("https://api.example.com/data");
    response.EnsureSuccessStatusCode();
    return await response.Content.ReadAsStringAsync();
});

策略上下文(Context)

通过 Context 可以在策略执行过程中传递自定义数据:

csharp 复制代码
// 创建带上下文的策略
var policy = Policy
    .Handle<Exception>()
    .RetryAsync(3, (ex, retryCount, context) =>
    {
        // 从上下文中获取数据
        if (context.ContainsKey("RequestId"))
        {
            Console.WriteLine($"请求 {context["RequestId"]} 重试 {retryCount}: {ex.Message}");
        }
    });

// 执行策略时传递上下文
await policy.ExecuteAsync(
    async context =>
    {
        // 执行受保护的操作
        return await DoWorkAsync();
    },
    new Context { { "RequestId", Guid.NewGuid().ToString() } }
);

Polly 与 HttpClient 集成

使用 Polly 与 HttpClientFactory

csharp 复制代码
// 注册 HttpClient 并应用 Polly 策略
services.AddHttpClient("ResilientClient")
    .AddPolicyHandler(GetRetryPolicy())
    .AddPolicyHandler(GetCircuitBreakerPolicy());

// 定义重试策略
private IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()  // 处理常见HTTP错误(408, 500-599)
        .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
        .WaitAndRetryAsync(
            retryCount: 3,
            sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
            onRetry: (response, sleepDuration, retryCount, context) =>
            {
                Console.WriteLine($"HTTP请求失败,重试 {retryCount}: {response.Result?.StatusCode}");
            }
        );
}

// 定义断路器策略
private IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .CircuitBreakerAsync(
            exceptionsAllowedBeforeBreaking: 3,
            durationOfBreak: TimeSpan.FromSeconds(30)
        );
}

在控制器中使用

csharp 复制代码
public class MyController : ControllerBase
{
    private readonly HttpClient _httpClient;
    
    public MyController(IHttpClientFactory clientFactory)
    {
        _httpClient = clientFactory.CreateClient("ResilientClient");
    }
    
    [HttpGet]
    public async Task<IActionResult> GetData()
    {
        var response = await _httpClient.GetAsync("https://api.example.com/data");
        response.EnsureSuccessStatusCode();
        return Ok(await response.Content.ReadAsStringAsync());
    }
}

高级特性

策略注册表(Policy Registry)

csharp 复制代码
// 注册策略到注册表
var policyRegistry = new PolicyRegistry
{
    { "RetryPolicy", Policy.Handle<Exception>().Retry(3) },
    { "CircuitBreakerPolicy", Policy.Handle<Exception>().CircuitBreaker(3, TimeSpan.FromSeconds(30)) }
};

// 从注册表获取策略
var retryPolicy = policyRegistry.Get<IAsyncPolicy>("RetryPolicy");

自定义策略

csharp 复制代码
// 创建自定义策略:记录所有操作
var loggingPolicy = Policy
    .Handle<Exception>()
    .ExecuteAndCaptureAsync(async (ctx, cancellationToken) =>
    {
        Console.WriteLine($"开始执行操作: {ctx.OperationKey}");
        try
        {
            var result = await ctx.CancellationToken.HasValue
                ? ctx.Action(ctx, ctx.CancellationToken.Value)
                : ctx.Action(ctx, CancellationToken.None);
                
            Console.WriteLine($"操作成功: {ctx.OperationKey}");
            return result;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"操作失败: {ctx.OperationKey}, 异常: {ex.Message}");
            throw;
        }
    });

分布式断路器

csharp 复制代码
// 使用Redis实现分布式断路器
public class RedisCircuitBreaker : AsyncCircuitBreakerPolicy
{
    private readonly IDistributedCache _cache;
    
    public RedisCircuitBreaker(IDistributedCache cache)
    {
        _cache = cache;
    }
    
    protected override async Task<bool> IsolateCircuitAsync(...)
    {
        var state = new CircuitState(State.Closed);
        await _cache.SetAsync("CircuitState", state);
    }
    
    protected override async Task<CircuitState> GetCircuitStateAsync()
    {
        return await _cache.GetAsync<CircuitState>("CircuitState");
    }
}

自适应弹性策略

csharp 复制代码
// 基于响应时间自动调整超时
var adaptiveTimeout = Policy.TimeoutAsync(() => 
{
    var avgResponseTime = Telemetry.GetAvgResponseTime("ServiceA");
    return avgResponseTime * 1.5; // 平均响应时间的1.5倍
});

gRPC 弹性处理

csharp 复制代码
// gRPC 弹性策略
var grpcPolicy = Policy
    .Handle<RpcException>(ex => 
        ex.StatusCode == StatusCode.Unavailable ||
        ex.StatusCode == StatusCode.DeadlineExceeded)
    .WaitAndRetryAsync(3, attempt => 
        TimeSpan.FromSeconds(attempt * 2));

// 使用
var client = new Greeter.GreeterClient(channel);
var response = await grpcPolicy.ExecuteAsync(() => 
    client.SayHelloAsync(new HelloRequest()).ResponseAsync);

性能优化策略

策略重用与缓存

csharp 复制代码
// 策略工厂模式(避免重复创建)
public static class PolicyFactory
{
    private static ConcurrentDictionary<string, IAsyncPolicy> _policies = new();
    
    public static IAsyncPolicy GetOrCreatePolicy(string key, Func<IAsyncPolicy> createPolicy)
    {
        return _policies.GetOrAdd(key, _ => createPolicy());
    }
}

// 使用示例
var policy = PolicyFactory.GetOrCreatePolicy("CircuitBreakerPolicy", () => 
    Policy.Handle<Exception>()
          .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));

上下文传递与监控

csharp 复制代码
// 策略上下文配置
var context = new Context
{
    {"OperationName", "GetUserData"},
    {"RequestId", Guid.NewGuid()}
};

// 策略执行
await policy.ExecuteAsync(async ctx => 
{
    var requestId = ctx["RequestId"];
    Telemetry.StartTracking(requestId);
    // 业务逻辑
}, context);

// 事件钩子
var policyWithEvents = Policy
    .Handle<Exception>()
    .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30),
    onBreak: (ex, breakDelay, ctx) => 
    {
        Telemetry.TrackCircuitBreak(ctx["OperationName"].ToString());
    },
    onReset: ctx => 
    {
        Telemetry.TrackCircuitReset(ctx["OperationName"].ToString());
    });

错误处理模式

csharp 复制代码
try
{
    return await _resilientPolicy.ExecuteAsync(BusinessOperation);
}
catch (BrokenCircuitException)
{
    // 处理开路状态
    return ServiceUnavailable();
}
catch (TimeoutRejectedException)
{
    // 处理超时
    return GatewayTimeout();
}
catch (BulkheadRejectedException)
{
    // 处理限流
    return TooManyRequests();
}
catch (Exception ex)
{
    // 其他异常
    return InternalError(ex);
}

Polly架构黄金法则:

csharp 复制代码
Policy.WrapAsync(
    FallbackPolicy.ForDegradedService(),    // 降级保护
    TimeoutPolicy.ForSlowServices(),        // 超时控制
    CircuitBreakerPolicy.ForProtection(),   // 短路保护
    BulkheadPolicy.ForConcurrency(),        // 资源保护
    RetryPolicy.ForTransientFaults()        // 重试机制
);

混沌工程与性能优化

V8 版本引入 混沌工程策略,模拟生产环境故障以验证系统韧性

csharp 复制代码
builder.AddResilienceHandler("chaos-pipeline", pipelineBuilder => {
    pipelineBuilder
        .AddRetry(new RetryStrategyOptions { /*...*/ })
        .AddChaosFault(0.02, () => new InvalidOperationException("模拟故障")) // 2% 请求注入异常
        .AddChaosLatency(0.01, TimeSpan.FromSeconds(5)); // 1% 请求增加 5s 延迟
});
  • 混沌注入类型:故障(Fault)、错误结果(Outcome)、延迟(Latency)、自定义行为(Behavior)

  • 动态控制:通过 InjectionRateEnabledGenerator 动态调整注入频率

集成应用实践

与 HttpClient 深度集成

通过 Microsoft.Extensions.Http.Polly 包实现声明式策略配置

csharp 复制代码
services.AddHttpClient("ResilientClient")
    .AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(10))
    .AddTransientHttpErrorPolicy(policyBuilder => 
        policyBuilder.WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(attempt)));

ASP.NET Core 全局策略注册

csharp 复制代码
services.AddSingleton<AsyncRetryPolicy>(Policy
    .Handle<SqlException>()
    .WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(2))
);
services.AddMvc().AddPollyPolicies(); // 配合 AOP 框架如 AspectCore 使用

高级应用场景

电商订单系统熔断设计

  • 触发条件:支付服务连续 5 次超时或返回 5xx 错误

  • 熔断动作:开启 30 秒熔断,快速返回"服务暂不可用"提示

  • 半开状态:熔断结束后首个请求成功则闭合,否则再次熔断

金融交易系统隔板隔离

  • 资源配置:核心交易线程池(10 线程),报表查询线程池(20 线程)

  • 效果:交易高负载时,报表查询不受影响,避免级联故障

性能优化

  • 策略复用:单例模式管理 Policy 对象,避免重复创建

  • 异步支持:优先使用 RetryAsync、ExecuteAsync 异步方法

  • 上下文传递:利用 Context 对象传递参数,支持动态策略调整

混沌工程

混沌工程是通过在生产或类生产环境中故意引入故障(如异常、延迟、假结果),测试系统在异常条件下的行为,以增强系统对不可预测故障的弹性(Resilience)。它源自 NetflixSimian Army 项目(如 Chaos Monkey),旨在验证系统是否能承受"混乱"场景。其核心目标是:

  • 暴露系统脆弱点:模拟生产环境中的非常规故障(如网络抖动、服务不可用)。

  • 检验弹性策略有效性:验证重试、熔断等策略是否能正确处理注入的故障。

  • 提升系统韧性:通过可控的混沌实验,增强系统抵御真实故障的能力。

Polly V8 的优势:

  • 无缝集成:与 Polly 的弹性策略(如重试、断路器)结合,测试弹性机制。

  • 灵活配置:支持同步/异步操作,统一 API(ChaosStrategyOptions)

  • 生产环境测试:在生产环境中以可控方式注入故障,无需修改核心代码。

  • telemetry 支持:通过 Polly 的遥测(Telemetry)系统记录混沌事件。

常见混沌实验类型:

  • 服务响应超时

  • 随机抛出异常

  • 模拟网络抖动

  • 限制资源(CPU、内存)

使用场景

  • HTTP 请求:测试 API 调用在网络延迟或失败时的弹性。

  • 数据库操作:模拟 EF CoreSqlExceptionDbUpdateConcurrencyException

  • 后台任务:结合 BackgroundServiceConcurrentQueue<T> 测试批量任务的容错。

  • CQRS 架构:在命令/查询处理程序中注入故障,验证业务逻辑的鲁棒性。

  • 微服务:模拟服务间通信失败,测试分布式系统的可靠性。

核心混沌策略

Polly V8 提供四类策略,支持细粒度故障模拟:

策略类型 作用 典型场景
故障注入 (Fault) 抛出自定义异常 模拟服务崩溃、依赖失效
结果注入 (Outcome) 返回错误结果或异常 测试客户端对错误响应的处理逻辑
延迟注入 (Latency) 增加处理延迟 模拟网络拥塞或资源竞争
行为注入 (Behavior) 执行自定义操作(如重启服务) 验证系统自愈能力

精细化控制参数

参数 类型 默认值 作用
InjectionRate double (0-1) 0.001 混沌触发概率(如0.05表示5%的请求受影响)
InjectionRateGenerator Func<double> null 动态生成注入率(支持按请求上下文调整)
EnabledGenerator Func<bool> null 动态启用策略(例如仅在测试环境激活)

核心混沌策略

Fault(故障注入)

注入异常(如 TimeoutException),模拟系统错误。

csharp 复制代码
var faultOptions = new ChaosFaultStrategyOptions
{
    FaultGenerator = new FaultGenerator()
        .AddException<TimeoutException>()
        .AddException(() => new InvalidOperationException("Chaos fault")),
    InjectionRate = 0.1, // 10% 概率
    OnFaultInjected = args =>
    {
        Console.WriteLine($"Fault injected: {args.Fault.Message}");
        return default;
    }
};
new ResiliencePipelineBuilder().AddChaosFault(faultOptions);
Outcome(结果注入)

注入假结果或异常,测试应用对不同响应的处理。

csharp 复制代码
var outcomeOptions = new ChaosOutcomeStrategyOptions
{
    OutcomeGenerator = args => new ValueTask<Outcome<object>>(new Outcome<object>(42)), // 注入假结果 42
    InjectionRate = 0.1,
    OnOutcomeInjected = args =>
    {
        Console.WriteLine($"Outcome injected: {args.Outcome.Result}");
        return default;
    }
};
new ResiliencePipelineBuilder().AddChaosOutcome(outcomeOptions);
Latency(延迟注入)

在操作前引入延迟,模拟网络或服务瓶颈。

csharp 复制代码
var latencyOptions = new ChaosLatencyStrategyOptions
{
    Latency = TimeSpan.FromSeconds(5),
    InjectionRate = 0.05,
    OnLatencyInjected = args =>
    {
        Console.WriteLine($"Latency injected: {args.Latency.TotalSeconds} seconds");
        return default;
    }
};
new ResiliencePipelineBuilder().AddChaosLatency(latencyOptions);
Behavior(行为注入)

在操作前执行自定义行为(如重启 Redis)。

csharp 复制代码
var behaviorOptions = new ChaosBehaviorStrategyOptions
{
    BehaviorGenerator = args => RestartRedisAsync(args.Context.CancellationToken),
    InjectionRate = 0.05,
    OnBehaviorInjected = args =>
    {
        Console.WriteLine("Behavior injected");
        return default;
    }
};
new ResiliencePipelineBuilder().AddChaosBehavior(behaviorOptions);

async Task RestartRedisAsync(CancellationToken ct)
{
    // 模拟重启 Redis
    await Task.Delay(100, ct);
}
组合混沌策略
csharp 复制代码
var pipeline = new ResiliencePipelineBuilder()
    .AddRetry(new RetryStrategyOptions { MaxRetryAttempts = 3, Delay = TimeSpan.FromSeconds(2) })
    .AddChaosFault(new ChaosFaultStrategyOptions { FaultGenerator = new FaultGenerator().AddException<TimeoutException>(), InjectionRate = 0.1 })
    .AddChaosLatency(new ChaosLatencyStrategyOptions { Latency = TimeSpan.FromSeconds(5), InjectionRate = 0.05 })
    .Build();
csharp 复制代码
// 组合异常注入和延迟注入策略
var chaosPolicy = Policy.WrapAsync(
    faultInjectionPolicy,
    latencyInjectionPolicy
);

// 执行受混沌策略影响的操作
await chaosPolicy.ExecuteAsync(async () =>
{
    // 可能会被注入故障或延迟的操作
    return await FetchDataFromServiceAsync();
});

混沌实验设计与执行

实验参数配置
csharp 复制代码
// 使用配置对象创建混沌策略
var chaosSettings = new ChaosSettings
{
    Enabled = true,
    FaultType = FaultType.Exception,  // 或 Latency
    InjectionRate = 0.1,  // 10%的故障率
    ExceptionType = typeof(TransientFailureException),
    Latency = TimeSpan.FromSeconds(1)
};

var chaosPolicy = Policy
    .HandleResult<string>(_ => true)
    .InjectChaosAsync(chaosSettings);
渐进式混沌实验
csharp 复制代码
// 随着时间增加故障率
var injectionRateProvider = new InjectionRateSlidingWindow(
    initialInjectionRate: 0.05,  // 初始5%故障率
    maxInjectionRate: 0.2,       // 最大20%故障率
    windowDuration: TimeSpan.FromMinutes(5)  // 每5分钟增加一次
);

var chaosPolicy = Policy
    .HandleResult<int>(_ => true)
    .InjectExceptionAsync(
        injectionRateProvider: () => injectionRateProvider.CurrentInjectionRate,
        faultFactory: () => new TransientFailureException("系统压力测试")
    );

与弹性策略的管道集成

混沌策略需置于标准弹性策略之后,形成"先防御后攻击"的管道

csharp 复制代码
services.AddHttpClient("ResilientClient")
    .AddResilienceHandler("chaos-pipeline", builder => 
    {
        // 标准弹性策略
        builder
            .AddRetry(new RetryStrategyOptions { MaxRetryAttempts = 3 })
            .AddCircuitBreaker(new CircuitBreakerStrategyOptions { FailureRatio = 0.3 })
            .AddTimeout(TimeSpan.FromSeconds(10));
        
        // 混沌策略(置于弹性策略后)
        builder
            .AddChaosLatency(0.05, TimeSpan.FromSeconds(15))
            .AddChaosFault(0.03, () => new HttpRequestException("Chaos fault"));
    });

// 创建弹性策略(重试 + 断路器)
var resiliencePolicy = Policy.WrapAsync(
    Policy.Handle<Exception>().RetryAsync(3),
    Policy.Handle<Exception>().CircuitBreakerAsync(3, TimeSpan.FromSeconds(30))
);

// 创建混沌策略
var chaosPolicy = Policy
    .HandleResult<string>(_ => true)
    .InjectExceptionAsync(0.1, () => new HttpRequestException("模拟网络故障"));

// 组合策略:先应用混沌,再应用弹性
var combinedPolicy = Policy.WrapAsync(resiliencePolicy, chaosPolicy);

// 执行受保护的操作
await combinedPolicy.ExecuteAsync(async () =>
{
    return await httpClient.GetStringAsync("https://api.example.com/data");
});

管道执行顺序: 请求 → 重试 → 熔断 → 超时 → 注入延迟/故障 → 目标服务

在测试环境中使用

单元测试示例
csharp 复制代码
[Fact]
public async Task GetData_ShouldSucceed_WhenTransientFailureOccurs()
{
    // 安排:创建带有故障注入的策略
    var chaosPolicy = Policy
        .HandleResult<string>(_ => true)
        .InjectExceptionAsync(
            injectionRate: 0.5,  // 50%的故障率
            faultFactory: () => new HttpRequestException("模拟网络故障")
        );
    
    // 安排:创建带有重试策略的服务
    var service = new MyService(
        httpClient: _httpClient,
        resiliencePolicy: Policy.WrapAsync(
            Policy.Handle<HttpRequestException>().RetryAsync(3),
            chaosPolicy
        )
    );
    
    // 执行
    var result = await service.GetDataAsync();
    
    // 断言:尽管有故障注入,服务仍应成功返回数据
    Assert.NotNull(result);
}
集成测试配置
csharp 复制代码
// 在测试环境中启用混沌工程
public void ConfigureServices(IServiceCollection services)
{
    if (Environment.IsDevelopment() || Environment.IsStaging())
    {
        // 添加混沌策略
        services.AddTransient<MyService>(sp =>
        {
            var httpClient = sp.GetRequiredService<HttpClient>();
            
            // 创建混沌策略
            var chaosPolicy = Policy
                .HandleResult<string>(_ => true)
                .InjectChaosAsync(new ChaosSettings
                {
                    Enabled = true,
                    FaultType = FaultType.Exception,
                    InjectionRate = 0.1,
                    ExceptionType = typeof(HttpRequestException)
                });
                
            // 创建弹性策略
            var resiliencePolicy = Policy.WrapAsync(
                Policy.Handle<Exception>().RetryAsync(3),
                chaosPolicy
            );
            
            return new MyService(httpClient, resiliencePolicy);
        });
    }
    else
    {
        // 在生产环境中不启用混沌
        services.AddTransient<MyService>();
    }
}

ASP.NET Core Web 项目中 Polly 实战

基础环境配置

  • 安装 NuGet
shell 复制代码
dotnet add package Microsoft.Extensions.Http.Polly
dotnet add package Polly
dotnet add package Polly.Contrib.WaitAndRetry # 提供高级重试策略

入口文件配置

csharp 复制代码
// Program.cs
builder.Services.AddHttpClient("ResilientApi")
    .AddTransientHttpErrorPolicy(policyBuilder => 
        policyBuilder.WaitAndRetryAsync(
            Backoff.DecorrelatedJitterBackoffV2(
                medianFirstRetryDelay: TimeSpan.FromSeconds(1), 
                retryCount: 3
            )
        )
    );

核心应用场景实现

外部 API 调用保护(商品服务)
csharp 复制代码
// ProductService.cs
public class ProductService
{
    private readonly IHttpClientFactory _httpClientFactory;
    
    public ProductService(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }
    
    public async Task<Product> GetProductAsync(int id)
    {
        var client = _httpClientFactory.CreateClient("ResilientApi");
        
        // 带策略的执行
        return await client.GetFromJsonAsync<Product>($"/api/products/{id}");
    }
}

// 策略说明:
// 1. 自动处理5xx错误、408超时和网络故障
// 2. 使用指数退避算法重试3次:0.5s, 1.5s, 4.5s
// 3. 添加Jitter避免同步重试风暴
数据库操作保护(EF Core 集成)
csharp 复制代码
// OrderRepository.cs
public class OrderRepository
{
    private readonly AsyncRetryPolicy _dbPolicy;
    private readonly AppDbContext _context;
    
    public OrderRepository(AppDbContext context)
    {
        _context = context;
        
        // 配置SQL重试策略
        _dbPolicy = Policy
            .Handle<SqlException>(ex => ex.IsTransient) // 捕获瞬态错误
            .Or<TimeoutException>()
            .WaitAndRetryAsync(
                retryCount: 3,
                sleepDurationProvider: attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)),
                onRetry: (ex, delay) => 
                    Log.Warning($"数据库操作失败,{delay.TotalSeconds}秒后重试: {ex.Message}")
            );
    }
    
    public async Task SaveOrderAsync(Order order)
    {
        await _dbPolicy.ExecuteAsync(async () => 
        {
            _context.Orders.Add(order);
            await _context.SaveChangesAsync();
        });
    }
}
添加降级策略

如果外部服务不可用,可以添加降级策略提供默认响应:

csharp 复制代码
// 在Program.cs中添加降级策略
var fallbackPolicy = Policy<HttpResponseMessage>
    .Handle<Exception>()
    .FallbackAsync(
        fallbackAction: async (ctx, token) =>
        {
            Console.WriteLine("执行降级策略,返回缓存数据");
            var response = new HttpResponseMessage(HttpStatusCode.OK);
            response.Content = new StringContent("[{\"Temperature\":25,\"Condition\":\"Sunny\"}]");
            return response;
        },
        onFallbackAsync: ex =>
        {
            Console.WriteLine($"请求失败,执行降级: {ex.Exception.Message}");
            return Task.CompletedTask;
        }
    );

// 更新HttpClient注册,添加降级策略
builder.Services.AddHttpClient("ResilientClient")
    .AddPolicyHandler(fallbackPolicy)
    .AddPolicyHandler(retryPolicy)
    .AddPolicyHandler(circuitBreakerPolicy);
全局熔断保护(防止服务雪崩)
csharp 复制代码
// Program.cs
var circuitBreakerPolicy = Policy
    .Handle<HttpRequestException>()
    .Or<TimeoutException>()
    .CircuitBreakerAsync(
        exceptionsAllowedBeforeBreaking: 5,
        durationOfBreak: TimeSpan.FromSeconds(30),
        onBreak: (ex, breakDelay) => 
            Log.Error($"熔断器开启!{breakDelay.TotalSeconds}秒内拒绝请求"),
        onReset: () => Log.Information("熔断器关闭")
    );

builder.Services.AddHttpClient("PaymentService")
    .AddPolicyHandler(circuitBreakerPolicy)
    .AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(10));

高级策略组合

完整策略链(重试+熔断+超时)
csharp 复制代码
// Program.cs
var retryPolicy = Policy<HttpResponseMessage>
    .HandleResult(r => (int)r.StatusCode >= 500)
    .Or<HttpRequestException>()
    .WaitAndRetryAsync(3, attempt => 
        TimeSpan.FromSeconds(0.5 * Math.Pow(2, attempt)));

var circuitBreaker = Policy<HttpResponseMessage>
    .HandleResult(r => (int)r.StatusCode >= 500)
    .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));

var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(10);

// 策略组合顺序:超时 → 熔断 → 重试
var policyWrap = Policy.WrapAsync(timeoutPolicy, circuitBreaker, retryPolicy);

builder.Services.AddHttpClient("InventoryService")
    .AddPolicyHandler(policyWrap);
环境差异化配置
csharp 复制代码
// 开发环境更激进的策略
if (env.IsDevelopment())
{
    services.AddHttpClient("DevService")
        .AddTransientHttpErrorPolicy(builder => 
            builder.WaitAndRetryAsync(5, attempt => TimeSpan.FromSeconds(0.5)));
}
// 生产环境保守策略
else 
{
    services.AddHttpClient("ProdService")
        .AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(3))
        .AddTransientHttpErrorPolicy(builder => 
            builder.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
}
监控与日志集成
csharp 复制代码
// Serilog 集成
Log.Logger = new LoggerConfiguration()
    .Enrich.WithProperty("Application", "ECommerce")
    .WriteTo.Console()
    .WriteTo.Seq("http://localhost:5341")
    .CreateLogger();

// Polly 事件记录
policy = policy.WithPolicyLogging(logger, "InventoryPolicy");
策略事件追踪
csharp 复制代码
var policy = Policy
    .Handle<Exception>()
    .RetryAsync(3, onRetry: (ex, retryCount) => 
    {
        logger.LogWarning("重试 #{RetryCount} 触发: {Exception}", retryCount, ex);
    })
    .WithTelemetry(loggerFactory); // 启用Polly内置遥测
模拟故障测试
csharp 复制代码
// 测试环境注入故障
if (env.IsEnvironment("Test"))
{
    services.AddHttpClient("MockService")
        .AddPolicyHandler(Policy<HttpResponseMessage>
            .HandleResult(r => true)
            .FallbackAsync(new HttpResponseMessage(HttpStatusCode.ServiceUnavailable)))
        .AddPolicyHandler(new FaultInjectionPolicy(0.1)); // 10%失败率
}

配置建议

配置驱动策略参数
json 复制代码
// appsettings.json
"PollyConfig": {
  "HttpRetryCount": 3,
  "HttpCircuitBreakThreshold": 5,
  "HttpCircuitBreakDuration": "00:00:30",
  "HttpTimeout": "00:00:15",
  "DbRetryCount": 3,
  "DbRetryDelayMilliseconds": 200
}
csharp 复制代码
// 基于配置的策略
static IAsyncPolicy<HttpResponseMessage> GetConfiguredPolicy(IConfiguration config)
{
    var retryCount = config.GetValue<int>("PollyConfig:HttpRetryCount");
    var circuitBreak = config.GetValue<int>("PollyConfig:HttpCircuitBreakThreshold");
    var breakDuration = config.GetValue<TimeSpan>("PollyConfig:HttpCircuitBreakDuration");
    var timeout = config.GetValue<TimeSpan>("PollyConfig:HttpTimeout");
    
    return Policy.WrapAsync(
        Policy.TimeoutAsync<HttpResponseMessage>(timeout),
        HttpPolicyExtensions
            .HandleTransientHttpError()
            .CircuitBreakerAsync(circuitBreak, breakDuration),
        HttpPolicyExtensions
            .HandleTransientHttpError()
            .WaitAndRetryAsync(retryCount, retryAttempt => 
                TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)))
    );
}

Web 项目特定场景处理

控制器动作级保护
csharp 复制代码
[HttpGet("products/{id}")]
public async Task<IActionResult> GetProduct(int id)
{
    // 创建请求级策略
    var policy = Policy
        .Handle<Exception>()
        .RetryAsync(2, (ex, retryCount) => 
            Log.Warning($"第{retryCount}次重试: {ex.Message}"));
    
    var product = await policy.ExecuteAsync(
        () => _productService.GetProductAsync(id));
    
    return product != null ? Ok(product) : NotFound();
}
自定义响应转换
csharp 复制代码
// 处理外部API的429响应
var rateLimitPolicy = Policy<HttpResponseMessage>
    .HandleResult(r => r.StatusCode == HttpStatusCode.TooManyRequests)
    .RetryAsync(1, onRetry: (result, retryCount) =>
    {
        var retryAfter = result.Result.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(5);
        Thread.Sleep(retryAfter.Value);
    });

builder.Services.AddHttpClient("ExternalApi")
    .AddPolicyHandler(rateLimitPolicy);
用户请求上下文传递
csharp 复制代码
var policy = Policy
    .Handle<Exception>()
    .RetryAsync(1, onRetry: (ex, retryCount, context) => 
    {
        var userId = context["user-id"]?.ToString();
        Log.Warning($"用户{userId}请求重试: {ex.Message}");
    });

// 执行时传递上下文
var response = await policy.ExecuteAsync(ctx => 
    _service.CallApi(userId), 
    new Context { ["user-id"] = userId });
下单流程示例
csharp 复制代码
// OrderController.cs
[HttpPost("orders")]
public async Task<IActionResult> CreateOrder([FromBody] OrderRequest request)
{
    // 组合策略: 超时5s + 3次重试
    var policy = Policy
        .Handle<TimeoutException>()
        .Or<HttpRequestException>()
        .WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(attempt))
        .WrapAsync(Policy.TimeoutAsync(5));
    
    try
    {
        var result = await policy.ExecuteAsync(async () => 
        {
            // 1. 检查库存
            var stockValid = await _inventoryService.CheckStockAsync(request.Items);
            
            // 2. 创建支付
            var payment = await _paymentService.CreatePaymentAsync(request.Payment);
            
            // 3. 保存订单
            var order = await _orderService.CreateOrderAsync(request, payment.Id);
            
            return new { OrderId = order.Id };
        });
        
        return Ok(result);
    }
    catch (TimeoutException)
    {
        return StatusCode(503, "服务响应超时,请稍后重试");
    }
    catch (HttpRequestException)
    {
        return StatusCode(502, "上游服务不可用");
    }
}
相关推荐
追逐时光者5 小时前
推荐 6 款基于 .NET 开源的串口调试工具,调试效率提升利器!
后端·.net
界面开发小八哥5 小时前
界面控件DevExpress WPF v25.1新版亮点:模板库更新升级
ui·.net·wpf·界面控件·devexpress·ui开发
咩咩觉主8 小时前
Unity编辑器拓展 IMGUI与部分Utility知识总结(代码+思维导图)
unity·c#·编辑器·游戏引擎
无规则ai9 小时前
C#入门实战:数字计算与条件判断
c#·visual studio
bianguanyue21 小时前
WPF——自定义ListBox
c#·wpf
追逐时光者1 天前
一款超级经典复古的 Windows 9x 主题风格 Avalonia UI 控件库,满满的回忆杀!
后端·.net
智者知已应修善业1 天前
【C# 找最大值、最小值和平均值及大于个数和值】2022-9-23
经验分享·笔记·算法·c#
猫猫的小茶馆1 天前
【STM32】FreeRTOS 任务的创建(二)
stm32·单片机·嵌入式硬件·mcu·c#·智能硬件
王柏龙1 天前
C# 11.0 新特性 u8 后缀
c#
会写代码的建筑师1 天前
在 swagger 中支持 asp.net core 可选路由参数
后端·.net