Share via


gRPC の再試行による一時的障害の処理

作成者: James Newton-King

gRPC 再試行の機能を使用すると、gRPC クライアントで失敗した呼び出しの再試行を自動的に行うことができます。 この記事では、.NET で再試行ポリシーを構成することにより、回復性がありフォールト トレラントな gRPC アプリを作成する方法について説明します。

gRPC の再試行には、Grpc.Net.Client バージョン 2.36.0 以降が必要です。

一時的な障害の処理

gRPC の呼び出しは、一時的な障害によって中断される場合があります。 一時的な障害には次のものがあります。

  • ネットワーク接続の瞬間的な喪失。
  • サービスの一時的な利用不能。
  • サーバーの負荷によるタイムアウト。

gRPC の呼び出しが中断されると、エラーに関する詳細情報が含まれる RpcException がクライアントによってスローされます。 クライアント アプリでその例外をキャッチし、エラーの処理方法を選択する必要があります。

var client = new Greeter.GreeterClient(channel);
try
{
    var response = await client.SayHelloAsync(
        new HelloRequest { Name = ".NET" });

    Console.WriteLine("From server: " + response.Message);
}
catch (RpcException ex)
{
    // Write logic to inspect the error and retry
    // if the error is from a transient fault.
}

アプリ全体に再試行ロジックを複製すると、冗長になり、エラーが発生しやすくなります。 さいわい、.NET の gRPC クライアントには自動再試行のサポートが組み込まれています。

gRPC 再試行ポリシーを構成する

再試行ポリシーは、gRPC チャネルが作成されるときに 1 回構成されます。

var defaultMethodConfig = new MethodConfig
{
    Names = { MethodName.Default },
    RetryPolicy = new RetryPolicy
    {
        MaxAttempts = 5,
        InitialBackoff = TimeSpan.FromSeconds(1),
        MaxBackoff = TimeSpan.FromSeconds(5),
        BackoffMultiplier = 1.5,
        RetryableStatusCodes = { StatusCode.Unavailable }
    }
};

var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions
{
    ServiceConfig = new ServiceConfig { MethodConfigs = { defaultMethodConfig } }
});

上記のコードでは次の操作が行われます。

  • MethodConfig を作成します。 再試行ポリシーはメソッドごとに構成でき、メソッドは Names プロパティを使用して照合されます。 このメソッドは MethodName.Default で構成されているため、このチャネルによって呼び出されるすべての gRPC メソッドに適用されます。
  • 再試行ポリシーを構成します。 このポリシーにより、状態コード Unavailable で失敗した gRPC 呼び出しを自動的に再試行することがクライアントに指示されます。
  • GrpcChannelOptions.ServiceConfig を設定することにより、作成されたチャネルで再試行ポリシーが使用されるように構成します。

そのチャネルで作成される gRPC クライアントは、失敗した呼び出しを自動的に再試行するようになります。

var client = new Greeter.GreeterClient(channel);
var response = await client.SayHelloAsync(
    new HelloRequest { Name = ".NET" });

Console.WriteLine("From server: " + response.Message);

再試行が有効な場合

次の場合に呼び出しが再試行されます。

  • 失敗した状態コードが、RetryableStatusCodes の値と一致する。
  • 以前の試行回数が MaxAttempts よりも小さい。
  • この呼び出しがコミットされていない。
  • 期限を超えていない。

gRPC の呼び出しは、次の 2 つのシナリオでコミットされます。

  • クライアントが応答ヘッダーを受け取る場合。 応答ヘッダーは ServerCallContext.WriteResponseHeadersAsync が呼び出されたとき、または最初のメッセージがサーバー応答ストリームに書き込まれたときに、サーバーによって送信されます。
  • クライアントの送信メッセージ (ストリーミングの場合はメッセージ) がクライアントの最大バッファー サイズを超えている場合。 MaxRetryBufferSize および MaxRetryBufferPerCallSizeチャネルで構成されます。

コミットされた呼び出しは、状態コードまたは以前の試行回数に関係なく再試行されません。

ストリーミング呼び出し

ストリーミング呼び出しは gRPC の再試行と共に使用できますが、一緒に使用する際に重要な考慮事項があります。

  • サーバー ストリーミング双方向ストリーミング: サーバーから複数のメッセージを返すストリーミング RPC は、最初のメッセージを受信した後は再試行されません。 アプリで、サーバーと双方向のストリーミング呼び出しを手動で再確立するロジックを追加する必要があります。
  • クライアント ストリーミング双方向ストリーミング: サーバーに複数のメッセージを送信するストリーミング RPC は、送信メッセージがクライアントの最大バッファー サイズを超えた場合は再試行されません。 バッファーの最大サイズは、構成で増やすことができます。

詳細については、「再試行が有効な場合」を参照してください。

再試行バックオフの遅延

再試行間のバックオフの遅延は、InitialBackoffMaxBackoff、および BackoffMultiplier を使用して構成されます。 各オプションの詳細については、「gRPC 再試行のオプション」セクションを参照してください。

再試行間の実際の遅延はランダム化されます。 0 から現在のバックオフまでの間のランダムな遅延により、次の再試行が行われるタイミングが決まります。 エクスポネンシャル バックオフを構成していて、試行間の現在のバックオフが増加する場合でも、試行間の実際の遅延は常に大きくなるとは限らないことを考慮してください。 この遅延は、複数の呼び出しからの再試行が集中してサーバーに過負荷が発生しないようにするために、ランダム化されます。

メタデータによる再試行の検出

gRPC の再試行は、grpc-previous-rpc-attempts メタデータの存在によって検出できます。 grpc-previous-rpc-attempts メタデータについて:

  • 再試行された呼び出しに自動的に追加され、サーバーに送信されます。
  • その値は、それまで行われた試行回数を表します。
  • その値は常に整数です。

次のような再試行のシナリオについて考えてみます。

  1. クライアントがサーバーに gRPC 呼び出しを行う。
  2. サーバーが失敗し、再試行可能な状態コード応答を返す。
  3. クライアントが gRPC 呼び出しを再試行する。 それまでの試行回数が 1 回なので、grpc-previous-rpc-attempts メタデータは 1 の値を持つ。 メタデータが再試行と共にサーバーに送信される。
  4. サーバーが成功し、OK を返す。
  5. クライアントが成功を報告する。 grpc-previous-rpc-attempts が応答メタデータにあり、1 という値を持っている。

grpc-previous-rpc-attempts メタデータは最初の gRPC 呼び出しときには存在せず、1 回目の再試行では 1、2 回目の再試行では 2、というようになります。

gRPC 再試行のオプション

次の表では、gRPC 再試行ポリシーを構成するためのオプションについて説明します。

オプション 説明
MaxAttempts 呼び出しの試行の最大回数 (元の試行を含みます)。 この値は GrpcChannelOptions.MaxRetryAttempts によって制限され、その既定値は 5 です。 値は必須で、1 より大きい必要があります。
InitialBackoff 再試行の間隔の初期バックオフ遅延。 0 から現在のバックオフまでの間のランダムな遅延により、次の再試行が行われるタイミングが決まります。 各試行の後で、現在のバックオフに BackoffMultiplier が乗算されます。 値は必須で、0 より大きい必要があります。
MaxBackoff 最大バックオフにより、エクスポネンシャル バックオフの増加の上限が設定されます。 値は必須で、0 より大きい必要があります。
BackoffMultiplier バックオフは、各再試行の後でこの値を乗算され、乗数が 1 より大きい場合は指数関数的に増加します。 値は必須で、0 より大きい必要があります。
RetryableStatusCodes 状態コードのコレクション。 一致する状態で失敗した gRPC 呼び出しは、自動的に再試行されます。 状態コードの詳細については、「gRPC での状態コードとその使用」を参照してください。 再試行可能な状態コードが少なくとも 1 つ必要です。

ヘッジング

ヘッジングは、別の再試行戦略です。 ヘッジングを使用すると、1 つの gRPC 呼び出しの複数のコピーを、応答を待たずに積極的に送信できます。 ヘッジされた gRPC 呼び出しは、サーバー上で複数回実行される可能性があり、最初に成功した結果が使用されます。 ヘッジングは、複数回実行しても悪影響のない安全なメソッドに対してのみ有効にすることが重要です。

再試行と比較して、ヘッジングには長所と短所があります。

  • ヘッジングの利点は、より早く成功結果が返される可能性があることです。 複数の gRPC 呼び出しを同時に実行でき、最初の成功結果が得られた時点で完了します。
  • ヘッジングの欠点は、無駄になるおそれがあることです。 複数の呼び出しが行われ、すべてが成功する場合があります。 最初の結果のみが使用され、残りは破棄されます。

gRPC のヘッジング ポリシーを構成する

ヘッジング ポリシーは、再試行ポリシーのように構成されます。 ヘッジング ポリシーを再試行ポリシーと組み合わせることはできないことにご注意ください。

var defaultMethodConfig = new MethodConfig
{
    Names = { MethodName.Default },
    HedgingPolicy = new HedgingPolicy
    {
        MaxAttempts = 5,
        NonFatalStatusCodes = { StatusCode.Unavailable }
    }
};

var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions
{
    ServiceConfig = new ServiceConfig { MethodConfigs = { defaultMethodConfig } }
});

gRPC のヘッジング オプション

次の表では、gRPC ヘッジング ポリシーを構成するためのオプションについて説明します。

オプション 説明
MaxAttempts ヘッジング ポリシーにより、最大この回数まで呼び出しが送信されます。 MaxAttempts は、すべての試行の合計回数を表します (元の試行を含みます)。 この値は GrpcChannelOptions.MaxRetryAttempts によって制限され、その既定値は 5 です。 値は必須で、2 以上である必要があります。
HedgingDelay 最初の呼び出しは直ちに送信されますが、後続のヘッジング呼び出しはこの値だけ遅延します。 遅延を 0 または null に設定すると、ヘッジされたすべての呼び出しが直ちに送信されます。 HedgingDelay は省略可能で、既定値は 0 です。 値は 0 以上である必要があります。
NonFatalStatusCodes 他のヘッジの呼び出しがまだ成功する可能性があることを示す状態コードのコレクション。 サーバーから致命的ではない状態コードが返された場合、ヘッジされた呼び出しは続行されます。 それ以外の場合は、未処理の要求は取り消され、エラーがアプリに返されます。 状態コードの詳細については、「gRPC での状態コードとその使用」を参照してください。

その他のリソース