resilience

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Resilience

弹性模式

Core Principles

核心原则

  1. Polly v8 resilience pipelines, not v7 policies — Polly v8 replaced
    Policy
    with
    ResiliencePipeline
    . Never use
    PolicyBuilder
    ,
    Policy.Handle<>()
    , or
    ISyncPolicy
    . The new API is composable, type-safe, and integrates natively with
    IHttpClientFactory
    .
  2. Configure via
    AddResilienceHandler
    , not manual wrapping
    — For HTTP calls, use
    Microsoft.Extensions.Http.Resilience
    which adds pipelines directly to
    HttpClient
    via DI. No manual
    ExecuteAsync
    wrapping.
  3. Compose strategies, don't nest them — A single
    ResiliencePipeline
    can chain retry + circuit breaker + timeout. Strategies execute outer-to-inner (first added = outermost). No need for nested try/catch or manual orchestration.
  4. Always set timeouts — Every external call needs a timeout. Use Polly's
    AddTimeout()
    as the innermost strategy so it applies per-attempt, and optionally an outer timeout for total elapsed time.
  5. Instrument everything — Polly v8 emits
    Metering
    events and supports
    TelemetryOptions
    for OpenTelemetry. Use them to monitor retry rates, circuit breaker state, and timeout frequency.
  1. 使用Polly v8弹性管道,而非v7策略 — Polly v8用
    ResiliencePipeline
    替代了
    Policy
    。切勿使用
    PolicyBuilder
    Policy.Handle<>()
    ISyncPolicy
    。新API具备可组合性、类型安全性,并原生集成
    IHttpClientFactory
  2. 通过
    AddResilienceHandler
    配置,而非手动包装
    — 对于HTTP调用,使用
    Microsoft.Extensions.Http.Resilience
    ,它通过依赖注入(DI)直接将管道添加到
    HttpClient
    。无需手动调用
    ExecuteAsync
    进行包装。
  3. 组合策略,而非嵌套 — 单个
    ResiliencePipeline
    可串联重试+断路器+超时策略。策略执行顺序为从外到内(先添加的为最外层)。无需嵌套try/catch或手动编排。
  4. 始终设置超时 — 每个外部调用都需要超时设置。将Polly的
    AddTimeout()
    作为最内层策略,使其应用于每次尝试,还可选择添加外层超时以限制总耗时。
  5. 监控所有内容 — Polly v8会发出
    Metering
    事件,并支持用于OpenTelemetry的
    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: 30s
Why:
AddStandardResilienceHandler()
from
Microsoft.Extensions.Http.Resilience
applies production-ready defaults. Override only when you need different thresholds.
csharp
// 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.Resilience
提供的
AddStandardResilienceHandler()
应用了生产就绪的默认配置。仅当需要调整阈值时才进行覆盖。

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:
AddResiliencePipeline
registers a named pipeline in DI. Inject with
[FromKeyedServices]
for clean, testable code.
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);
    }
}
原因
AddResiliencePipeline
在DI中注册命名管道。通过
[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
AddRateLimiter()
— no external packages needed. Algorithms:
AddFixedWindowLimiter
,
AddSlidingWindowLimiter
,
AddTokenBucketLimiter
,
AddConcurrencyLimiter
.
csharp
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()
提供内置速率限制中间件——无需外部包。支持的算法:
AddFixedWindowLimiter
AddSlidingWindowLimiter
AddTokenBucketLimiter
AddConcurrencyLimiter
csharp
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 = Open
csharp
// 正确 — 通过OpenTelemetry捕获Polly v8指标
builder.Services.AddOpenTelemetry()
    .WithMetrics(metrics => metrics.AddMeter("Polly"));

// 仪表盘告警:polly.circuit_breaker.state = Open

Decision Guide

决策指南

ScenarioStrategyConfiguration
HTTP calls to external APIs
AddStandardResilienceHandler()
Use defaults, override only specific thresholds
HTTP with custom thresholds
AddResilienceHandler("name", ...)
Named handler with per-service tuning
Database / EF Core calls
AddResiliencePipeline("db", ...)
Retry on deadlock/timeout, no circuit breaker
Message queue publishing
AddResiliencePipeline("mq", ...)
Retry with exponential backoff, timeout
Latency-sensitive reads
AddHedging(...)
Parallel request after delay threshold
Graceful degradation
AddFallback(...)
Return cached/default value on total failure
Per-attempt time limit
AddTimeout(...)
innermost
2-10s depending on operation
Total operation time limit
AddTimeout(...)
outermost
Sum of all retries + buffer
Non-idempotent writesRetry with idempotency keyOr no retry — fail fast
Read-heavy microserviceStandard handler + hedgingLow latency with redundancy
API rate limiting
AddRateLimiter()
+
RequireRateLimiting()
Fixed, sliding, or token bucket per endpoint
场景策略配置
调用外部API的HTTP请求
AddStandardResilienceHandler()
使用默认配置,仅在需要时覆盖特定阈值
需自定义阈值的HTTP请求
AddResilienceHandler("name", ...)
命名处理程序,针对不同服务调优
数据库/EF Core调用
AddResiliencePipeline("db", ...)
针对死锁/超时重试,不使用断路器
消息队列发布
AddResiliencePipeline("mq", ...)
指数退避重试+超时
对延迟敏感的读取操作
AddHedging(...)
达到延迟阈值后发送并行请求
优雅降级
AddFallback(...)
完全失败时返回缓存/默认值
单次尝试时间限制
AddTimeout(...)
作为最内层
根据操作设置2-10秒
操作总时间限制
AddTimeout(...)
作为最外层
所有重试耗时总和+缓冲时间
非幂等写入操作带幂等键的重试或不重试——快速失败
读密集型微服务标准处理程序+Hedging低延迟+冗余
API速率限制
AddRateLimiter()
+
RequireRateLimiting()
针对端点使用固定窗口、滑动窗口或令牌桶算法