Servizi gRPC affidabili con scadenze e annullamento

Di James Newton-King

Le scadenze e l'annullamento sono funzionalità usate dai client gRPC per interrompere le chiamate in corso. Questo articolo illustra perché le scadenze e l'annullamento sono importanti e come usarle nelle app .NET gRPC.

Scadenze

Una scadenza consente a un client gRPC di specificare per quanto tempo attenderà il completamento di una chiamata. Quando viene superata una scadenza, la chiamata viene annullata. L'impostazione di una scadenza è importante perché fornisce un limite superiore per quanto tempo può essere eseguita una chiamata. Impedisce che i servizi non funzionano per sempre e esauriscono le risorse del server. Le scadenze sono uno strumento utile per la creazione di app affidabili e devono essere configurate.

Configurazione scadenza:

  • Una scadenza viene configurata utilizzando CallOptions.Deadline quando viene effettuata una chiamata.
  • Non esiste alcun valore di scadenza predefinito. Le chiamate gRPC non sono limitate a meno che non venga specificata una scadenza.
  • Una scadenza è l'ora UTC di quando viene superata la scadenza. Ad esempio, DateTime.UtcNow.AddSeconds(5) è una scadenza di 5 secondi da ora.
  • Se viene utilizzata un'ora precedente o corrente, la chiamata supera immediatamente la scadenza.
  • La scadenza viene inviata con la chiamata gRPC al servizio e viene monitorata in modo indipendente sia dal client che dal servizio. È possibile che una chiamata gRPC venga completata in un computer, ma entro il momento in cui la risposta è stata restituita al client la scadenza è stata superata.

Se viene superata una scadenza, il client e il servizio hanno un comportamento diverso:

  • Il client interrompe immediatamente la richiesta HTTP sottostante e genera un DeadlineExceeded errore. L'app client può scegliere di intercettare l'errore e visualizzare un messaggio di timeout all'utente.
  • Nel server la richiesta HTTP in esecuzione viene interrotta e viene generato ServerCallContext.CancellationToken . Sebbene la richiesta HTTP venga interrotta, la chiamata gRPC continua a essere eseguita nel server fino al completamento del metodo. È importante che il token di annullamento venga passato ai metodi asincroni in modo che vengano annullati insieme alla chiamata. Ad esempio, passando un token di annullamento alle query asincrone del database e alle richieste HTTP. Il passaggio di un token di annullamento consente il completamento rapido della chiamata annullata nel server e liberare risorse per altre chiamate.

Configurare CallOptions.Deadline per impostare una scadenza per una chiamata gRPC:

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

Uso ServerCallContext.CancellationToken di in un servizio gRPC:

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 };
}

Scadenze e nuovi tentativi

Quando una chiamata gRPC è configurata con la gestione degli errori di ripetizione dei tentativi e una scadenza, la scadenza tiene traccia del tempo in tutti i tentativi per una chiamata gRPC. Se la scadenza viene superata, una chiamata gRPC interrompe immediatamente la richiesta HTTP sottostante, ignora eventuali tentativi rimanenti e genera un DeadlineExceeded errore.

Propagazione delle scadenze

Quando viene eseguita una chiamata gRPC da un servizio gRPC in esecuzione, la scadenza deve essere propagata. Ad esempio:

  1. Chiamate FrontendService.GetUser dell'app client con una scadenza.
  2. FrontendService chiama UserService.GetUser. La scadenza specificata dal client deve essere specificata con la nuova chiamata gRPC.
  3. UserService.GetUser riceve la scadenza. Si verifica correttamente il timeout se viene superata la scadenza dell'app client.

Il contesto di chiamata fornisce la scadenza con 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;
}

La propagazione manuale delle scadenze può essere complessa. La scadenza deve essere passata a ogni chiamata ed è facile perdere accidentalmente. Una soluzione automatica è disponibile con la factory client gRPC. Specifica di EnableCallContextPropagation:

  • Propaga automaticamente la scadenza e il token di annullamento alle chiamate figlio.
  • Non propaga la scadenza se la chiamata figlio specifica una scadenza inferiore. Ad esempio, una scadenza propagata di 10 secondi non viene usata se una chiamata figlio specifica una nuova scadenza di 5 secondi usando CallOptions.Deadline. Quando sono disponibili più scadenze, viene utilizzata la scadenza più piccola.
  • È un ottimo modo per garantire che gli scenari gRPC complessi annidati propagano sempre la scadenza e l'annullamento.
services
    .AddGrpcClient<User.UserServiceClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .EnableCallContextPropagation();

Per altre informazioni, vedere Integrazione della factory client gRPC in .NET.

Annullamento

L'annullamento consente a un client gRPC di annullare chiamate a esecuzione prolungata non più necessarie. Ad esempio, una chiamata gRPC che trasmette gli aggiornamenti in tempo reale viene avviata quando l'utente visita una pagina in un sito Web. Il flusso deve essere annullato quando l'utente si allontana dalla pagina.

Una chiamata gRPC può essere annullata nel client passando un token di annullamento con CallOptions.CancellationToken o chiamando Dispose sulla chiamata.

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();
}

I servizi gRPC che possono essere annullati devono:

  • Passare ServerCallContext.CancellationToken ai metodi asincroni. L'annullamento dei metodi asincroni consente il completamento rapido della chiamata sul server.
  • Propagare il token di annullamento alle chiamate figlio. La propagazione del token di annullamento garantisce che le chiamate figlio vengano annullate con il relativo elemento padre. gRPC client factory e EnableCallContextPropagation() propaga automaticamente il token di annullamento.

Risorse aggiuntive