1. 核心概念:什么是 Polly?为什么需要它?
在分布式环境中(如调用其他微服务、数据库、API),失败是不可避免的。失败可能是瞬态的(短暂的、自恢复的),如:
- 网络波动
- 服务暂时过载(HTTP 500、503)
- 数据库连接瞬间爆满
Polly 的作用 就是帮助你的应用程序优雅地处理这些故障,通过预定义的策略(Policies) 来提高应用的弹性和可用性。
Polly 的两种主要用法:
- 弹性策略(Resiliency Policies) :定义当故障发生时该如何反应(如重试、回退、断路)。
- 功能策略(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. 最佳实践与常见陷阱
- 策略应放在调用端:谁发起调用,谁负责处理失败。保护你的出站请求。
- 谨慎使用重试 :
- 一定要用带退避的重试 (
WaitAndRetry
),而不是立即重试,避免加剧下游服务压力。 - 并非所有错误都要重试 :
404 Not Found
或400 Bad Request
这种客户端错误重试再多次也没用。使用HandleTransientHttpError()
可以很好地避免这个问题。
- 一定要用带退避的重试 (
- 断路器是服务级别的 :通常一个下游服务对应一个断路器实例,共享状态。确保在依赖注入中以单例形式注册断路器策略(
AddPolicyHandler
默认就是单例)。 - 策略是单例的 :通过
AddPolicyHandler
添加的策略在容器中是单例的,这意味着它们的状态(如断路器的开闭状态)是共享的。这是期望的行为。 - 测试你的策略 :使用单元测试模拟异常和超时,确保你的策略按预期工作。Polly 提供了丰富的回调(
onBreak
,onRetry
)来辅助测试和监控。 - 结合日志和监控:在策略的回调中记录日志和发送指标(如重试次数、熔断事件),这对于诊断问题至关重要。
总结:策略选择指南
故障类型 | 推荐策略 | 说明 |
---|---|---|
瞬态网络错误 | Retry + Circuit Breaker | 重试解决瞬时问题,断路器防止雪崩 |
慢响应/挂起 | Timeout | 保证调用方不被阻塞 |
下游服务完全宕机 | Circuit Breaker + Fallback | 快速失败并提供降级体验 |
防止资源耗尽 | Bulkhead | 限制并发,隔离故障 |
所有以上情况 | PolicyWrap | 组合多种策略,构建终极弹性方案 |
通过本指南,你应该已经掌握了 Polly 的核心概念和如何在 ASP.NET Core 中实际使用它。立即行动:从为你最关键的 HTTP 调用添加一个简单的重试策略开始,逐步构建起复杂的弹性模式。