Поделиться через


Реализация фоновых задач в микрослужбах с помощью IHostedService и класса BackgroundService

Подсказка

Это фрагмент из электронной книги «Архитектура микрослужб .NET для контейнеризованных приложений .NET», доступной в документации .NET или в виде бесплатного скачиваемого PDF-файла, который можно прочитать в автономном режиме.

Архитектура микросервисов .NET для приложений .NET в контейнерах, миниатюра обложки электронной книги.

Фоновые задачи и запланированные задания — это то, что может потребоваться использовать в любом приложении, независимо от того, соответствует ли он шаблону архитектуры микрослужб. Разница в использовании архитектуры микрослужб заключается в том, что вы можете реализовать фоновую задачу в отдельном процессе или контейнере для размещения, чтобы уменьшить или увеличить масштаб в зависимости от необходимости.

С универсальной точки зрения в .NET мы назвали эти типы задач размещенными службами, так как они являются службами или логикой, размещенными в узле, приложении или микрослужбе. Обратите внимание, что в этом случае служба-хост просто означает класс с логикой фоновых задач.

С версии .NET Core 2.0, платформа предоставляет новый интерфейс под названием IHostedService, который помогает легко реализовать размещенные службы. Основная идея заключается в том, что можно зарегистрировать несколько фоновых задач (хостинговых служб), которые запускаются в фоновом режиме, пока работает ваш хост, как показано на изображении 6-26.

Схема сравнения ASP.NET Core IWebHost и IHost .NET Core.

Рис. 6–26. Использование IHostedService в WebHost и Host

ASP.NET Core 1.x и 2.x поддерживают IWebHost для фоновых процессов в веб-приложениях. .NET Core 2.1 и более поздние версии поддерживают IHost для фоновых процессов с простыми консольными приложениями. Обратите внимание на разницу между WebHost и Host.

(базовый WebHost класс, реализующий IWebHost) в ASP.NET Core 2.0 — это артефакт инфраструктуры, используемый для предоставления функций HTTP-сервера в процесс, например при реализации веб-приложения MVC или службы веб-API. Он предоставляет все новые возможности инфраструктуры в ASP.NET Core, позволяя использовать внедрение зависимостей, добавлять промежуточное программное обеспечение в конвейер запросов и выполнять подобные задачи. WebHost использует эти же самые IHostedServices для фоновых задач.

В .NET Core 2.1 был введён базовый класс Host, реализующий IHost. В основном, Host позволяет вам иметь аналогичную инфраструктуру, как у вас с WebHost (передача зависимостей, размещенные службы и т. д.), но в этом случае вы хотите иметь простой и легкий процесс в качестве хоста, ничем не связанный с MVC, веб-API или HTTP-сервером.

Поэтому можно выбрать и создать специализированный процесс узла для IHost обработки размещенных служб и ничего другого, например микрослужбу, созданную только для размещения IHostedServices, или можно также расширить существующий ASP.NET Core WebHost, например существующее веб-API ASP.NET Core или приложение MVC.

Каждый подход имеет преимущества и недостатки в зависимости от потребностей бизнеса и масштабируемости. В основном это то, что если фоновые задачи не имеют ничего общего с HTTP (IWebHost) следует использовать IHost.

Регистрация размещенных служб в веб-хостинге или хосте

Давайте подробно рассмотрим интерфейс IHostedService, так как его использование довольно похоже на WebHost или Host.

SignalR является одним из примеров компонента, использующего размещённые службы, но его также можно использовать для гораздо более простых задач, таких как:

  • Задача в фоновом режиме опрашивает базу данных в поисках изменений.
  • Запланированная задача периодически обновляет некоторые кэши.
  • Реализация компонента QueueBackgroundWorkItem, позволяющая выполнить задачу в фоновом потоке.
  • Обработка сообщений из очереди сообщений в фоновом режиме веб-приложения при совместном использовании общих служб, таких как ILogger.
  • Фоновая задача запущена с Task.Run().

Вы можете просто выгрузить любое из этих действий в фоновую задачу, которая реализует IHostedService.

Способ добавления одного или нескольких IHostedServices в ваш WebHost или Host путем их регистрации с помощью метода расширения AddHostedService в WebHost ASP.NET Core (или в Host для .NET Core 2.1 и выше). По сути, необходимо зарегистрировать размещенные службы в Program.cs при запуске приложения.

//Other DI registrations;

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

В этом коде размещенная GracePeriodManagerService служба представляет собой реальный код микрослужбы заказов в eShopOnContainers, а остальные два — всего два дополнительных примера.

Выполнение IHostedService фоновой задачи координируется со временем существования приложения (узла или микрослужбы). Вы регистрируете задачи при запуске приложения, и у вас есть возможность выполнить некоторые изящные действия или очистку при завершении работы приложения.

Без использования IHostedServiceвсегда можно запустить фоновый поток для выполнения любой задачи. Разница заключается именно во времени завершения работы приложения, когда этот поток просто будет завершен, не имея возможности выполнить аккуратные действия по очистке.

Интерфейс IHostedService

При регистрации IHostedService, .NET вызывает методы StartAsync() и StopAsync() вашего типа IHostedService во время запуска и остановки приложения соответственно. Дополнительные сведения см. в интерфейсе IHostedService.

Как вы можете себе представить, можно создать несколько реализаций IHostedService и зарегистрировать каждую из них в Program.cs, как показано ранее. Все размещенные службы будут запущены и остановлены вместе с приложением или микрослужбой.

Как разработчик, вы несете ответственность за обработку действия остановки своих служб, когда StopAsync() метод активируется хостом.

Реализация IHostedService с пользовательским классом хост-сервиса, который наследует от базового класса BackgroundService

Вы можете идти вперед и создать пользовательский класс размещенной службы с нуля и реализовать IHostedService, как необходимо сделать при использовании .NET Core 2.0 и более поздних версий.

Однако, так как большинство фоновых задач будут иметь аналогичные потребности в отношении управления маркерами отмены и других типичных операций, существует удобный абстрактный базовый класс, от который можно наследовать (доступно с BackgroundService .NET Core 2.1).

Этот класс предоставляет основную работу, необходимую для настройки фоновой задачи.

Следующий код — абстрактный базовый класс BackgroundService, реализованный в .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();
    }
}

При создании производных классов из предыдущего абстрактного базового класса благодаря наследуемой реализации необходимо просто реализовать метод ExecuteAsync() в собственном пользовательском классе расположенного сервиса, как в следующем упрощенном коде из eShopOnContainers, который опрашивает базу данных и публикует интеграционные события в шину событий по мере необходимости.

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

    .../...
}

В этом конкретном случае для eShopOnContainers выполняется метод приложения, который запрашивает таблицу базы данных для поиска заказов с определенным состоянием и при применении изменений, он публикует события интеграции через шину событий (под ним можно использовать RabbitMQ или служебную шину Azure).

Конечно, можно запустить любую другую фоновую задачу, связанную с бизнесом.

По умолчанию токен отмены устанавливается с временем ожидания в 5 секунд, хотя вы можете изменить это значение при создании WebHost с использованием расширения UseShutdownTimeout для IWebHostBuilder. Это означает, что ожидается завершение работы нашей службы в течение 5 секунд, в противном случае она будет внезапно прекращена.

Следующий код будет изменять это время на 10 секунд.

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

Схема сводных классов

На следующем рисунке показана визуальная сводка классов и интерфейсов, участвующих при реализации IHostedServices.

Схема, показывающая, что IWebHost и IHost могут размещать множество служб.

Рис. 6–27. Схема классов с несколькими классами и интерфейсами, связанными с IHostedService

Схема классов: IWebHost и IHost могут размещать множество служб, которые наследуются от BackgroundService, которая реализует IHostedService.

Рекомендации по развертыванию и основные выводы

Важно отметить, что способ развертывания ASP.NET Core WebHost или .NET Host может повлиять на окончательное решение. Например, если вы развернете WebHost в IIS или обычной службе приложений Azure, ваш хост может быть отключен из-за перезапуска пула приложений. Но если вы размещаете хост в качестве контейнера в оркестраторе, например Kubernetes, вы можете контролировать обеспеченное количество активных экземпляров вашего хоста. Кроме того, вы можете рассмотреть другие подходы в облаке, специально разработанные для этих сценариев, такие как функции Azure. Наконец, если требуется, чтобы служба выполнялось все время и развертывается на Windows Server, можно использовать службу Windows.

Но даже для WebHost, развернутого в пуле приложений, существуют сценарии, такие как восстановление или очистка кэша приложения в памяти, которые все еще актуальны.

Интерфейс IHostedService предоставляет удобный способ запуска фоновых задач в веб-приложении ASP.NET Core (в .NET Core 2.0 и более поздних версиях) или в любом процессе или узле (начиная с .NET Core 2.1 с IHost). Основное преимущество заключается в том, что плавная отмена позволяет очистить код фоновых задач, когда хост сам завершает работу.

Дополнительные ресурсы