mcpField
R
resilience

resilience

github.com/codewithmukesh開発7913.0k 回インストール12 週間前に更新
リポジトリ

resilience について

.NET 開発者向けに、Polly v8 と .NET 組み込みのレート制限を使ったレジリエンスを解説します。HTTP と非 HTTP の処理を対象に、タイムアウト、再試行、サーキットブレーカー、テレメトリー、安全な再試行を扱います。

主な機能

  1. 01Polly v8 のレジリエンスパイプライン
  2. 02HTTP クライアントの標準・カスタムハンドラー
  3. 03再試行、サーキットブレーカー、タイムアウト、フォールバック
  4. 04低遅延読み取り向けのヘッジング
  5. 05テレメトリーと組み込みレート制限

活用シーン

  1. 01外部 HTTP API にレジリエンスを設定する
  2. 02データベースやメッセージキュー用の名前付きパイプラインを登録する
  3. 03低遅延読み取りに並列リクエストを使う
  4. 04サーキットブレーカーを監視し API リクエストを制限する

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/codewithmukesh/dotnet-claude-kit --skill resilience

インストール後、Claude Code で/resilienceを入力して読み込まれたか確認します。

詳細

レジリエンス

基本原則

  1. v7 ポリシーではなく、Polly v8 のレジリエンスパイプラインを使用する — Polly v8 では Policy が ResiliencePipeline に置き換えられました。PolicyBuilder、Policy.Handle<>()、ISyncPolicy は決して使用しないでください。新しい API は合成可能で、型安全であり、IHttpClientFactory とネイティブに統合されます。
  2. 手動でラップせず、AddResilienceHandler で設定する — HTTP 呼び出しには、DI を通じて HttpClient にパイプラインを直接追加する Microsoft.Extensions.Http.Resilience を使用してください。手動で ExecuteAsync をラップする必要はありません。
  3. ストラテジーはネストせず、組み合わせる — 1 つの ResiliencePipeline で、リトライ + サーキットブレーカー + タイムアウトを連鎖させることができます。ストラテジーは外側から内側へ実行されます(最初に追加したものが最外側です)。ネストした try/catch や手動のオーケストレーションは不要です。
  4. 必ずタイムアウトを設定する — 外部への呼び出しにはすべてタイムアウトが必要です。試行ごとに適用されるよう、Polly の AddTimeout() を最も内側のストラテジーとして使用し、必要に応じて合計経過時間を制限する外側のタイムアウトも追加してください。
  5. すべてを計測する — Polly v8 は Metering イベントを出力し、OpenTelemetry 用の TelemetryOptions をサポートしています。これらを使用して、リトライ率、サーキットブレーカーの状態、タイムアウトの頻度を監視してください。

パターン

HTTP クライアントのレジリエンス(推奨されるデフォルト)
// 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

理由:Microsoft.Extensions.Http.Resilience の AddStandardResilienceHandler() は、本番環境に対応したデフォルト設定を適用します。しきい値を変更する必要がある場合にのみ、上書きしてください。

HTTP のカスタムレジリエンス設定
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));
});

理由:名前付きレジリエンスハンドラーを使うと、サービスごとに調整できます。順序が重要です。合計タイムアウト > リトライ > サーキットブレーカー > 試行ごとのタイムアウト。

HTTP 以外のレジリエンスパイプライン
// 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);
    }
}

理由:AddResiliencePipeline は、DI に名前付きパイプラインを登録します。[FromKeyedServices] を使って注入すると、シンプルでテストしやすいコードになります。

型付きレジリエンスパイプライン
// 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));
});

理由:型付きパイプラインを使うと、すべてのリトライを使い切った際にデフォルト値を返すフォールバックストラテジーを追加できます。これは適切な縮退に不可欠です。

ヘッジング(並列リクエスト)
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));
    });

理由:ヘッジングでは、最初のリクエストが遅延時間内に応答しない場合、並列リクエストを送信します。重複した処理を許容できる、レイテンシーに敏感な読み取りに使用してください。

テレメトリの統合
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"));
レート制限(.NET 組み込み)

.NET には AddRateLimiter() による組み込みのレート制限ミドルウェアが用意されています。外部パッケージは不要です。アルゴリズム:AddFixedWindowLimiter、AddSlidingWindowLimiter、AddTokenBucketLimiter、AddConcurrencyLimiter。

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");

アンチパターン

悪い例:Polly v7 API の使用
// 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"));
良い例:Polly v8 のレジリエンスパイプライン
// GOOD — v8 pipeline via DI
builder.Services.AddHttpClient<IDataService, DataServiceClient>()
    .AddStandardResilienceHandler();

悪い例:すべての呼び出しを手動でラップする
// 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;
    }
}
良い例:HttpClient の DI を通じてパイプラインですべてを処理する
// 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);
}

悪い例:冪等でない操作のリトライ
// BAD — retrying a POST that creates a resource risks duplicates
builder.AddRetry(new RetryStrategyOptions
{
    MaxRetryAttempts = 5 // This will create 5 orders on transient failures!
});
良い例:冪等な操作のみをリトライするか、冪等性キーを使用する
// 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());

悪い例:監視を行わないサーキットブレーカー
// BAD — circuit breaker with no visibility into state changes
builder.AddCircuitBreaker(new CircuitBreakerStrategyOptions());
// How do you know when it trips? You don't.
良い例:テレメトリを備えたサーキットブレーカー
// GOOD — Polly v8 metrics captured via OpenTelemetry
builder.Services.AddOpenTelemetry()
    .WithMetrics(metrics => metrics.AddMeter("Polly"));

// Dashboard alerts on: polly.circuit_breaker.state = Open

判断ガイド

シナリオ戦略設定
外部 API への HTTP 呼び出しAddStandardResilienceHandler()デフォルトを使用し、特定のしきい値のみを上書きします
独自のしきい値を設定する HTTPAddResilienceHandler("name", ...)サービスごとに調整できる名前付きハンドラー
データベース/EF Core 呼び出しAddResiliencePipeline("db", ...)デッドロック/タイムアウト時に再試行し、サーキットブレーカーは使用しません
メッセージキューへの公開AddResiliencePipeline("mq", ...)指数バックオフとタイムアウトを伴う再試行
レイテンシー重視の読み取りAddHedging(...)遅延のしきい値を超えた後に並列リクエストを送信します
グレースフルデグラデーションAddFallback(...)全体的な失敗時にキャッシュ値/デフォルト値を返します
試行ごとの時間制限最も内側に AddTimeout(...)操作に応じて 2~10 秒
操作全体の時間制限最も外側に AddTimeout(...)すべての再試行時間の合計+バッファー
冪等でない書き込み冪等性キーを使用して再試行または再試行せず、即座に失敗させます
読み取り中心のマイクロサービス標準ハンドラー+ヘッジング冗長性による低レイテンシー
API のレート制限AddRateLimiter() + RequireRateLimiting()エンドポイントごとに固定、スライディング、またはトークンバケットを使用します

コメント