MiniAPI（二十四）:Pollyを使用したリトライ


システムの安定性とセキュリティを保証するため、サードパーティのサービスを呼び出す際には、リトライとサーキットブレーカーの追加が可能です。リトライは、一度の呼び出しに失敗した後に再試行することで、下流サービスの一時的な断絶が全てのプロセスを終了させることを避けます。サーキットブレーカーは、過多の無効アクセスを防ぎ、システムの未知の例外が発生するのを防ぐためのものです。
Pollyは、独立したリトライメカニズムのサードパーティライブラリです。ここでは、httpclientを使用して下流APIへのリクエスト時のリトライとサーキットブレーカーについてのみ説明します。NuGetパッケージMicrosoft.Extensions.Http.Pollyをインポートする必要があります。

まず、シンプルなリトライから見てみましょう。

```csharp
using Polly;
var builder = WebApplication.CreateBuilder(args);
builder.Services
    .AddHttpClient("RetryClient", httpclient =>
    {
        httpclient.BaseAddress = new Uri("http://localhost:5258");
    })
    .AddTransientHttpErrorPolicy(policyBuilder => policyBuilder.RetryAsync(3));

var app = builder.Build();

// httpclientを呼び出す
app.MapGet("/test", async (IHttpClientFactory httpClientFactory) =>
{
    try
    {
        var httpClient = httpClientFactory.CreateClient("RetryClient");
        var content = await httpClient.GetStringAsync("other-api");
        Console.WriteLine(content);
        return "ok";
    }
    catch (Exception exc)
    {
        if (!Count.Time.HasValue)
        {
            Count.Time = DateTime.Now;
        }
        return $"{exc.Message}    【回数：{Count.I++}】  【{Count.Time.Value.ToString("yyyy-MM-dd HH:mm:ss.fffffff")}】";
    }
});

// ステータスコード500を返す呼び出されたインターフェイス
app.MapGet("/other-api", (ILogger<Program> logger) =>
{
    logger.LogInformation($"失敗:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fffffff")}");
    return Results.StatusCode(500);
});
app.Run();

static class Count
{
    public static int I = 1;
    public static DateTime? Time;
}
```

![alt 画像](https://mmbiz.qpic.cn/mmbiz_png/4CAA63nvHRBiaje9D8TQbdasr9xe3JZ8bnDSkeSTRDPJQk0aynl4YB06Vl1Dgib0FozY886GKgaxqHiaREv3bBK5A/640?wx_fmt=png)

`.AddTransientHttpErrorPolicy(policyBuilder => policyBuilder.RetryAsync(3));`によってリクエストに3回のリトライが行われ、初回の1回を合わせて計4回のリクエストが行われます。赤い枠内に注目すると、リクエストが非常に短時間に集中していることがわかります。下流サービスに障害がある場合、これほど短期間で自動的に復旧することはないかもしれません。より良い方法は、リトライ回数に応じてリクエスト間の時間を延長する（ランダムにしたり、独自の遅延アルゴリズムを構築する）ことです。

```csharp
.AddTransientHttpErrorPolicy(policyBuilder =>
        policyBuilder.WaitAndRetryAsync(3, retryNumber =>
        {
            switch (retryNumber)
            {
                case 1:
                    return TimeSpan.FromMilliseconds(500);
                case 2:
                    return TimeSpan.FromMilliseconds(1000);
                case 3:
                    return TimeSpan.FromMilliseconds(1500);
                default:
                    return TimeSpan.FromMilliseconds(100);
            }
        }));
```

この結果、以下のように指定された時間に基づいてリトライが行われます。

![alt 画像](https://mmbiz.qpic.cn/mmbiz_png/4CAA63nvHRCicib2XWIGICuS9WNZ0To10ghC76ia0hd2g1pNOFN8N49icMtCWCXlKZEh2zP0wH1a6oFUicw7Ek125iaA/640?wx_fmt=png)

その他のリトライ戦略も存在します。

```csharp
// 無限にリトライ
.AddTransientHttpErrorPolicy(policyBuilder => policyBuilder.RetryForeverAsync());
// 2秒ごとにリトライ
.AddTransientHttpErrorPolicy(policyBuilder => policyBuilder.WaitAndRetryForeverAsync(retryNumber =>
{
    Console.WriteLine(retryNumber);
    return TimeSpan.FromSeconds(2);
}));
// 5秒間に4回のリクエストがあり、50%が失敗した場合、10秒間のサーキットブレーカーを行う
.AddTransientHttpErrorPolicy(policyBuilder => policyBuilder.AdvancedCircuitBreakerAsync(0.5d, TimeSpan.FromSeconds(5), 4, TimeSpan.FromSeconds(10)));
```

サーキットブレーカーはサービスを守る手段です。この例での具体的な使用方法は以下の通りです。

```csharp
builder.Services
    .AddHttpClient("RetryClient", httpclient =>
    {
        httpclient.BaseAddress = new Uri("http://localhost:5258");
    })
    .AddTransientHttpErrorPolicy(policyBuilder =>
        policyBuilder.WaitAndRetryAsync(3, retryNumber =>
        {
            switch (retryNumber)
            {
                case 1:
                    return TimeSpan.FromMilliseconds(500);
                case 2:
                    return TimeSpan.FromMilliseconds(1000);
                case 3:
                    return TimeSpan.FromMilliseconds(1500);
                default:
                    return TimeSpan.FromMilliseconds(100);
            }
        }))
    // サーキットブレーカー
    .AddTransientHttpErrorPolicy(policyBuilder => policyBuilder.CircuitBreakerAsync(6, TimeSpan.FromSeconds(30)));
```

`CircuitBreaker`は6回の失敗したリクエストがあった場合、30秒間の一時停止を制御します。具体的なプロンプトは以下のようになります。

![alt 画像](https://mmbiz.qpic.cn/mmbiz_png/4CAA63nvHRCicib2XWIGICuS9WNZ0To10gWtiaa4gptFy77SErXL707Y0C6SiciaPeU3UcWYA4xZ2Q4GtQkV91XBFww/640?wx_fmt=png)

(Translated by GPT)

元のリンク：https://mp.weixin.qq.com/s?__biz=MzA3NDM1MzIyMQ==&mid=2247484982&idx=1&sn=8967531b39ad66bbf1afe7216c25cf1a&chksm=9f00591ca877d00a4abc37827a16b25c3e0ae6d62454553947444ffb991acf9f2d7a0701925d&token=347102560&lang=zh_CN#rd