Занимательная задача с микросервисами в .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 транзакций и порядок их выполнения
А потом пакуем приложение с помощью docker compose. Результат тут
Тут мы можем увидеть, что для каждого клиента выполняется по одной транзакции:
3 клиента по 10000, 1000 и 500 транзакций в одном запросе в контейнере instance2–1
А тут, то что «Load balancer» перенаправляет запросы с менее 100 транзакциями в первый инстанс:
Запрос четвертого клиента, у которого всего 90 транзакций (меньше 100) обрабатывается в отдельном контейнере instance1–1
Предлагайте как еще можно улучшить решение. Также предлагайте другие задачи на тему микросервисов