期限とキャンセルを使用した信頼性の高い gRPC サービス

作成者: James Newton-King

期限とキャンセルは、gRPC クライアントが実行中の呼び出しを中止するために使用する機能です。 この記事では、期限とキャンセルが重要である理由と、.NET gRPC アプリでそれらを使用する方法について説明します。

期限

期限を使用すれば、gRPC クライアントで、呼び出しの完了をどれだけの時間待機するかを指定できます。 期限を超過した場合、呼び出しはキャンセルされます。 期限を設定することは重要です。呼び出しを実行できる時間の長さに上限を設けることができるからです。 これにより、誤動作しているサービスが永遠に実行されてサーバー リソースが枯渇するのを防ぐことができます。 期限は、信頼性の高いアプリをビルドするための便利なツールであり、構成する必要があります。

期限の構成:

  • 期限は、呼び出しを行うときに CallOptions.Deadline を使用して構成します。
  • 期限に既定値はありません。 期限が指定されない限り、gRPC の呼び出しに時間の制限はありません。
  • 期限とは、期限を超過したときの UTC 時刻です。 たとえば、DateTime.UtcNow.AddSeconds(5) は今から 5 秒が期限です。
  • 過去または現在の時刻が使用されている場合、呼び出しはすぐに期限を超過します。
  • 期限は、gRPC 呼び出しと共にサービスに送信され、クライアントとサービスの両方によって個別に追跡されます。 gRPC の呼び出しは 1 台のコンピューター上で完了しますが、その応答がクライアントに返される前に期限を超過してしまう可能性があります。

期限を超過した場合、クライアントとサービスでは動作は異なります。

  • クライアントでは、基になる HTTP 要求が直ちに中止され、DeadlineExceeded エラーがスローされます。 クライアント アプリでは、エラーをキャッチしてタイムアウト メッセージをユーザーに表示することを選択できます。
  • サーバー上では、実行中の HTTP 要求が中止され、ServerCallContext.CancellationToken が発生します。 HTTP 要求は中止されますが、gRPC 呼び出しは、メソッドが完了するまでサーバー上で実行され続けます。 非同期メソッドにキャンセル トークンが渡され、呼び出しと共にキャンセルされるようにすることが重要です。 たとえば、非同期データベース クエリおよび HTTP 要求にキャンセル トークンを渡します。 キャンセル トークンを渡すことにより、キャンセルされた呼び出しをサーバー上ですばやく完了させ、他の呼び出しのためにリソースを解放することができます。

gRPC 呼び出しの期限を設定するには、CallOptions.Deadline を構成します。

var client = new Greet.GreeterClient(channel);

try
{
    var response = await client.SayHelloAsync(
        new HelloRequest { Name = "World" },
        deadline: DateTime.UtcNow.AddSeconds(5));
    
    // Greeting: Hello World
    Console.WriteLine("Greeting: " + response.Message);
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.DeadlineExceeded)
{
    Console.WriteLine("Greeting timeout.");
}

gRPC サービスで ServerCallContext.CancellationToken を使用する場合:

public override async Task<HelloReply> SayHello(HelloRequest request,
    ServerCallContext context)
{
    var user = await _databaseContext.GetUserAsync(request.Name,
        context.CancellationToken);

    return new HelloReply { Message = "Hello " + user.DisplayName };
}

期限と再試行

gRPC 呼び出しが再試行による障害の処理と期限で構成されている場合、gRPC 呼び出しのすべての再試行で期限によって時間が追跡されます。 期限を超えた場合、gRPC 呼び出しは基になる HTTP 要求をすぐに中止し、残りの再試行をスキップして、DeadlineExceeded エラーをスローします。

期限の伝達

実行中の gRPC サービスから gRPC 呼び出しが行われる場合は、期限が伝達される必要があります。 次に例を示します。

  1. クライアント アプリで、期限付きの FrontendService.GetUser 呼び出しが行われています。
  2. FrontendServiceUserService.GetUser を呼び出します。 クライアントによって指定された期限は、新しい gRPC 呼び出しでも指定される必要があります。
  3. UserService.GetUser は期限を受け取ります。 それは、クライアント アプリの期限を超過すると、正常にタイムアウトします。

呼び出しコンテキストでは、ServerCallContext.Deadline を使用して期限が指定されます。

public override async Task<UserResponse> GetUser(UserRequest request,
    ServerCallContext context)
{
    var client = new User.UserServiceClient(_channel);
    var response = await client.GetUserAsync(
        new UserRequest { Id = request.Id },
        deadline: context.Deadline);

    return response;
}

手動による期限の伝達は煩雑になることがあります。 期限は、すべての呼び出しに渡す必要がありますが、誤って見逃しがちです。 gRPC クライアント ファクトリでは、自動ソリューションを使用できます。 EnableCallContextPropagation を指定すると、次のようになります。

  • 期限およびキャンセル トークンが子呼び出しに自動的に伝達されます。
  • 子呼び出しでより小さい期限が指定されている場合、期限は伝達されません。 たとえば、伝達された 10 秒の期限は、子呼び出しで CallOptions.Deadline を使用して新しい期限の 5 秒を指定した場合、使用されません。 複数の期限がある場合は、最小の期限が使用されます。
  • 複雑で入れ子になった gRPC のシナリオで、常に期限とキャンセルが確実に伝達されるようにする優れた方法となります。
services
    .AddGrpcClient<User.UserServiceClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .EnableCallContextPropagation();

詳細については、「.NET での gRPC クライアント ファクトリの統合」を参照してください。

キャンセル

キャンセルを使用すると、gRPC クライアントで、必要なくなった実行時間の長い呼び出しをキャンセルすることができます。 たとえば、リアルタイムの更新をストリームする gRPC 呼び出しは、ユーザーが Web サイト上のページにアクセスしたときに開始されます。 ユーザーがそのページから移動したとき、ストリームはキャンセルされる必要があります。

クライアントで gRPC 呼び出しをキャンセルするには、CallOptions CancellationToken を使用してキャンセル トークンを渡すか、または呼び出しに対して Dispose を呼び出します。

private AsyncServerStreamingCall<HelloReply> _call;

public void StartStream()
{
    _call = client.SayHellos(new HelloRequest { Name = "World" });

    // Read response in background task.
    _ = Task.Run(async () =>
    {
        await foreach (var response in _call.ResponseStream.ReadAllAsync())
        {
            Console.WriteLine("Greeting: " + response.Message);
        }
    });
}

public void StopStream()
{
    _call.Dispose();
}

キャンセルできる gRPC サービスは次のとおりです。

  • ServerCallContext.CancellationToken を非同期メソッドに渡します。 非同期メソッドをキャンセルすると、サーバー上での呼び出しを迅速に完了させることができます。
  • キャンセル トークンを子呼び出しに伝達します。 キャンセル トークンを伝達することにより、子の呼び出しがその親と共に確実にキャンセルされます。 gRPC クライアント ファクトリEnableCallContextPropagation() を使用すれば、キャンセル トークンが自動的に伝達されます。

その他のリソース