Implementowanie zadań w tle w mikrousług za pomocą usługi IHostedService i klasy BackgroundService

Napiwek

Ta zawartość jest fragmentem książki eBook, architektury mikrousług platformy .NET dla konteneryzowanych aplikacji platformy .NET dostępnych na platformie .NET Docs lub jako bezpłatnego pliku PDF, który można odczytać w trybie offline.

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

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. Podstawową ideą jest to, że można zarejestrować wiele zadań w tle (hostowanych usług), które są uruchamiane w tle podczas działania hosta internetowego lub hosta, jak pokazano na obrazie 6–26.

Diagram comparing ASP.NET Core IWebHost and .NET Core IHost.

Rysunek 6–26. Używanie usługi IHostedService na hoście sieci Web a hosta

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 aplikacjami konsoli zwykłych. Zwróć uwagę na różnicę między WebHost i Host.

A (implementacja WebHost klasy IWebHostbazowej ) w ASP.NET Core 2.0 to artefakt infrastruktury używany do udostępniania funkcji serwera HTTP procesowi, na przykład podczas implementowania aplikacji internetowej MVC lub usługi internetowego interfejsu API. Zapewnia ona całą nową dobroć infrastruktury w ASP.NET Core, umożliwiając korzystanie z wstrzykiwania zależności, wstawiania oprogramowania pośredniczącego w potoku żądania i podobnych. Te WebHost zastosowania są takie same IHostedServices w przypadku zadań w tle.

A Host (implementacja IHostklasy bazowej ) została wprowadzona w programie .NET Core 2.1. Zasadniczo element umożliwia korzystanie z WebHost podobnej infrastruktury niż to, Host co masz (iniekcja zależności, hostowane usługi itp.), ale w tym przypadku po prostu chcesz mieć prosty i lżejszy proces jako host, z niczym związanymi z funkcjami MVC, interfejsu API sieci Web lub serwera HTTP.

W związku z tym można wybrać i utworzyć wyspecjalizowany proces hosta w IHost celu obsługi hostowanych usług i nic innego, takiej mikrousługi wykonanej tylko do hostowania IHostedServices, lub możesz alternatywnie rozszerzyć istniejący ASP.NET Core WebHost, taki jak istniejący ASP.NET Core Web API lub aplikacja 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 część pamięci podręcznej.
  • Implementacja elementu QueueBackgroundWorkItem, która umożliwia wykonanie zadania w wątku 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ć dowolną z tych akcji do zadania w tle, które implementuje IHostedServiceelement .

Sposób dodawania jednego lub wielu IHostedServices do elementu WebHost lub Host polega na zarejestrowaniu ich za pomocą AddHostedService metody rozszerzenia w środowisku ASP.NET Core WebHost (lub w programie .NET Core 2.1 lub nowszym Host ). 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 okresem istnienia aplikacji (hostem lub mikrousługą). Zadania są rejestrowane podczas uruchamiania aplikacji i masz możliwość wykonania pewnych akcji lub czyszczenia, gdy aplikacja zostanie zamknięta.

Bez użycia polecenia IHostedServicezawsze można uruchomić wątek w tle, aby uruchomić dowolne zadanie. Różnica jest dokładnie w czasie zamykania aplikacji, gdy ten wątek zostanie po prostu zabity bez możliwości uruchamiania bezproblemowych akcji oczyszczania.

Interfejs IHostedService

Podczas rejestrowania elementu IHostedServiceplatforma .NET wywołuje StartAsync() metody IHostedService i StopAsync() typu podczas uruchamiania i zatrzymywania aplikacji odpowiednio. 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 główną pracę wymaganą 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 konkretnym przypadku w przypadku aplikacji eShopOnContainers wykonuje ona metodę aplikacji, która wykonuje zapytanie względem tabeli bazy danych, wyszukując zamówienia o określonym stanie i podczas stosowania zmian, publikuje zdarzenia integracji za pośrednictwem magistrali zdarzeń (poniżej może być używany program 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 oczekuje się, że nasza usługa zostanie anulowana w ciągu 5 sekund. W przeciwnym razie zostanie ona nagle zabita.

Poniższy kod zmieni ten czas na 10 sekund.

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

Diagram klas podsumowania

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

Diagram showing that IWebHost and IHost can host many services.

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. Jeśli na przykład wdrożysz usługę WebHost IIS lub zwykłą usługę aplikacja systemu Azure, host może zostać zamknięty z powodu recyklingu puli aplikacji. Jeśli jednak wdrażasz host jako kontener w orkiestratorze, na przykład Kubernetes, możesz kontrolować pewną liczbę wystąpień na żywo 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 WebHost wdrożenia w puli aplikacji istnieją scenariusze, takie jak ponowne wypełnianie lub opróżnianie pamięci podręcznej aplikacji w pamięci, które nadal mają zastosowanie.

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/hoście (począwszy od platformy .NET Core 2.1 z IHostprogramem ). Jego główną zaletą jest możliwość bezpiecznego anulowania w celu oczyszczenia kodu zadań w tle, gdy sam host jest zamykany.

Dodatkowe zasoby