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

от автора

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

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

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

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

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


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

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

[Route("api/[controller]")] [ApiController] public class ProcessController : ControllerBase {     [HttpPost("Queue")]     public async Task<IActionResult> 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<Task> tasks = new List<Task>();     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

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


ссылка на оригинал статьи https://habr.com/ru/articles/768962/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *