LINUX.ORG.RU

Вопрос про микросервисную архитектуру

 , ,


0

2

Я с микросервисами сталкивался мало, но у меня давно гложил такой момент о котором я сейчас вспомнил и решил задать про него вопрос экспертам по микросервисам с ЛОРа.

Допустим у вас есть микросервисное приложение. За внешнее взаимодействие с клиентами отвечает сервис с обработчиками запросов. Он принимает сообщения от клиентов и направляет их обработку целевым сервисам которые должны будут осуществить работу и сообщить о результате. Коммуникация между микросервисами происходит через Kafka.

Внимание вопрос, если у вас микросервис принимающий запросы от клиента динамически масштабирующий (т.е. в зависимости от загрузки добавляются/убираются его инстансы) то как поступать с SSE или Websocket соединениями, когда клиент подключившись к определенному инстансу ожидает ответ обработки? Т.е. результат работы целевого микросервиса должен быть доставлен на конкретный инстанс где висит соединение с клиентом.

Как в этом случае адресуют конкретный инстанс? Как именно в него доставить результат, а не случайному инстансу подписанному на получение событий из очереди сообщений?

P.S. Порекомендуйте литературу где, в том числе, есть ответ и на мой вопрос.

★★★★★

Коммуникация между микросервисами происходит через Kafka.

P.S. Порекомендуйте литературу где, в том числе, есть ответ и на мой вопрос.

Документация Kafka.

raspopov
()

Как в этом случае адресуют конкретный инстанс? Как именно в него доставить результат, а не случайному инстансу подписанному на получение событий из очереди сообщений?

Это вряд ли будет случайный инстанс (кафка таки не месседж брокер), а один, который получит все сообщения.

Чтобы оно работало как ты хочешь есть два варианта. Первый - каждый инстанс садится на свой топик и каждое сообщение для которого ожидается ответ маркается именем этого топика, чтобы ответ кидали куда нужно. Второй - сделать доставку сообщения всем (емнип в кафке для этого надо сделать много хранилищ внутри одного топика) и фильтроаать ответы уже на клиенте.

ya-betmen ★★★★★
()
Ответ на: комментарий от ya-betmen

Для стабильной доставки нужному экземпляру сервиса юзается partition key.

А вот что делать, если клиент отвалится и подключится к другому инстансу фронта - вопрос более эротический.

anonymous
()
Ответ на: комментарий от ya-betmen

Как я хочу? А это разве не типовая ситуация? Ну типа стандартный подход который всем нужен.

Я открыл доку, понял про partition, про consumer group.

Вот какой-то кусок кода нашел, на зловещем JavaScript, но это ладно:

const consumer = new Kafka.KafkaConsumer({
  'group.id': 'kafka-consumer',
  'group.instance.id': 'kafka-consumer-<unique-instance-id>',
  'partition.assignment.strategy': 'range',
  'metadata.broker.list': 'localhost:9092',
}, {});

Вижу что тут динамически создают группу и в имя группы добавили инедтификатор инстанса.

Значит в случае какого-нибудь kubernates надо передать через переменную окружения POD_NAME и использовать его для создания consumer_group, после чего этот consumer_group передавать в сообщении к микросервису от которого ожидается ответ. Когда ответ будет готов микросервис отправит ответ используя consumer-group из полученного запроса.

Так?


Хот я тут какую-то чушь наверное написал, я пока не разобрался что такое group.instance.id и как оно фактически работает.

Aber ★★★★★
() автор топика
Последнее исправление: Aber (всего исправлений: 2)
Ответ на: комментарий от anonymous

Ну тут вроде как очевидно, все инстансы используют какой-нибудь хранилище, или настоящую субд как single point of truth, т.е. все состояния операций сохранять туда.

Если клиент пере-подключился то поискать там все что было запрошено и все результаты работы. Хотя да, результат то может быть еще не пришел. Тогда не знаю что делать :) Не опрашивать же с периодичностью N секунд ? или опрашивать? :)


Хотя может быть лучше тогда исходный сервис научить отвечать повторяющимися ответами на одинаковые запросы, т.е. если результат уже был получен для запроса, но пришел точно такой-же запрос опять то просто отдать готовый ответ без выполнения работы.

Таки-да, мне так нравится. Т.е. микросервис встречающий запросы клиента сохраняет статус запроса и его идентификатор (какой-нибудь случайный UID) в свое хранилище, ждет ответ от сервиса. Клиент отваливается и переподключается к другому инстансу, то другой инстанс поднимает из хранилища последнее состояние – запрос с его уникальным идентификатором, инстанс отправляет сообщение вновь и исходный микросервис должен просто ответить результатом не выполняя какую-либо работу, потому что идентификатор запроса не поменялся.

Aber ★★★★★
() автор топика
Последнее исправление: Aber (всего исправлений: 2)
Ответ на: комментарий от Aber

Если я правильно понял, то у тебя ответ должен прийти в конкретный конзюмер, а не просто в группу, не?

А это разве не типовая ситуация?

Она конечно типовая, но это если в детали не смотреть. Например у кого-то повторное получение сообщеньки это просто дубль в чятике, а у кого-то это двойная отправка платежа. Или при падении ноды кто-то может просто сказать юзеру "перелогинься, чувак", а кому-то надо держать на подхвате второй инстанс и перекидывать туда без обрыва сессии. И т.п.

ya-betmen ★★★★★
()
Ответ на: комментарий от ya-betmen

Я думал создать группы по количеству инстанстов, типа N групп на N инстанстов, и связать из 1 к 1 :) Я пока не разобрался, потому пишу наверное чушь.

Хотя конечно тогда вопрос а зачем тут kafka, если все можно делать прямыми запросами gRPC :)

Aber ★★★★★
() автор топика
Последнее исправление: Aber (всего исправлений: 1)
Ответ на: комментарий от anonymous

Было бы круто если кто-то кинул поваренную книгу микроанархиста, где были бы типовые рецепты мекросервисаварения, что-нибудь хозяйкам на заметку чтоб самому не придумывать невкусные велосипеды.

Aber ★★★★★
() автор топика
Ответ на: комментарий от Aber

Хотя конечно тогда вопрос а зачем тут kafka, если все можно делать прямыми запросами gRPC :)

Вопрос "зачем тут кафка?" имеет смысл задавать почти всегда когда собираешь её использовать. Почти всегда ответ будет "нафиг не нужна".

Ответ "хочу галочку в резюме" тоже принимается.

ya-betmen ★★★★★
()
Последнее исправление: ya-betmen (всего исправлений: 1)
Ответ на: комментарий от ya-betmen

Да нафиг она мне нужна, работодатели требуют. Скачал какую-то Microservices Patterns With examples in Java by Chris Richardson, выглядит скучно. Хотелось бы что-то вроде GoF для микросервисов.

Aber ★★★★★
() автор топика

это называется обогащением или асинхронный RequestReply паттерн. тебе нужно научиться ловить именно свои сообщения, а отсекать чужие.

т.е. твой кейс.

  1. браузер/мобилка сидит на инстансе А вебсокетом, он формирует ключ ID1, который отправляется в партицию 2 топика «запросы».

  2. на «той стороне» есть процессоры твоего запроса. они должны считать ID1 из ключа твоего сообщения и не «просрать» его, а отправить результат ровно с таким же ID1 тебе.

  3. твой инстанс А должен быть подключен к партиции 2 - консюмеры в кафке работают в консюмер-группах, поэтому ты делаешь каждому инстансу свою консюмер-группу, тогда он точно будет подключен к партиции 2 (ну и к другим тоже, но это не важно).

  4. когда получаешь сообщение - отправляешь его ровно в тот вебсокет, из которого пришло сообщение с ID1 (да, тебе нужно где-то хранить соответствие)

мой опыт https://blog.bvn13.me/2022/08/13/request-reply-pattern-using-apache-kafka-or-how-not-to-loose-your-data.html

bvn13 ★★★★★
()
Последнее исправление: bvn13 (всего исправлений: 1)
Ответ на: комментарий от anonymous

ретраи :) все корнер-кейсы должны быть заложены в архитектуру.

например, фронт при подключении должен «выбрать» именно конкретный (по хешу) бэк, к которому он подключался в прошлый раз.

или ответы прихранивать в БД, и отдавать при переподключении «к любому другому» бэкенду.

вариантов несколько.

bvn13 ★★★★★
()
Ответ на: комментарий от Aber

Читай, читай. Возможно заказчик хочет асихронную архитектуру, а для этого и обмен через кафку, а не через grpc. Там про это есть

cobold ★★★★★
()
Последнее исправление: cobold (всего исправлений: 1)
Ответ на: комментарий от cobold

Возможно заказчик хочет асихронную архитектуру

Асинхронщина разная бывает, даже без очередей это реализуется при помощи разделения эндпоинтов на отправку запроса на обработку/отправку запроса на получение результата или отправку запроса на обработку/коллбэк с результатами. В случае наличия очередей просто упрощается масштабирование/повышается надежность/снижаются накладные расходы/снижается задержка получения результата/упрощается параллельная обработка одного сообщения.

anonymous
()

Сама постановка задачи неверная. У тебя не должно быть особого инстанса для конкретного клиента. Все инстансы должны быть способны обслужить любого клиента. Вебсокет должен мочь подключаться к любому серверу.

В целом лучше архитектуру планировать исходя из повторяющихся GET запросов, а не вебсокетов. Так проще.

vbr ★★★★★
()
Последнее исправление: vbr (всего исправлений: 1)

результат работы целевого микросервиса должен быть доставлен на конкретный инстанс где висит соединение с клиентом

Инстанс в заголовок сообщения пихает уникальную консумер группу которую он слушает. Другие сервисы/инстансы отвечают в эту группу.

Можно также сделать на топиках, но это не так гибко и облака берут плату по топикам.

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 2)
Ответ на: комментарий от vbr

В целом лучше архитектуру планировать исходя из повторяющихся GET запросов, а не вебсокетов. Так проще.

А тогда CQRS как делать без подписки? Тупо долбить GET запросами в цикле?

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

Это не так работает, господи. Нет никакой потребности вычитывать и скипать не свои сообщения, это делается, если уж так хочется кафку без промежуточного хранилища юзать, правильным раскладыванием сообщений по партициям.

anonymous
()
Ответ на: комментарий от anonymous

Это не так работает, господи

Работает, холоп.

раскладыванием сообщений по партициям

Это тут вообще никаким боком не влияет.

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)
Ответ на: комментарий от no-such-file

ищо раз: отказываясь от хоть какого-то роутинга сообщений через партиции и играя через создание личных консьюмеров, вы, во-первых, получаете квадратичное увеличение нагрузки на кафку при увеличении трафика, и, во-вторых, убиваете весь смысл масштабирования, потому что каждый фронт в итоге получает все сообщения, забивая себе канал.

Вообще, ситуация выглядит как «если нужно объяснять, значит, не нужно объяснять».

anonymous
()
Ответ на: комментарий от anonymous

отказываясь от хоть какого-то роутинга сообщений через партиции

Ты экономишь сотни денег.

убиваете весь смысл масштабирования

ТСу не нужно никакое мегамасштабирование. Ему и кафка-то не нужна по сути. Но раз уж надо как-то прикрутить, то так это будет работать.

no-such-file ★★★★★
()
Ответ на: комментарий от bvn13

Каждому инстансу по группе!

Это был отличный ответ, сегодня начитался блогов найденным по упомянутым кейвордам и всему что у тебя написано в блоге, завтра буду тыкать кафку :)

Я уже с ней сталкивался, но глубоко не копал, тыкал её мимоходом как какую-то очередную MQ, теперь буду погружаться по полной.

Aber ★★★★★
() автор топика
Ответ на: комментарий от anonymous

отказываясь от хоть какого-то роутинга сообщений через партиции и играя через создание личных консьюмеров,

Да там же каждое добавление/убавление партиций это stop-the-world пауза в группе. А завести группы ничего не стоит и любой группе можно отправить личное сообщение по идентификатору, я так понял. Завтра проверю.

Aber ★★★★★
() автор топика
Последнее исправление: Aber (всего исправлений: 1)
Ответ на: комментарий от Aber

любой группе можно отправить личное сообщение по идентификатору, я так понял Группы просто позволяют обрабатывать одно сообщение одновременно множеством групп потребителей. В твоем случае все инстансы получат сообщение «одновременно» и зная какие клиенты к ним подключены отфильтруют лишнее.

anonymous
()
Ответ на: комментарий от no-such-file

Клиент подключается к одному из серверов, передаёт ему идентификатор, который клиент хочет слушать. Сервер подключается к твоей базе данных вроде кафки и проксирует события клиенту. Сервер может быть любым, он просто прокси между клиентом и базой, проверяющий авторизацию и прочее. В каких-то случаях можно вообще готовый софт поставить для конкретно этой задачи.

По сути твоя задача заключается в том, чтобы все сложные решения переложить на кафку, например. Т.к. её писали умные люди и она написана условно говоря хорошо, скорей всего другие умные люди её настроили и запустили в облаке для тебя. А тебе остаётся только написать нужные прокладочки, добавляющие какие-то мелочи.

Самое главное это не начинать изобретать сложные архитектурно и алгоритмически штуки. Вся суть хорошего серверного софта в том, чтобы у него не было состояния. Чтобы любой твой сервис мог упасть и этого никто не заметил (в худшем случае несколько клиентов увидят 5xx и сделают запрос ещё раз). А состояние должно лежать в хороших базах.

Конечно кому-то и кафку надо изобретать, но, полагаю, в этой теме обсуждается не тот случай.

vbr ★★★★★
()
Последнее исправление: vbr (всего исправлений: 1)
Ответ на: комментарий от no-such-file

Ты экономишь сотни денег.

Нет, ты просто делаешь неработоспособную систему.

Но раз уж надо как-то прикрутить, то так это будет работать.

Зачем давать заведомо некорректные советы и оправдывать это тем, что у ТСа и так всё через жопу?

anonymous
()
Ответ на: комментарий от Aber

Да там же каждое добавление/убавление партиций это stop-the-world пауза в группе.

Там всё подряд stop-the-world, включая подключение новых консьюмеров. К группе это, кстати, вообше отношения не имеет.

Ну и как бы с чего вы взяли, что нужно динамически создавать партиции под инстансы фронта? Это безумие.

anonymous
()
Ответ на: комментарий от Aber

Это был отличный ответ Это была просто глупость от человека, который кафку только на ютубе видел и даже терминологией не владеет.

Ваша ситуация разруливается через отправку ответа в определённую партицию. Это не велосипед, это стандартная практика.

https://docs.spring.io/spring-kafka/reference/kafka/sending-messages.html#replying-template

anonymous
()
Ответ на: комментарий от anonymous

Там всё подряд stop-the-world, включая подключение новых консьюмеров

Почему? Добавление партиции требует ребаланс. А в случае добавления очередного слушателя в новой группе другие группы не затронуты, никакого ребаланса. Не так?

Ну и как бы с чего вы взяли, что нужно динамически создавать партиции под инстансы фронта? Это безумие.

Ну так динамический скейлниг при нагрузке больше обработчиков запросов, при отсутствии меньше.

Aber ★★★★★
() автор топика
Ответ на: комментарий от anonymous

включая подключение новых консьюмеров.

Из официальной доки:

Conceptually you can think of a consumer group as being a single logical subscriber that happens to be made up of multiple processes. As a multi-subscriber system, Kafka naturally supports having any number of consumer groups for a given topic without duplicating data (additional consumers are actually quite cheap).

  1. Consumer Group -> single logical subscriber
  2. Kafka naturally supports having any number of consumer groups
  3. consumers are ... cheap
Aber ★★★★★
() автор топика
Последнее исправление: Aber (всего исправлений: 2)
Ответ на: комментарий от anonymous

Я видел такие решения. Короче я понял что есть несколько подходов, но с партициями страшнее :) Пока огромные нагрузки не грозят я смотреть в их сторону не буду.

Aber ★★★★★
() автор топика

то как поступать с SSE или Websocket соединениями, когда клиент подключившись к определенному инстансу ожидает ответ обработки

Делаешь broadcast через пабсаб/очередь/базу, все инстансы-апишки его слушают, смотрят кому он адресован (кидаешь в броадкасте id юзера или что-то такое), смотрят подключен ли к ним такой

Но вообще когда видишь микросервисы первый вопрос должен быть не как, а нахрена. Геморроя много, а плюсы есть далеко не всегда

upcFrost ★★★★★
()
Последнее исправление: upcFrost (всего исправлений: 1)