Implementace úloh na pozadí v mikroslužbách pomocí IHostedService a třídy BackgroundService

Tip

Tento obsah je výňatek z eBooku, architektury mikroslužeb .NET pro kontejnerizované aplikace .NET, které jsou k dispozici na .NET Docs nebo jako zdarma ke stažení PDF, které lze číst offline.

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

Úlohy na pozadí a naplánované úlohy jsou něco, co může být potřeba použít v jakékoli aplikaci, ať už se řídí vzorem architektury mikroslužeb. Rozdíl při použití architektury mikroslužeb spočívá v tom, že úlohu na pozadí můžete implementovat v samostatném procesu nebo kontejneru pro hostování, abyste ji mohli podle potřeby vertikálně snížit nebo snížit.

Z obecného hlediska jsme v .NET nazvali tento typ úloh hostovaných služeb, protože se jedná o služby/logiku hostující v rámci hostitele, aplikace nebo mikroslužby. Všimněte si, že v tomto případě hostovaná služba jednoduše znamená třídu s logikou úlohy na pozadí.

Vzhledem k tomu, rozhraní .NET Core 2.0 poskytuje nové rozhraní, které IHostedService vám pomůže snadno implementovat hostované služby. Základní myšlenkou je, že můžete zaregistrovat několik úloh na pozadí (hostovaných služeb), které běží na pozadí, zatímco váš webový hostitel nebo hostitel běží, jak je znázorněno na obrázku 6–26.

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

Obrázek 6–26 Použití služby IHostedService ve webovém hostiteli vs. hostitele

podpora ASP.NET Core 1.x a 2.x IWebHost pro procesy na pozadí ve webových aplikacích. .NET Core 2.1 a novější verze podporují IHost procesy na pozadí s prostými konzolovými aplikacemi. Všimněte si rozdílu mezi WebHost a Host.

A WebHost (implementace IWebHostzákladní třídy) v ASP.NET Core 2.0 je artefakt infrastruktury, který používáte k poskytování funkcí serveru HTTP vašemu procesu, například při implementaci webové aplikace MVC nebo služby webového rozhraní API. Poskytuje veškerou novou infrastrukturu v ASP.NET Core, která umožňuje používat injektáž závislostí, vkládat middlewary do kanálu žádosti a podobně. Tyto WebHost funkce se používají stejně IHostedServices pro úlohy na pozadí.

V Host .NET Core 2.1 byla zavedena implementace základní třídy IHost. V Host podstatě vám to umožňuje mít podobnou infrastrukturu, jakou máte WebHost (injektáž závislostí, hostované služby atd.), ale v tomto případě chcete mít jako hostitele jednoduchý a lehčí proces, bez toho, co souvisí s funkcemi MVC, webového rozhraní API nebo serveru HTTP.

Proto můžete zvolit a buď vytvořit specializovaný hostitelský proces IHost pro zpracování hostovaných služeb a nic jiného, jako je mikroslužba vytvořená jen pro hostování IHostedServices, nebo můžete případně rozšířit existující ASP.NET Core WebHost, jako je existující webové rozhraní API ASP.NET Core nebo aplikace MVC.

Každý přístup má výhody a nevýhody v závislosti na potřebách vaší firmy a škálovatelnosti. Dolní řádek je v podstatě to, že pokud vaše úlohy na pozadí nemají nic společného s HTTP (IWebHost), byste měli použít IHost.

Registrace hostovaných služeb ve webovém hostiteli nebo hostiteli

Pojďme přejít k podrobnostem o IHostedService rozhraní, protože jeho použití je docela podobné v nebo WebHost v Hostrozhraní .

SignalR je jedním z příkladů artefaktu, který používá hostované služby, ale můžete ho také použít pro mnohem jednodušší věci, jako například:

  • Úloha na pozadí dotazující databázi, která hledá změny.
  • Naplánovaná úloha pravidelně aktualizuje určitou mezipaměť.
  • Implementace QueueBackgroundWorkItem, která umožňuje spuštění úlohy ve vlákně na pozadí.
  • Zpracování zpráv z fronty zpráv na pozadí webové aplikace při sdílení běžných služeb, jako ILoggerje .
  • Úloha na pozadí byla spuštěna s Task.Run().

V podstatě můžete některou z těchto akcí přesměrovat na úlohu na pozadí, která implementuje IHostedService.

Způsob, jakým do svého WebHost účtu přidáte jeden nebo víceIHostedServices, je jejich registrací prostřednictvím AddHostedService metody rozšíření v ASP.NET Core WebHost (nebo v Host .NET Core 2.1 a novějšímHost). V podstatě musíte v Program.cs zaregistrovat hostované služby v rámci spuštění aplikace.

//Other DI registrations;

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

V takovém kódu GracePeriodManagerService je hostovaná služba skutečným kódem z mikroslužby ordering business v eShopOnContainers, zatímco ostatní dvě jsou jen dvě další ukázky.

Provádění IHostedService úloh na pozadí je koordinované s životností aplikace (hostitel nebo mikroslužba, pro tuto záležitost). Když se aplikace spustí, zaregistrujete úlohy a máte možnost provést nějakou elegantní akci nebo vyčistit, když se aplikace vypne.

Bez použití IHostedServicebyste mohli vždy spustit vlákno na pozadí ke spuštění libovolné úlohy. Rozdíl je přesně v době vypnutí aplikace, kdy by se toto vlákno jednoduše zabilo, aniž by bylo možné spouštět řádné akce čištění.

Rozhraní IHostedService

Při registraci IHostedServicerozhraní .NET volá a metody StartAsync() vašeho IHostedService typu během spuštění a StopAsync() zastavení aplikace. Další podrobnosti najdete v tématu Rozhraní IHostedService.

Jak si můžete představit, můžete vytvořit více implementací IHostedService a zaregistrovat každý z nich v Program.cs, jak je znázorněno dříve. Všechny hostované služby se spustí a zastaví spolu s aplikací nebo mikroslužbou.

Jako vývojář zodpovídáte za zpracování akce zastavení služeb při StopAsync() aktivaci metody hostitelem.

Implementace IHostedService s vlastní hostovaným třídou služby odvozenou ze základní třídy BackgroundService

Můžete pokračovat a vytvořit vlastní hostované třídy služby od začátku a implementovat IHostedService, jak je potřeba udělat při použití .NET Core 2.0 a novější.

Vzhledem k tomu, že většina úloh na pozadí bude mít podobné potřeby týkající se správy tokenů zrušení a dalších typických operací, existuje praktická abstraktní základní třída, ze které můžete odvodit název BackgroundService (k dispozici od .NET Core 2.1).

Tato třída poskytuje hlavní práci potřebnou k nastavení úlohy na pozadí.

Dalším kódem je abstraktní základní třída BackgroundService, jak je implementována v .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();
    }
}

Při odvození z předchozí abstraktní základní třídy, díky této zděděné implementaci stačí implementovat metodu ExecuteAsync() ve vlastní hostované třídě služby, stejně jako v následujícím zjednodušeném kódu z eShopOnContainers, který dotazuje databázi a publikování událostí integrace do služby Event Bus v případě potřeby.

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

    .../...
}

V tomto konkrétním případě pro eShopOnContainers se provádí metoda aplikace, která dotazuje tabulku databáze, která hledá objednávky s konkrétním stavem a při použití změn, publikuje události integrace prostřednictvím sběrnice událostí (pod ní může být použití RabbitMQ nebo Azure Service Bus).

Samozřejmě můžete místo toho spustit jakoukoli jinou úlohu na pozadí firmy.

Ve výchozím nastavení je token zrušení nastaven s vypršením časového limitu 5 sekund, i když tuto hodnotu můžete změnit při sestavování WebHost pomocí UseShutdownTimeout rozšíření IWebHostBuilder. To znamená, že se očekává, že naše služba bude zrušena do 5 sekund, jinak dojde k náhlému zabití.

Následující kód by tento čas změnil na 10 sekund.

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

Diagram souhrnné třídy

Následující obrázek znázorňuje vizuální souhrn tříd a rozhraní zahrnutých při implementaci IHostedServices.

Diagram showing that IWebHost and IHost can host many services.

Obrázek 6–27 Diagram tříd znázorňující více tříd a rozhraní souvisejících s IHostedService

Diagram tříd: IWebHost a IHost mohou hostovat mnoho služeb, které dědí z BackgroundService, které implementují IHostedService.

Aspekty nasazení a poznatky

Je důležité si uvědomit, že způsob nasazení ASP.NET Core WebHost nebo .NET Host může mít vliv na konečné řešení. Pokud například nasadíte WebHost službu IIS nebo běžnou službu Aplikace Azure, můžete hostitele vypnout z důvodu recyklace fondu aplikací. Pokud ale nasazujete hostitele jako kontejner do orchestrátoru, jako je Kubernetes, můžete řídit ujištěný počet živých instancí hostitele. Kromě toho byste mohli zvážit i další přístupy v cloudu, zejména pro tyto scénáře, jako je Azure Functions. A konečně, pokud potřebujete, aby služba běžela neustále a nasazujete na Windows Server, můžete použít službu systému Windows.

I když je nasazený WebHost do fondu aplikací, existují scénáře, jako je opětovné naplnění nebo vyprázdnění mezipaměti aplikace v paměti, které by se stále daly použít.

Rozhraní IHostedService poskytuje pohodlný způsob, jak spustit úlohy na pozadí ve webové aplikaci ASP.NET Core (v .NET Core 2.0 a novějších verzích) nebo v jakémkoli procesu nebo hostiteli (počínaje verzí .NET Core 2.1 s IHost). Její hlavní výhodou je příležitost, kterou získáte se zrušením odkladu, abyste vyčistili kód úloh na pozadí, když se samotný hostitel vypne.

Další materiály