Udostępnij za pośrednictwem


Implementacja zadań wykonywanych w tle w mikrousługach za pomocą usługi IHostedService i klasy BackgroundService

Wskazówka

Ta treść jest fragmentem eBooka "Architektura mikrousług .NET dla konteneryzowanych aplikacji .NET", dostępnego na .NET Docs lub jako bezpłatny plik PDF do pobrania i czytania w trybie offline.

Miniatura okładki eBooka „Architektura mikrousług platformy .NET dla konteneryzowanych aplikacji platformy .NET”.

Zadania w tle i zaplanowane zadania mogą być potrzebne do użycia w dowolnej aplikacji, niezależnie od tego, czy są zgodne ze wzorcem architektury mikrousług. Różnica w przypadku korzystania z architektury mikrousług polega na tym, że można zaimplementować zadanie w tle w osobnym procesie/kontenerze do hostowania, aby można było skalować je w dół/w górę w zależności od potrzeb.

Z ogólnego punktu widzenia na platformie .NET nazwaliśmy tego typu zadaniami Hostowane usługi, ponieważ są to usługi/logika hostowane w ramach hosta/aplikacji/mikrousługi. Należy pamiętać, że w tym przypadku hostowana usługa po prostu oznacza klasę z logiką zadań w tle.

Ponieważ platforma .NET Core 2.0 udostępnia nowy interfejs o nazwie IHostedService ułatwia implementowanie hostowanych usług. Podstawowa idea to możliwość zarejestrowania wielu zadań w tle (usługi hostingowe), które są uruchamiane w tle, gdy działa host internetowy lub inny host, jak pokazano na rysunku 6–26.

Diagram porównujący ASP.NET Core IWebHost i .NET Core IHost.

Rysunek 6–26. Używanie usługi IHostedService w WebHost vs. Host

ASP.NET Core 1.x i 2.x obsługują IWebHost procesy w tle w aplikacjach internetowych. Platforma .NET Core 2.1 i nowsze wersje obsługują IHost procesy w tle z prostymi aplikacjami konsolowymi. Zwróć uwagę na różnicę między WebHost i Host.

A (WebHost implementacja klasy bazowej IWebHost) w ASP.NET Core 2.0 to element infrastruktury używany do udostępniania funkcji serwera HTTP procesowi, na przykład podczas tworzenia aplikacji webowej MVC lub usługi Web API. Zapewnia wszystkie nowe funkcje infrastruktury w ASP.NET Core, umożliwiając korzystanie z mechanizmu wstrzykiwania zależności, dodawania oprogramowania pośredniczącego do kolejki żądań i innych podobnych rozwiązań. WebHost używa tych samych IHostedServices do zadań w tle.

A Host (klasa bazowa implementująca IHost) została wprowadzona w .NET Core 2.1. Zasadniczo element Host umożliwia korzystanie z podobnej infrastruktury do tej, którą masz z WebHost (iniekcja zależności, hostowane usługi itp.), ale w tym przypadku chcesz mieć po prostu prosty i lżejszy proces jako host, bez funkcji związanych z MVC, interfejsem API sieci Web lub serwerem HTTP.

W związku z tym możesz wybrać i utworzyć wyspecjalizowany proces hosta z IHost służący do obsługi hostowanych usług, jak mikrousługa przeznaczona wyłącznie do hostowania IHostedServices, lub możesz rozszerzyć istniejącą aplikację ASP.NET Core WebHost, na przykład ASP.NET Core Web API lub aplikację MVC.

Każde podejście ma zalety i wady w zależności od potrzeb biznesowych i skalowalności. Najważniejsze jest to, że jeśli zadania w tle nie mają nic wspólnego z protokołem HTTP (IWebHost), należy użyć polecenia IHost.

Rejestrowanie hostowanych usług na hoście sieci Web lub hoście

Przejdźmy do szczegółów interfejsu IHostedService , ponieważ jego użycie jest dość podobne w elemecie WebHost lub w elemecie Host.

SignalR to jeden z przykładów artefaktu korzystającego z hostowanych usług, ale można go również użyć w znacznie prostszych sytuacjach, takich jak:

  • Zadanie w tle sonduje bazę danych, wyszukując zmiany.
  • Zaplanowane zadanie okresowo aktualizuje pewne dane w pamięci podręcznej.
  • Implementacja QueueBackgroundWorkItem, która pozwala na wykonanie zadania w tle.
  • Przetwarzanie komunikatów z kolejki komunikatów w tle aplikacji internetowej podczas udostępniania typowych usług, takich jak ILogger.
  • Zadanie w tle rozpoczęte z Task.Run().

W zasadzie można odciążyć jedną z tych akcji na zadanie w tle, które implementuje IHostedService.

Sposób dodawania jednego lub wielu IHostedServices do elementu WebHost lub Host polega na zarejestrowaniu ich za pomocą metody rozszerzenia AddHostedService w WebHost środowisku ASP.NET Core (lub w Host środowisku .NET Core 2.1 i nowszym). Zasadniczo musisz zarejestrować hostowane usługi w ramach uruchamiania aplikacji w Program.cs.

//Other DI registrations;

// Register Hosted Services
builder.Services.AddHostedService<GracePeriodManagerService>();
builder.Services.AddHostedService<MyHostedServiceB>();
builder.Services.AddHostedService<MyHostedServiceC>();
//...

W tym kodzie hostowana GracePeriodManagerService usługa jest prawdziwym kodem z mikrousługi biznesowej Ordering w eShopOnContainers, podczas gdy pozostałe dwa są tylko dwoma dodatkowymi przykładami.

Wykonywanie IHostedService zadania w tle jest koordynowane z cyklem życia aplikacji (hosta lub mikrousługi). Rejestrujesz zadania podczas uruchamiania aplikacji i masz okazję wykonać pewne działania lub posprzątać, gdy aplikacja się zamyka.

Bez użycia polecenia IHostedServicezawsze można uruchomić wątek w tle, aby uruchomić dowolne zadanie. Różnica polega dokładnie na tym, że podczas zamykania aplikacji wątek ten zostanie po prostu przerwany, bez możliwości wykonania łagodnych czynności porządkowych.

Interfejs IHostedService

Podczas rejestrowania elementu IHostedService, platforma .NET podczas uruchamiania i zatrzymywania aplikacji wywołuje odpowiednio metody StartAsync() i StopAsync() typu IHostedService. Aby uzyskać więcej informacji, zobacz Interfejs IHostedService.

Jak można sobie wyobrazić, możesz utworzyć wiele implementacji usługi IHostedService i zarejestrować je w Program.cs, jak pokazano wcześniej. Wszystkie te hostowane usługi zostaną uruchomione i zatrzymane wraz z aplikacją/mikrousługą.

Jako deweloper odpowiadasz za obsługę akcji zatrzymywania usług, gdy StopAsync() metoda jest wyzwalana przez hosta.

Implementowanie usługi IHostedService przy użyciu niestandardowej klasy usługi hostowanej pochodzącej z klasy bazowej BackgroundService

Możesz utworzyć niestandardową klasę usługi hostowanej od podstaw i zaimplementować klasę IHostedService, co należy zrobić podczas korzystania z platformy .NET Core 2.0 lub nowszej.

Jednak ponieważ większość zadań w tle będzie mieć podobne potrzeby w odniesieniu do zarządzania tokenami anulowania i innych typowych operacji, istnieje wygodna abstrakcyjna klasa bazowa, z której można korzystać, o nazwie BackgroundService (dostępna od wersji .NET Core 2.1).

Ta klasa zapewnia podstawowe czynności wymagane do skonfigurowania zadania w tle.

Następny kod jest abstrakcyjną klasą bazową BackgroundService zaimplementowaną na platformie .NET.

// Copyright (c) .NET Foundation. Licensed under the Apache License, Version 2.0.
/// <summary>
/// Base class for implementing a long running <see cref="IHostedService"/>.
/// </summary>
public abstract class BackgroundService : IHostedService, IDisposable
{
    private Task _executingTask;
    private readonly CancellationTokenSource _stoppingCts =
                                                   new CancellationTokenSource();

    protected abstract Task ExecuteAsync(CancellationToken stoppingToken);

    public virtual Task StartAsync(CancellationToken cancellationToken)
    {
        // Store the task we're executing
        _executingTask = ExecuteAsync(_stoppingCts.Token);

        // If the task is completed then return it,
        // this will bubble cancellation and failure to the caller
        if (_executingTask.IsCompleted)
        {
            return _executingTask;
        }

        // Otherwise it's running
        return Task.CompletedTask;
    }

    public virtual async Task StopAsync(CancellationToken cancellationToken)
    {
        // Stop called without start
        if (_executingTask == null)
        {
            return;
        }

        try
        {
            // Signal cancellation to the executing method
            _stoppingCts.Cancel();
        }
        finally
        {
            // Wait until the task completes or the stop token triggers
            await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite,
                                                          cancellationToken));
        }

    }

    public virtual void Dispose()
    {
        _stoppingCts.Cancel();
    }
}

Podczas wyprowadzania z poprzedniej abstrakcyjnej klasy bazowej, dzięki tej odziedziczonej implementacji wystarczy zaimplementować ExecuteAsync() metodę we własnej niestandardowej klasie usługi hostowanej, jak w poniższym uproszczonym kodzie z modułu eShopOnContainers, który sonduje bazę danych i publikuje zdarzenia integracji w magistrali zdarzeń w razie potrzeby.

public class GracePeriodManagerService : BackgroundService
{
    private readonly ILogger<GracePeriodManagerService> _logger;
    private readonly OrderingBackgroundSettings _settings;

    private readonly IEventBus _eventBus;

    public GracePeriodManagerService(IOptions<OrderingBackgroundSettings> settings,
                                     IEventBus eventBus,
                                     ILogger<GracePeriodManagerService> logger)
    {
        // Constructor's parameters validations...
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogDebug($"GracePeriodManagerService is starting.");

        stoppingToken.Register(() =>
            _logger.LogDebug($" GracePeriod background task is stopping."));

        while (!stoppingToken.IsCancellationRequested)
        {
            _logger.LogDebug($"GracePeriod task doing background work.");

            // This eShopOnContainers method is querying a database table
            // and publishing events into the Event Bus (RabbitMQ / ServiceBus)
            CheckConfirmedGracePeriodOrders();

            try {
                    await Task.Delay(_settings.CheckUpdateTime, stoppingToken);
                }
            catch (TaskCanceledException exception) {
                    _logger.LogCritical(exception, "TaskCanceledException Error", exception.Message);
                }
        }

        _logger.LogDebug($"GracePeriod background task is stopping.");
    }

    .../...
}

W tym przypadku aplikacja eShopOnContainers wykonuje metodę, która przeszukuje tabelę bazy danych w poszukiwaniu zamówień o określonym stanie. Podczas wprowadzania zmian publikuje zdarzenia integracyjne za pomocą magistrali zdarzeń (pod spodem może używać RabbitMQ lub Azure Service Bus).

Oczywiście można uruchomić dowolne inne zadanie w tle biznesowym, zamiast tego.

Domyślnie token anulowania jest ustawiany z limitem czasu 5 sekund, chociaż można zmienić wartość podczas kompilowania WebHost przy użyciu UseShutdownTimeout rozszerzenia IWebHostBuilder. Oznacza to, że oczekujemy anulowania naszej usługi w ciągu 5 sekund, w przeciwnym razie zostanie ona gwałtownie zakończona.

Poniższy kod zmieni ten czas na 10 sekund.

WebHost.CreateDefaultBuilder(args)
    .UseShutdownTimeout(TimeSpan.FromSeconds(10))
    ...

Diagram klas podsumowujący

Na poniższej ilustracji przedstawiono wizualne podsumowanie klas i interfejsów zaangażowanych podczas implementowania IHostedServices.

Diagram pokazujący, że IWebHost i IHost mogą hostować wiele usług.

Rysunek 6–27. Diagram klas przedstawiający wiele klas i interfejsów związanych z usługą IHostedService

Diagram klas: IWebHost i IHost mogą hostować wiele usług, które dziedziczą z usługi BackgroundService, która implementuje usługę IHostedService.

Zagadnienia dotyczące wdrażania i wnioski

Należy pamiętać, że sposób wdrażania ASP.NET Core WebHost lub .NET Host może mieć wpływ na ostateczne rozwiązanie. Na przykład, jeśli wdrożysz swoje WebHost na IIS lub zwykłą usługę Azure App Service, twój host może zostać wyłączony z powodu recyklingu puli aplikacji. Jeśli jednak wdrażasz host jako kontener do orkiestratora, na przykład Kubernetes, możesz kontrolować zagwarantowaną liczbę działających instancji hosta. Ponadto możesz rozważyć inne podejścia w chmurze, szczególnie w przypadku tych scenariuszy, takich jak usługa Azure Functions. Na koniec, jeśli chcesz, aby usługa mogła być uruchomiona przez cały czas i wdrażana w systemie Windows Server, możesz użyć usługi systemu Windows.

Jednak nawet w przypadku wdrożenia WebHost do puli aplikacji, istnieją scenariusze, takie jak ponowne wypełnianie lub opróżnianie pamięci podręcznej aplikacji przechowywanej w pamięci, które nadal są odpowiednie.

Interfejs IHostedService zapewnia wygodny sposób uruchamiania zadań w tle w aplikacji internetowej ASP.NET Core (w wersji .NET Core 2.0 i nowszych) lub w dowolnym procesie lub hoście (począwszy od platformy .NET Core 2.1 z IHost). Jego główną zaletą jest możliwość łagodnego anulowania, które pozwala na oczyszczenie kodu zadań w tle, kiedy sam host jest zamykany.

Dodatkowe zasoby