resilience
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseResilience
弹性模式
Core Principles
核心原则
- Polly v8 resilience pipelines, not v7 policies — Polly v8 replaced with
Policy. Never useResiliencePipeline,PolicyBuilder, orPolicy.Handle<>(). The new API is composable, type-safe, and integrates natively withISyncPolicy.IHttpClientFactory - Configure via , not manual wrapping — For HTTP calls, use
AddResilienceHandlerwhich adds pipelines directly toMicrosoft.Extensions.Http.Resiliencevia DI. No manualHttpClientwrapping.ExecuteAsync - Compose strategies, don't nest them — A single can chain retry + circuit breaker + timeout. Strategies execute outer-to-inner (first added = outermost). No need for nested try/catch or manual orchestration.
ResiliencePipeline - Always set timeouts — Every external call needs a timeout. Use Polly's as the innermost strategy so it applies per-attempt, and optionally an outer timeout for total elapsed time.
AddTimeout() - Instrument everything — Polly v8 emits events and supports
Meteringfor OpenTelemetry. Use them to monitor retry rates, circuit breaker state, and timeout frequency.TelemetryOptions
- 使用Polly v8弹性管道,而非v7策略 — Polly v8用替代了
ResiliencePipeline。切勿使用Policy、PolicyBuilder或Policy.Handle<>()。新API具备可组合性、类型安全性,并原生集成ISyncPolicy。IHttpClientFactory - 通过配置,而非手动包装 — 对于HTTP调用,使用
AddResilienceHandler,它通过依赖注入(DI)直接将管道添加到Microsoft.Extensions.Http.Resilience。无需手动调用HttpClient进行包装。ExecuteAsync - 组合策略,而非嵌套 — 单个可串联重试+断路器+超时策略。策略执行顺序为从外到内(先添加的为最外层)。无需嵌套try/catch或手动编排。
ResiliencePipeline - 始终设置超时 — 每个外部调用都需要超时设置。将Polly的作为最内层策略,使其应用于每次尝试,还可选择添加外层超时以限制总耗时。
AddTimeout() - 监控所有内容 — Polly v8会发出事件,并支持用于OpenTelemetry的
Metering。利用它们监控重试率、断路器状态和超时频率。TelemetryOptions
Patterns
模式示例
HTTP Client Resilience (Recommended Default)
HttpClient弹性配置(推荐默认方案)
csharp
// Program.cs — Standard resilience handler covers 90% of use cases
builder.Services.AddHttpClient<IPaymentGateway, PaymentGatewayClient>(client =>
{
client.BaseAddress = new Uri("https://api.payments.example.com");
})
.AddStandardResilienceHandler(); // Retry + circuit breaker + timeout out of the box
// That's it. The standard handler configures:
// - Retry: 3 attempts, exponential backoff, jitter
// - Circuit breaker: 10% failure ratio over 30s sampling, 30s break
// - Attempt timeout: 10s per attempt
// - Total request timeout: 30sWhy: from applies production-ready defaults. Override only when you need different thresholds.
AddStandardResilienceHandler()Microsoft.Extensions.Http.Resiliencecsharp
// Program.cs — 标准弹性处理程序可覆盖90%的使用场景
builder.Services.AddHttpClient<IPaymentGateway, PaymentGatewayClient>(client =>
{
client.BaseAddress = new Uri("https://api.payments.example.com");
})
.AddStandardResilienceHandler(); // 开箱即用的重试+断路器+超时配置
// 以上代码即可完成配置,标准处理程序默认包含:
// - 重试:3次尝试,指数退避,带抖动
// - 断路器:30秒采样周期内失败率达10%时触发,断路时长30秒
// - 单次尝试超时:10秒
// - 请求总超时:30秒原因:提供的应用了生产就绪的默认配置。仅当需要调整阈值时才进行覆盖。
Microsoft.Extensions.Http.ResilienceAddStandardResilienceHandler()Custom HTTP Resilience Configuration
自定义HTTP弹性配置
csharp
builder.Services.AddHttpClient<ICatalogService, CatalogServiceClient>(client =>
{
client.BaseAddress = new Uri("https://api.catalog.example.com");
})
.AddResilienceHandler("catalog", builder =>
{
// Total timeout — outermost, caps total elapsed time
builder.AddTimeout(TimeSpan.FromSeconds(15));
// Retry — exponential backoff with jitter
builder.AddRetry(new HttpRetryStrategyOptions
{
MaxRetryAttempts = 3,
BackoffType = DelayBackoffType.Exponential,
UseJitter = true,
Delay = TimeSpan.FromMilliseconds(500),
ShouldHandle = static args => ValueTask.FromResult(
args.Outcome.Result?.StatusCode is HttpStatusCode.RequestTimeout
or HttpStatusCode.TooManyRequests
or HttpStatusCode.ServiceUnavailable
|| args.Outcome.Exception is HttpRequestException)
});
// Circuit breaker — prevent cascading failures
builder.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
{
FailureRatio = 0.5,
SamplingDuration = TimeSpan.FromSeconds(10),
MinimumThroughput = 10,
BreakDuration = TimeSpan.FromSeconds(30)
});
// Per-attempt timeout — innermost
builder.AddTimeout(TimeSpan.FromSeconds(5));
});Why: Named resilience handlers let you tune per-service. The order matters: total timeout > retry > circuit breaker > attempt timeout.
csharp
builder.Services.AddHttpClient<ICatalogService, CatalogServiceClient>(client =>
{
client.BaseAddress = new Uri("https://api.catalog.example.com");
})
.AddResilienceHandler("catalog", builder =>
{
// 总超时 — 最外层,限制总耗时
builder.AddTimeout(TimeSpan.FromSeconds(15));
// 重试 — 带抖动的指数退避
builder.AddRetry(new HttpRetryStrategyOptions
{
MaxRetryAttempts = 3,
BackoffType = DelayBackoffType.Exponential,
UseJitter = true,
Delay = TimeSpan.FromMilliseconds(500),
ShouldHandle = static args => ValueTask.FromResult(
args.Outcome.Result?.StatusCode is HttpStatusCode.RequestTimeout
or HttpStatusCode.TooManyRequests
or HttpStatusCode.ServiceUnavailable
|| args.Outcome.Exception is HttpRequestException)
});
// 断路器 — 防止级联故障
builder.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
{
FailureRatio = 0.5,
SamplingDuration = TimeSpan.FromSeconds(10),
MinimumThroughput = 10,
BreakDuration = TimeSpan.FromSeconds(30)
});
// 单次尝试超时 — 最内层
builder.AddTimeout(TimeSpan.FromSeconds(5));
});原因:命名弹性处理程序允许针对不同服务进行调优。顺序至关重要:总超时 > 重试 > 断路器 > 单次尝试超时。
Non-HTTP Resilience Pipeline
非HTTP弹性管道
csharp
// For database calls, message queues, or any non-HTTP operation
builder.Services.AddResiliencePipeline("database", builder =>
{
builder
.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
BackoffType = DelayBackoffType.Exponential,
Delay = TimeSpan.FromMilliseconds(200),
ShouldHandle = new PredicateBuilder()
.Handle<TimeoutException>()
.Handle<InvalidOperationException>(ex =>
ex.Message.Contains("deadlock", StringComparison.OrdinalIgnoreCase))
})
.AddTimeout(TimeSpan.FromSeconds(10));
});
// Inject and use
public sealed class OrderRepository(
AppDbContext db,
[FromKeyedServices("database")] ResiliencePipeline pipeline)
{
public async Task<Order?> GetByIdAsync(Guid id, CancellationToken ct)
{
return await pipeline.ExecuteAsync(
async token => await db.Orders.FindAsync([id], token),
ct);
}
}Why: registers a named pipeline in DI. Inject with for clean, testable code.
AddResiliencePipeline[FromKeyedServices]csharp
// 适用于数据库调用、消息队列或任何非HTTP操作
builder.Services.AddResiliencePipeline("database", builder =>
{
builder
.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
BackoffType = DelayBackoffType.Exponential,
Delay = TimeSpan.FromMilliseconds(200),
ShouldHandle = new PredicateBuilder()
.Handle<TimeoutException>()
.Handle<InvalidOperationException>(ex =>
ex.Message.Contains("deadlock", StringComparison.OrdinalIgnoreCase))
})
.AddTimeout(TimeSpan.FromSeconds(10));
});
// 注入并使用
public sealed class OrderRepository(
AppDbContext db,
[FromKeyedServices("database")] ResiliencePipeline pipeline)
{
public async Task<Order?> GetByIdAsync(Guid id, CancellationToken ct)
{
return await pipeline.ExecuteAsync(
async token => await db.Orders.FindAsync([id], token),
ct);
}
}原因:在DI中注册命名管道。通过注入,实现代码整洁且易于测试。
AddResiliencePipeline[FromKeyedServices]Typed Resilience Pipeline
类型化弹性管道
csharp
// When the operation returns a specific type, use ResiliencePipeline<T>
builder.Services.AddResiliencePipeline<string, HttpResponseMessage>("external-api", builder =>
{
builder
.AddFallback(new FallbackStrategyOptions<HttpResponseMessage>
{
FallbackAction = static args =>
{
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent("{\"status\":\"degraded\",\"data\":[]}")
};
return Outcome.FromResultAsValueTask(response);
},
ShouldHandle = static args => ValueTask.FromResult(
args.Outcome.Exception is not null
|| args.Outcome.Result?.IsSuccessStatusCode == false)
})
.AddRetry(new RetryStrategyOptions<HttpResponseMessage>
{
MaxRetryAttempts = 2,
Delay = TimeSpan.FromMilliseconds(500)
})
.AddTimeout(TimeSpan.FromSeconds(5));
});Why: Typed pipelines let you add fallback strategies that return a default value when all retries are exhausted — critical for graceful degradation.
csharp
// 当操作返回特定类型时,使用ResiliencePipeline<T>
builder.Services.AddResiliencePipeline<string, HttpResponseMessage>("external-api", builder =>
{
builder
.AddFallback(new FallbackStrategyOptions<HttpResponseMessage>
{
FallbackAction = static args =>
{
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent("{\"status\":\"degraded\",\"data\":[]}")
};
return Outcome.FromResultAsValueTask(response);
},
ShouldHandle = static args => ValueTask.FromResult(
args.Outcome.Exception is not null
|| args.Outcome.Result?.IsSuccessStatusCode == false)
})
.AddRetry(new RetryStrategyOptions<HttpResponseMessage>
{
MaxRetryAttempts = 2,
Delay = TimeSpan.FromMilliseconds(500)
})
.AddTimeout(TimeSpan.FromSeconds(5));
});原因:类型化管道允许添加降级回退策略,当所有重试失败时返回默认值——这对优雅降级至关重要。
Hedging (Parallel Requests)
并行请求(Hedging)
csharp
builder.Services.AddHttpClient<ISearchService, SearchServiceClient>()
.AddResilienceHandler("search-hedging", builder =>
{
builder.AddHedging(new HttpHedgingStrategyOptions
{
MaxHedgedAttempts = 2,
Delay = TimeSpan.FromMilliseconds(500) // Send parallel request after 500ms
});
builder.AddTimeout(TimeSpan.FromSeconds(3));
});Why: Hedging sends a parallel request if the first hasn't responded within the delay. Use for latency-sensitive reads where you can tolerate duplicate work.
csharp
builder.Services.AddHttpClient<ISearchService, SearchServiceClient>()
.AddResilienceHandler("search-hedging", builder =>
{
builder.AddHedging(new HttpHedgingStrategyOptions
{
MaxHedgedAttempts = 2,
Delay = TimeSpan.FromMilliseconds(500) // 500ms后发送并行请求
});
builder.AddTimeout(TimeSpan.FromSeconds(3));
});原因:如果第一个请求在延迟时间内未响应,Hedging会发送并行请求。适用于对延迟敏感的读取场景,且可容忍重复操作。
Telemetry Integration
遥测集成
csharp
builder.Services.AddResiliencePipeline("monitored", (builder, context) =>
{
// Polly v8 emits metrics via System.Diagnostics.Metrics automatically.
// ConfigureTelemetry wires structured logging for strategy events.
builder
.ConfigureTelemetry(new TelemetryOptions
{
LoggerFactory = context.ServiceProvider.GetRequiredService<ILoggerFactory>()
})
.AddRetry(new RetryStrategyOptions { MaxRetryAttempts = 3 })
.AddCircuitBreaker(new CircuitBreakerStrategyOptions())
.AddTimeout(TimeSpan.FromSeconds(10));
});
// In Program.cs — wire up OpenTelemetry to capture Polly metrics
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics => metrics.AddMeter("Polly"));csharp
builder.Services.AddResiliencePipeline("monitored", (builder, context) =>
{
// Polly v8自动通过System.Diagnostics.Metrics发出指标。
// ConfigureTelemetry为策略事件连接结构化日志。
builder
.ConfigureTelemetry(new TelemetryOptions
{
LoggerFactory = context.ServiceProvider.GetRequiredService<ILoggerFactory>()
})
.AddRetry(new RetryStrategyOptions { MaxRetryAttempts = 3 })
.AddCircuitBreaker(new CircuitBreakerStrategyOptions())
.AddTimeout(TimeSpan.FromSeconds(10));
});
// 在Program.cs中 — 连接OpenTelemetry以捕获Polly指标
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics => metrics.AddMeter("Polly"));Rate Limiting (.NET Built-in)
速率限制(.NET内置)
.NET provides built-in rate limiting middleware via — no external packages needed. Algorithms: , , , .
AddRateLimiter()AddFixedWindowLimiterAddSlidingWindowLimiterAddTokenBucketLimiterAddConcurrencyLimitercsharp
builder.Services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter("fixed", opt =>
{
opt.PermitLimit = 100;
opt.Window = TimeSpan.FromSeconds(60);
opt.QueueLimit = 0;
});
// Always return ProblemDetails with Retry-After on 429
options.OnRejected = async (context, ct) =>
{
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter))
context.HttpContext.Response.Headers.RetryAfter =
((int)retryAfter.TotalSeconds).ToString();
await context.HttpContext.Response.WriteAsJsonAsync(
new ProblemDetails { Title = "Too many requests", Status = 429 }, ct);
};
});
app.UseRateLimiter();
app.MapGet("/api/orders", ListOrders).RequireRateLimiting("fixed");.NET通过提供内置速率限制中间件——无需外部包。支持的算法:、、、。
AddRateLimiter()AddFixedWindowLimiterAddSlidingWindowLimiterAddTokenBucketLimiterAddConcurrencyLimitercsharp
builder.Services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter("fixed", opt =>
{
opt.PermitLimit = 100;
opt.Window = TimeSpan.FromSeconds(60);
opt.QueueLimit = 0;
});
// 触发429时始终返回带Retry-After的ProblemDetails
options.OnRejected = async (context, ct) =>
{
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter))
context.HttpContext.Response.Headers.RetryAfter =
((int)retryAfter.TotalSeconds).ToString();
await context.HttpContext.Response.WriteAsJsonAsync(
new ProblemDetails { Title = "请求过多", Status = 429 }, ct);
};
});
app.UseRateLimiter();
app.MapGet("/api/orders", ListOrders).RequireRateLimiting("fixed");Anti-patterns
反模式
BAD: Using Polly v7 API
错误:使用Polly v7 API
csharp
// BAD — v7 policy syntax, do not use
var retryPolicy = Policy
.Handle<HttpRequestException>()
.WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)));
var response = await retryPolicy.ExecuteAsync(() => httpClient.GetAsync("/api/data"));csharp
// 错误 — v7策略语法,请勿使用
var retryPolicy = Policy
.Handle<HttpRequestException>()
.WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)));
var response = await retryPolicy.ExecuteAsync(() => httpClient.GetAsync("/api/data"));GOOD: Polly v8 Resilience Pipeline
正确:Polly v8弹性管道
csharp
// GOOD — v8 pipeline via DI
builder.Services.AddHttpClient<IDataService, DataServiceClient>()
.AddStandardResilienceHandler();csharp
// 正确 — 通过DI配置v8管道
builder.Services.AddHttpClient<IDataService, DataServiceClient>()
.AddStandardResilienceHandler();BAD: Wrapping Every Call Manually
错误:手动包装每个调用
csharp
// BAD — manual resilience per call site
public async Task<Order> GetOrderAsync(Guid id)
{
try
{
return await _pipeline.ExecuteAsync(async ct =>
await _httpClient.GetFromJsonAsync<Order>($"/orders/{id}", ct));
}
catch (TimeoutRejectedException)
{
return Order.Empty;
}
catch (BrokenCircuitException)
{
return Order.Empty;
}
}csharp
// 错误 — 在每个调用点手动处理弹性
public async Task<Order> GetOrderAsync(Guid id)
{
try
{
return await _pipeline.ExecuteAsync(async ct =>
await _httpClient.GetFromJsonAsync<Order>($"/orders/{id}", ct));
}
catch (TimeoutRejectedException)
{
return Order.Empty;
}
catch (BrokenCircuitException)
{
return Order.Empty;
}
}GOOD: Pipeline Handles Everything via HttpClient DI
正确:通过HttpClient DI让管道处理所有逻辑
csharp
// GOOD — resilience is configured at the HttpClient level
public async Task<Order?> GetOrderAsync(Guid id, CancellationToken ct)
{
var response = await _httpClient.GetAsync($"/orders/{id}", ct);
if (!response.IsSuccessStatusCode) return null;
return await response.Content.ReadFromJsonAsync<Order>(ct);
}csharp
// 正确 — 在HttpClient层面配置弹性
public async Task<Order?> GetOrderAsync(Guid id, CancellationToken ct)
{
var response = await _httpClient.GetAsync($"/orders/{id}", ct);
if (!response.IsSuccessStatusCode) return null;
return await response.Content.ReadFromJsonAsync<Order>(ct);
}BAD: Retry on Non-Idempotent Operations
错误:对非幂等操作重试
csharp
// BAD — retrying a POST that creates a resource risks duplicates
builder.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 5 // This will create 5 orders on transient failures!
});csharp
// 错误 — 对创建资源的POST请求重试会导致重复创建
builder.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 5 // 瞬时故障时会创建5个订单!
});GOOD: Retry Only Idempotent Operations or Use Idempotency Keys
正确:仅对幂等操作重试或使用幂等键
csharp
// GOOD — use idempotency key header for non-idempotent operations
builder.AddRetry(new HttpRetryStrategyOptions
{
MaxRetryAttempts = 3,
ShouldHandle = static args => ValueTask.FromResult(
args.Outcome.Result?.StatusCode is HttpStatusCode.RequestTimeout
or HttpStatusCode.ServiceUnavailable)
});
// Pair with idempotency key in the request
httpClient.DefaultRequestHeaders.Add("Idempotency-Key", Guid.NewGuid().ToString());csharp
// 正确 — 对非幂等操作使用幂等键请求头
builder.AddRetry(new HttpRetryStrategyOptions
{
MaxRetryAttempts = 3,
ShouldHandle = static args => ValueTask.FromResult(
args.Outcome.Result?.StatusCode is HttpStatusCode.RequestTimeout
or HttpStatusCode.ServiceUnavailable)
});
// 配合请求中的幂等键
httpClient.DefaultRequestHeaders.Add("Idempotency-Key", Guid.NewGuid().ToString());BAD: Circuit Breaker Without Monitoring
错误:无监控的断路器
csharp
// BAD — circuit breaker with no visibility into state changes
builder.AddCircuitBreaker(new CircuitBreakerStrategyOptions());
// How do you know when it trips? You don't.csharp
// 错误 — 断路器状态变化无可见性
builder.AddCircuitBreaker(new CircuitBreakerStrategyOptions());
// 你无法知道它何时触发断路GOOD: Circuit Breaker with Telemetry
正确:带遥测的断路器
csharp
// GOOD — Polly v8 metrics captured via OpenTelemetry
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics => metrics.AddMeter("Polly"));
// Dashboard alerts on: polly.circuit_breaker.state = Opencsharp
// 正确 — 通过OpenTelemetry捕获Polly v8指标
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics => metrics.AddMeter("Polly"));
// 仪表盘告警:polly.circuit_breaker.state = OpenDecision Guide
决策指南
| Scenario | Strategy | Configuration |
|---|---|---|
| HTTP calls to external APIs | | Use defaults, override only specific thresholds |
| HTTP with custom thresholds | | Named handler with per-service tuning |
| Database / EF Core calls | | Retry on deadlock/timeout, no circuit breaker |
| Message queue publishing | | Retry with exponential backoff, timeout |
| Latency-sensitive reads | | Parallel request after delay threshold |
| Graceful degradation | | Return cached/default value on total failure |
| Per-attempt time limit | | 2-10s depending on operation |
| Total operation time limit | | Sum of all retries + buffer |
| Non-idempotent writes | Retry with idempotency key | Or no retry — fail fast |
| Read-heavy microservice | Standard handler + hedging | Low latency with redundancy |
| API rate limiting | | Fixed, sliding, or token bucket per endpoint |
| 场景 | 策略 | 配置 |
|---|---|---|
| 调用外部API的HTTP请求 | | 使用默认配置,仅在需要时覆盖特定阈值 |
| 需自定义阈值的HTTP请求 | | 命名处理程序,针对不同服务调优 |
| 数据库/EF Core调用 | | 针对死锁/超时重试,不使用断路器 |
| 消息队列发布 | | 指数退避重试+超时 |
| 对延迟敏感的读取操作 | | 达到延迟阈值后发送并行请求 |
| 优雅降级 | | 完全失败时返回缓存/默认值 |
| 单次尝试时间限制 | | 根据操作设置2-10秒 |
| 操作总时间限制 | | 所有重试耗时总和+缓冲时间 |
| 非幂等写入操作 | 带幂等键的重试 | 或不重试——快速失败 |
| 读密集型微服务 | 标准处理程序+Hedging | 低延迟+冗余 |
| API速率限制 | | 针对端点使用固定窗口、滑动窗口或令牌桶算法 |