Minimal Api как затормозить поток запросов?

В c# я не сильно давно, можно сказать, только познаю его. Однако, пытаюсь кой-чего реализовать в коде и возник один вопрос. Использую Minimal Api. Это там, где app.MapGet() и вот это вот всё. ну вроде такого:

app.MapGet("/stubs/handler_api.php", async (HttpRequest request, ActionHandler actionHandler) =>
{
    return await actionHandler.DoAction(request);
});

Внутри DoAction есть ветвление, в зависимости от разобранного запроса он может быть отправлен к местную базу данных на MariaDB (11.4), либо может быть переадресован на другой сервис через http post запрос. Перед тем как выполнить этот http запрос в другой сервис, я его предварительно "форматирую" согласно требований самого сервиса. Проблема возникает в тот момент, когда на наш сервис идёт большой наплыв запросов. скажем, 100 в секунду. Вроде не много, но для второго сервиса (он не наш) это много и они просят соблюдать рекомендованные паузы, которые они отправляют ответом. Я получаю эту паузу из их ответа, но, не очень понял, что делать с ней дальше. Попробовал сделать так:

private Logger logger;
private List<UserLimit> userLimitList;
private static readonly SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);
private readonly System.Timers.Timer cleanupTimer;

private readonly TimeSpan inactiveTimeout = TimeSpan.FromSeconds(1);

public LimitsHandler(Logger logger)
{
    this.logger = logger;
    userLimitList = new List<UserLimit>();

    cleanupTimer = new System.Timers.Timer(1000);   // Проверять каждую секунду
    cleanupTimer.Elapsed += DecrementLimitTimer;
    cleanupTimer.Start();
}

public async Task SetLimit(int countryId, string serviceId, int limitTime)
{
    try
    {
        if (await semaphore.WaitAsync(2000))
        {
            var limit = userLimitList
                .Where(ul => ul.CountryId == countryId && ul.ServiceId == serviceId).FirstOrDefault();

            if (limit == null)
            {
                userLimitList.Add(new UserLimit()
                {
                    CountryId = countryId,
                    ServiceId = serviceId,
                    LimitTime = limitTime + 1
                });
            }
            else
            {
                limit.LimitTime = limitTime + 1;    // Обновление счетчика
            }
        }
    }
    catch (Exception ex)
    {
        logger.Error($"[SetUserLimit] Failed to lock the resource for update. {ex.ToString()}");
    }
    finally
    {
        semaphore.Release();
    }
}

public async Task<bool> GetLimit(int countryId, string serviceId)
{
    bool isLockAcquired = false;

    try
    {
        isLockAcquired = await semaphore.WaitAsync(2000);
        if (isLockAcquired)
        {
            var limitExists = userLimitList
                .Any(ul => ul.CountryId == countryId && ul.ServiceId == serviceId);
            return limitExists;
        }
    }
    catch (Exception ex)
    {
        logger.Error($"[GetLimit] Failed to lock the resource for update. {ex}");
        return true; // true - значит лимит присутствует
    }
    finally
    {
        if (isLockAcquired) // Освобождаем семафор только если он был успешно захвачен
        {
            semaphore.Release();
        }
    }

    logger.Error("[GetLimit] Could not acquire the semaphore in the allotted time.");
    return true; // true - значит лимит присутствует
}

private void DecrementLimitTimer(object sender, ElapsedEventArgs e)
{
    lock (userLimitList)
    {
        for (int i = userLimitList.Count - 1; i >= 0; i--)
        {
            var limit = userLimitList[i];
            limit.LimitTime--;

            // Проверяем, если LimitTime достиг 0
            if (limit.LimitTime <= 0)
            {
                logger.Info($"[DecrementLimitTimer] Removing limit for CountryId: {limit.CountryId}, ServiceId: {limit.ServiceId}");
                userLimitList.RemoveAt(i);
            }
        }
    }
}

При входе в DoAction выполняется вызов GetLimits. При первом входе там пусто и запрос свободно пролетает на второй сервис. от туда я ловлю лимит и выставляю там же вызывая SetLimit(). При повторном обращении в рамках этого временного лимита всё ок, юзер ловит паузу. Но, когда она проходит, то снова вся пачка запросов летит на второй сервис. Т.е. иными словами, запросы от юзеров ходят пачками, а мне бы их как то разделить во времени, чтобы они в итоге на второй сервис заходили чуть чуть друг за другом. Попытка воткнуть внутри GetLimit() семафоры результата не дали, так же как и Lock() (с ним вообще тормоза начались).

Да, чуть не забыл. ActionHandler и класс содержащий DoAction в контейнере зарегистрированы как Scoped. Вот даже есть мысль, что DoAction должен быт ь как Singleton и на входе поставить семафора? не уверен.

Заранее спасибо за помощь!


Ответы (0 шт):