Занимательная задача с микросервисами в .NET

Необходимо реализовать возможность приёма транзакций от разных клиентов для процессинга и дальнейшей отправки в разные банки. Клиенты могут присылать как равномерно (например, по 1шт в секунду), так и большими пачками (1000шт каждый час или 10000 раз в день), банки обрабатывают транзакции последовательно.

Для простоты интеграции необходимо использовать REST API для приёма транзакций

Необходимо исключить ситуации, когда:

Тысячи клиентов по несколько транзакции ожидают двух, у которых по 10000 транзакций

Несколько клиентов по 10000 ждут тысячи клиентов по несколько транзакций.

Ок, нужно реализовать сервис который обрабатывает транзакции

Первым делом, для клиентов у которых меньше 100 транзакций в запросе использовать отдельный инстанс сервиса, масштабировать горизонтально.

[Route("api/[controller]")]
[ApiController]
public class ProcessController : ControllerBase
{
    [HttpPost("Queue")]
    public async Task Queue([FromBody] RequestDTO request)
    {
        // Что-то вроде балансировки нагрузки
        var requestUri = request.TransactionsCount < 100
            ? $"http://instance1:81/api/Process/Queue"
            : $"http://instance2:81/api/Process/Queue";

        HttpClient client = new HttpClient();
        var result = await client.PostAsJsonAsync(requestUri, request); 
        var content = await result.Content.ReadAsStringAsync();
        return Ok(content);
    }
}

Да, но в случае, когда есть 3 клиента (Клиент 1: 10000 транзакций), (Клиент 2: 1000 транзакций), (Клиент 3: 100 транзакций) третий клиент будет ожидать пока выполнятся все 11000 транзакций первых двух клиентов

Может обрабатывать сперва запросы у которых меньше транзакций?

Тогда в случае, когда 1000 клиентов (Клиент 1: 10000 транзакций), (Клиент 2–500: 1000 транзакций), (Клиент 501 — 1000: 100 транзакций), Клиент 1 будет ожидать пока обработаются все мелкие запросы. Вариант увеличить мощность машины в котором инстанс для больших запросов, масштабировав вертикально тоже отметается.

Тогда придется добавить все запросы в очередь (или группировать по клиентам, или по другому параметру) и выполнять транзакции по очереди с каждого запроса. Это, по моему мнению, самый справедливый вариант для клиентов, так как клиент не просто ждет пока дойдет его очередь, а постепенно получает callback от выполненных транзакций

if (Requests.Any())
{
    // С каждого запроса выполняем одну транзакцию
    List tasks = new List();
    foreach(var request in Requests.ToList())
    {
        // имитируем обработку транзакции
        tasks.Add(TransactionProcess());
        request.TransactionsCount--;
        // имитируем callback
        logger.LogCritical($"[Client: {request.Client}] left {request.TransactionsCount} transactions");
        if(request.TransactionsCount == 0)
        {
            // Если все транзакции клиента выполнены, удаляем запрос из очереди
            Requests.Remove(request);
        }
    }
    // Выполняем транзакции одного цикла параллельно
    await Task.WhenAll(tasks);
}

3 запроса по 1, 3 и 6 транзакций и порядок их выполнения

3 запроса по 1, 3 и 6 транзакций и порядок их выполнения

А потом пакуем приложение с помощью docker compose. Результат тут

Тут мы можем увидеть, что для каждого клиента выполняется по одной транзакции:

3 клиента по 10000, 1000 и 500 транзакций в одном запросе в контейнере instance2-1

3 клиента по 10000, 1000 и 500 транзакций в одном запросе в контейнере instance2–1

А тут, то что «Load balancer» перенаправляет запросы с менее 100 транзакциями в первый инстанс:

Запрос четвертого клиента, у которого всего 90 транзакций (меньше 100) обрабатывается в отдельном контейнере instance1-1

Запрос четвертого клиента, у которого всего 90 транзакций (меньше 100) обрабатывается в отдельном контейнере instance1–1

Предлагайте как еще можно улучшить решение. Также предлагайте другие задачи на тему микросервисов

© Habrahabr.ru