Files
parr_api/PARR.SnapshotWorker/Worker.cs

115 lines
5.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using PARR.Core.Common.Interfaces;
using PARR.Core.Services.Snapshots.Interfaces;
namespace PARR.SnapshotWorker
{
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
private readonly IIntervalService _intervalService;
private readonly IServiceProvider _serviceProvider;
// Хранилище времени СЛЕДУЮЩЕГО запуска для каждого сервиса
private readonly Dictionary<string, DateTimeOffset> _nextRunTimers = new();
// День последней успешной очистки базы данных
private int _lastCleanupDay = -1;
// Интервал, должен быть меньше минуты, чтобы попадать во все возможные интервалы
TimeSpan tickInterval = TimeSpan.FromSeconds(25);
public Worker(
ILogger<Worker> logger,
IIntervalService intervalService,
IServiceProvider serviceProvider
)
{
_logger = logger;
_intervalService = intervalService;
_serviceProvider = serviceProvider;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Запуск фонового сервиса создания системных снапшотов");
await _intervalService.IntervalInitAsync(async () =>
{
var now = DateTimeOffset.UtcNow;
using (var scope = _serviceProvider.CreateScope())
{
// Собираем абсолютно все зарегистрированные сервисы снапшотов
var providers = scope.ServiceProvider.GetServices<ISnapshotProvider>();
#region Параллельный сбор снапшотов
var tasksToRun = new List<Task>();
foreach (var provider in providers)
{
var providerKey = provider.GetType().FullName!;
// Если сервис видим впервые, планируем его первый старт прямо сейчас
if (!_nextRunTimers.ContainsKey(providerKey))
{
_nextRunTimers[providerKey] = now;
}
// Если текущее время добежало до запланированного «будильника»
if (now >= _nextRunTimers[providerKey])
{
// СРАЗУ планируем следующий старт, чтобы сетка времени не съезжала
// из-за времени выполнения самого метода
_nextRunTimers[providerKey] = now.Add(provider.Interval);
// Добавляем таску в список для параллельного выполнения (без await!)
tasksToRun.Add(ExecuteSafelyAsync(provider, () => provider.TakeSnapshotAsync(stoppingToken), "Создание снапшота"));
}
}
// Запускаем все готовые снапшоты ОДНОВРЕМЕННО
if (tasksToRun.Any())
{
await Task.WhenAll(tasksToRun);
}
#endregion
#region Ежедневная очистка БД
// Если наступили новые сутки в формате UTC — запускаем ротацию старых данных
if (now.Day != _lastCleanupDay)
{
_logger.LogInformation("Наступили новые сутки. Запуск процесса очистки устаревших снапшотов...");
foreach (var provider in providers)
{
await ExecuteSafelyAsync(provider, () => provider.CleanUpOldSnapshotsAsync(stoppingToken), "Очистка старых данных");
}
// Запоминаем, что за сегодня очистку уже провели успешно
_lastCleanupDay = now.Day;
}
#endregion
}
}, tickInterval);
}
private async Task ExecuteSafelyAsync(ISnapshotProvider provider, Func<Task> action, string operationName)
{
try
{
await action();
}
catch (Exception ex)
{
_logger.LogError(ex, "Критическая ошибка во время операции '{Operation}' в сервисе {ProviderName}", operationName, provider.GetType().Name);
}
}
}
}