Niezawodne usługi gRPC z terminami i anulowaniem

Autor: James Newton-King

Terminy i anulowanie to funkcje używane przez klientów gRPC do przerwania wywołań w toku. W tym artykule opisano, dlaczego terminy i anulowanie są ważne oraz jak używać ich w aplikacjach gRPC platformy .NET.

Terminy

Termin ostateczny umożliwia klientowi gRPC określenie, jak długo będzie czekać na zakończenie wywołania. Po przekroczeniu terminu połączenia zostanie anulowane. Ustawienie terminu ostatecznego jest ważne, ponieważ zapewnia górny limit czasu uruchomienia wywołania. Przestaje działać nieprawidłowo działające usługi na zawsze i wyczerpać zasoby serwera. Terminy ostateczne to przydatne narzędzie do tworzenia niezawodnych aplikacji i należy je skonfigurować.

Konfiguracja terminu ostatecznego:

  • Termin ostatecznego ostatecznego terminu jest konfigurowany przy użyciu CallOptions.Deadline wywołania.
  • Nie ma domyślnej wartości terminu ostatecznego. Wywołania gRPC nie są ograniczone czasowo, chyba że zostanie określony termin ostateczny.
  • Termin ostateczny to czas UTC przekroczenia terminu ostatecznego. Na przykład DateTime.UtcNow.AddSeconds(5) jest to termin 5 sekund od teraz.
  • Jeśli jest używany czas przeszły lub bieżący, wywołanie natychmiast przekracza termin ostateczny.
  • Termin jest wysyłany z wywołaniem gRPC do usługi i jest niezależnie śledzony przez klienta i usługę. Istnieje możliwość, że wywołanie gRPC zostanie ukończone na jednej maszynie, ale do czasu, gdy odpowiedź wróciła do klienta, termin został przekroczony.

W przypadku przekroczenia terminu klient i usługa mają inne zachowanie:

  • Klient natychmiast przerywa bazowe żądanie HTTP i zgłasza DeadlineExceeded błąd. Aplikacja kliencka może wybrać przechwycenie błędu i wyświetlić użytkownikowi komunikat o przekroczeniu limitu czasu.
  • Na serwerze wykonywane żądanie HTTP zostało przerwane, a zgłaszana jest wartość ServerCallContext.CancellationToken . Mimo że żądanie HTTP zostało przerwane, wywołanie gRPC będzie nadal działać na serwerze, dopóki metoda nie zostanie ukończona. Ważne jest, aby token anulowania został przekazany do metod asynchronicznych, aby zostały anulowane wraz z wywołaniem. Na przykład przekazanie tokenu anulowania do asynchronicznych zapytań bazy danych i żądań HTTP. Przekazanie tokenu anulowania umożliwia szybkie ukończenie anulowanego wywołania na serwerze i zwolnienie zasobów dla innych wywołań.

Skonfiguruj CallOptions.Deadline , aby ustawić termin ostatecznego wywołania 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.");
}

Używanie ServerCallContext.CancellationToken w usłudze 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 };
}

Terminy i ponawianie prób

Gdy wywołanie gRPC jest skonfigurowane z obsługą błędów ponawiania i terminem, termin ostatecznego śledzi czas we wszystkich ponownych próbach wywołania gRPC. Jeśli termin zostanie przekroczony, wywołanie gRPC natychmiast przerywa bazowe żądanie HTTP, pomija wszelkie pozostałe próby i zgłasza DeadlineExceeded błąd.

Propagowanie terminów ostatecznych

Gdy wywołanie gRPC jest wykonywane z wykonywanej usługi gRPC, termin powinien zostać rozpropagowany. Przykład:

  1. Wywołania FrontendService.GetUser aplikacji klienckiej z terminem ostatecznym.
  2. FrontendService wywołuje metodę UserService.GetUser. Termin określony przez klienta należy określić przy użyciu nowego wywołania gRPC.
  3. UserService.GetUser otrzymuje termin. Prawidłowo przekracza limit czasu, jeśli termin ostatecznego terminu aplikacji klienckiej zostanie przekroczony.

Kontekst wywołania zawiera termin ostateczny: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;
}

Ręczne propagowanie terminów może być kłopotliwe. Termin musi zostać przekazany do każdego połączenia i łatwo jest przypadkowo przegapić. Automatyczne rozwiązanie jest dostępne w fabryce klienta gRPC. Określanie elementu EnableCallContextPropagation:

  • Automatycznie propaguje termin ostatecznego i token anulowania do wywołań podrzędnych.
  • Nie propaguje terminu ostatecznego, jeśli wywołanie podrzędne określa krótszy termin. Na przykład propagowany termin 10 sekund nie jest używany, jeśli wywołanie podrzędne określa nowy termin 5 sekund przy użyciu polecenia CallOptions.Deadline. Gdy dostępnych jest wiele terminów ostatecznych, jest używany najmniejszy termin.
  • Jest doskonałym sposobem zapewnienia, że złożone, zagnieżdżone scenariusze gRPC zawsze propagują termin i anulowanie.
services
    .AddGrpcClient<User.UserServiceClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .EnableCallContextPropagation();

Aby uzyskać więcej informacji, zobacz integracja fabryki klienta gRPC na platformie .NET.

Anulowanie

Anulowanie umożliwia klientowi gRPC anulowanie długotrwałych wywołań, które nie są już potrzebne. Na przykład wywołanie gRPC, które przesyła strumieniowo aktualizacje w czasie rzeczywistym, jest uruchamiane, gdy użytkownik odwiedza stronę w witrynie internetowej. Strumień powinien zostać anulowany, gdy użytkownik przejdzie z dala od strony.

Wywołanie gRPC można anulować w kliencie, przekazując token anulowania za pomocą metody CallOptions.CancellationToken lub wywołując wywołanie Dispose wywołania.

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

Usługi gRPC, które można anulować, powinny:

  • Przekazywanie ServerCallContext.CancellationToken do metod asynchronicznych. Anulowanie metod asynchronicznych umożliwia szybkie wykonanie wywołania na serwerze.
  • Propagacja tokenu anulowania do wywołań podrzędnych. Propagowanie tokenu anulowania gwarantuje, że wywołania podrzędne zostaną anulowane z ich elementem nadrzędnym. Fabryka klienta gRPC i EnableCallContextPropagation() automatycznie propaguje token anulowania.

Dodatkowe zasoby