Pull to refresh

Comments 34

Учитывая, что микросервисы у вас асинхронные, какое преимущество дает использование RabbitMQ в данном случае?

Бесшовный деплой и надежность, как минимум. Если микросервис отключится на небольшое время, тогда запросы останутся в очереди. Запросы, которые обрабатывались во время отключения микросервиса (но они не отправились в гейтвей), тоже останутся в очереди.

Как микросервис включится, клиент получит запрос, так как его запрос не потерялся нигде. Но это зависит от таймаута настроенного (по дефолту он 15 секунд).
Вариант, но бесшовность и надежность можно достигнуть и другими способами. Я предпочитаю blue/green посредством чего-то вроде Kubernetes+Helm. К тому же RabbitMQ и гейтвей тоже могут навернуться, резервирование наше все.
Я к тому, что из статьи не совсем очевидно, зачем очередь нужна. Я бы добавил пару предложений, объясняющих назначение RabbitMQ. А так же подчеркнул, что это только одно из возможных решений, чтобы начинающие разработчики не повторяли бездумно, а понимали что они делают и зачем.
Спасибо за комментарий, добавил в статью объяснение.
UFO just landed and posted this here

Как минимум — отложенность. Так же, вместо балансировки в через round-robin у нас получается более умная, которая учитывает загруженность сервисов.

Спасибо за статью. Какие новые ограничения накладывает архитектура при переходе с express? Будет ли ограничен размер запросов и ответов, возможен ли стриминг? Какова стратегия при сбое в обработчике запроса и можно ли ее кастомизировать?

Промахнулся с веткой комментариев, ответ вам написал ниже.
Ограничение запросов и ответов вы можете указать самостоятельно, если нужно. Возможно, эта фича будет в будущих обновлениях. Из ограничений это, конечно, использование только локальной базы данных. Теперь вы не можете, к примеру, получить баланс пользователя из микросервиса пользователей, отправив запрос к базе. Для этого вам придется отправлять запрос по рэббиту в микросервис баланса (см. метод ask).

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

Если я правильно вас понял про стратегию обработки ошибок, то можете посмотреть пример из документации по обработке ошибок. Миддлвары реализованы на промисах, поэтому можно удобно жонглировать ими, как вам удобно.
А в обратную сторону оно работает. Например, у меня часть бизнес-логики, которую я хочу вынести в микросервис завязана на Express. Как тогда поступить?
Объясните, пожалуйста, как именно она завязана на экспрессе? Что мешает вынесению в микросервис?
Ну так мы его вынесем в микросервис, просто на экспресс. Микросервис это же не про то, на чем написано, а про разделение ответственности и дефрагментацию. Завязано может быть как угодно, например, написана целая куча мидлваров + использование существующих мидлваров экспресса для работы.
Обычно, в микросервисной архитектуре пользователь отправляет запрос только на один домен, его принимает гейтвей, дальше отправляет микросервису через раббит, ждет ответа и отправляет его клиенту.

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

Если вы хотите на экспрессе строить микросервис, ставя его за гейтвей, тогда непонятен выбор именно экспресса. В библиотеки MicroMQ есть нужный роутинг (как в экспрессе), миддлвари на промисах (лучше, чем в эскпрессе).
Это не совсем так. Микросервиснаяя архитектура породила свою вторую часть это средства оркестрами kubernetis, nomad и т.п. Оркестраторы следят за роутингом, а также помогают с деплоями, а ещё делают проверку доступности микросервисов ответ случае его падения запускают реплики.
Единственная функциональная разница в решении с сообщениями которую в Вашей реализации это правило выбора реплики микросервисов. И.к. микроскруисов сам выбирает очередное событие то он естественно сделает это после своего освобождения. Хотя это тоже может быть не всегда оптимально. Я не знаю в подробностях Вашу реализацию. Но пусть мы допускаем что на реплика могут выполняться о одного до трёх параллельных запросов. Тогда уже не все так просто получается. Если например система загружена слабо то у нас может одна реплика выбрать три запроса а другая простаивать в это время. То есть проблема с дисциплиной выбора реплики не ушла а сместилась в другую плоскость.
Понял вас.

> система загружена слабо то у нас может одна реплика выбрать три запроса а другая простаивать в это время

Это может быть задано в логике гейтвея, написать веерную балансировку. Например, запускаем три микросервиса пользователей, очереди для запросов: users:requests-1, users:requests-2, users:requests-3. И, соответственно, отправляем первый запрос в первый микросервис, второй во второй, третий в третий, четвертый в первый и т.д.
Во-первых, совершенно не обязательно иметь гейтвей. Он просто делает работу клиентов удобнее и абстрагирует их от деталей реализации микросервисов.

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

Опять же сам транспорт между микросервисами не так важен с точки зрения архитектуры. Скажите пожалуйста, а можно ли как-то организовать общение между 2-ся гейтвеями в вашем решении? То есть, чтобы один гейтвей присоедененный к экспрессу делегировал запросы в другой гейтвей?
>на легаси проектах переписывать микросервисы на другой роутинг и мидлвары может оказаться дороже

Смотря на каких легаси, если это легаси на экспрессе, то никаких проблем не будет. Роутинг, миддлвари обратно совместимые в моей библиотеки с экспрессом.

>можно ли как-то организовать общение между 2-ся гейтвеями

Да, можно. Здесь неважно — гейтвей это или микросервис. Существует метод ask, который отправляет запрос в микросервис по реббиту. Можно сделать какую угодно цепочку делегирования.
А, ну вот это ровно то, что я хотел узнать. Спасибо!
Автор, если вы собираетесь делегировать полностью контроллеры, почему вы не хотите использовать nginx для этого? Или у вас в ТЗ была поставлена задача обрабатывать все запросы независимо от нестабильности бекенда? Или бекенд был настолько нестабилен что такое решение было самым правильным?
Мне всегда казалось что микросервисная архитектура отличается от монолита тем, как именно ты получаешь доступ к нужным данным. Т.е. ты выделяешь отдельные компоненты, пишешь API — которого тебе должно хватить, и используешь только его, без попыток «хакнуть внутрянку» и подробно документируя все составляющие твоего проекта. Но здесь же — просто балансировка. Подними с той стороны такой же монолит — и ничего не поменяется. Напиши перенаправление по всем контроллерам, подними нужное количество «микросервисов» ( в данном случае просто экземпляры вашего монолита ) — и получите «микросервисную архитектуру»?
Необязательно делегировать именно контроллеры, все зависит от бизнеса, с которым вы работаете. Монолит можно дробить по-разному.

Нджиниксом не проксируется потому, что иногда могут понадобятся какие-то данные (авторизации, к примеру). Гейтвей может делегировать запрос с дополнительной информацией в микросервис. Конечно, с нджиниксом можно сделать так же (nginx lua, к примеру), но здесь разница в удобстве большая.

> пишешь API — которого тебе должно хватить, и используешь только его, без попыток «хакнуть внутрянку»

Касаемо этого, не совсем понял. Вы действительно пишете микросервисы, которые могут общаться с собой через внешнее API (обычные эндпоинты, RPC), согласно микросервисной архитектуре. Гейтвей выступает лишь в качестве прокси, его можно заменить нджиниксом, как я и сказал выше.

> Подними с той стороны такой же монолит — и ничего не поменяется.

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

Великолепно проксируется — глобальный session id у вас великолепно пробрасывается через cookie с помощью nginx( в вашем случае все данные по запросу как я понимаю приходится запрашивать отдельно ). А так же вы можете сгенерировать request id для лучшего чтения логов по разным микросервисам — но это уже тонкости логирования. По глобальному session id — вы прекрасно получаете всю информацию об авторизации и прочим параметрам, сохраненных в сессии. Здесь прекрасно подойдет redis или любое подобное key-value решение. Тем более никто не мешает вам из микросервиса обратиться к микросервису. Не в публичный endpoint — а напрямую к требуемому микросервису.

Ваша бизнес-сущность (например, пользователи) будут изолированы от другой бизнес-сущности (например, товары).

А кто вам мешает их разделить в монолите? Кто вам навязывает только 1 соединение с базой данных в монолите? К примеру я в монолите великолепно создавал ещё одно соединение, и использовал его после в моделях. Очень многие фреймворки позволяют переопределить в моделях используемую бд.
Насчет проксирования нджиниксом — это сильно зависит от проекта. На проекте может быть существующий монолит, в котором написана авторизация, разделение ролей пользователей и многое другое. Начинать распил с авторизации, как по мне, не самый лучший вариант. Я предпочитаю CAS, сессию хранить в гейтвее.

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

Вариант с проксированием через NGINX и сессии в микросервисе:

request -> nginx -> microservice #1 -> microservice #2 (получаем сессию) -> microservice #1 (обрабатываем ответ согласно бизнес-логике) -> response

Шагов куда больше, чтобы понять, что пользователь неавторизован.

Вот схема с gateway:

Неавторизованный пользователь: request -> gateway -> response
Авторизованный пользователь: request -> gateway -> microservice #1 -> response

С такой схемой, если пользователь не авторизован, мы сразу отвечаем ему и не отправляем сообщение в раббит (микросервис).
Вариант с проксированием через NGINX и сессии в микросервисе:

request -> nginx -> microservice1 ( -> microservice{2,3...} ) -> response
Ведь никто не говорит вам запрашивать синхронно только с одного микросервиса нужные данные? К тому же безопасность — если у вас появляется где-то уязвимость инжекта запросов, вы ничего не потеряете, потому что ваш микросервис имеет проверки допустимости и не полагается на то, что его вызывают правильно. И если кто-то проникает в вашу сеть, он не сможет без валидной сессии что-то сделать с вашими данными. И отлаживать приятнее — если у вас возникает вопрос — а почему сервис N не отрабатывает — вы ищите проблему только в логах сервиса N ( ну и опционально, если нам ответили ошибкой — тогда идем в нужный микросервис, и соответственно в 1-м месте видим все требования к запросам, и что не было выполнено ).

С такой схемой, если пользователь не авторизован, мы сразу отвечаем ему и не отправляем сообщение в раббит (микросервис).

Для оптимизации — без вопросов. Но так ли высоки издержки если мы через rabbitmq пошлем запрос и он в самом начале обработки скажет нам 403? По моему опыту — это больше «преждевременная оптимизация», и это в крайне редких случаях является узким местом. А там, где является — там уже highload, и действуют уже немного другие законы ( и естественно ваша библиотека там наверняка не работает ).
>А кто вам мешает их разделить в монолите?

Если компания захочет взять другой язык для определенного сервиса? Другую версию языка? Что насчет изоляции того же редиса, к примеру? Поднимать несколько штук на разных портах?
Простите, но вы же не утверждаете что из языка в язык общение с redis как-то меняется? Смена языка в данном случае ваще ни на что не влияет. Оно влияет лишь на то — как будет передан запрос. Будет ли это обычный proxy_pass или ваш rabbitmq. Да и то — вопрос удобства, не более.
И просто взять и захотеть сменить язык — это требует кадров, которые умеют работать с этим языком как минимум. Если компания имеет специалистов разного уровня и с разным набором языков, почему она не может найти человека, который допишет конфиг в nginx?
Если я правильно вас понял, то вы предлагаете оставить монолит, разделив контроллеры, хотя так и должно быть изначально.

Когда у вас в проекте (репозитории) будут несколько языков, начнется каша. Микросервисы, по моему мнению, это абстракция. Если джуниору дать задачу изменить какой-то эндпоинт, расширить его функционал, то уверен, это будет гораздо сделать легче в микросервисе, где всего 6 эндпоинтов, чем в большом монолите, где намешано несколько языков.
Я лишь выступаю против написания балансировщика на node(просто хотя бы потому что express весьма слабо для такого приспособлен, и возможности у того же nginx в плане производительности выше). А у вас пока именно он и описан — вы прозрачно передаете управление по контроллерам.

Если в проекте есть документация, явно видны связи, явно видно где находится endpoint, который надо поправить — то даже у джуна не возникнет особых проблем его поправить. В случае с микросервисами — у джуна могут возникнуть проблемы плана «а как получить некоторую информацию». Как обработать ошибки при получении этой информации? Надо ли повторять запрос в микросервис? Если надо — то через какое количество времени и сколько раз? Хотя я не спорю что бывают такие проекты, в которых и сениер схватится за голову.
По поводу прокси через экспресс — я с вами полностью согласен, это плохой вариант. Я рассматриваю вариант поднятия нескольких гейтвеев и проксирования их через апстрим нджиникса: nginx -> gateway upstream -> rabbitmq
Однако для случая «передать все запросы на несколько контроллеров на другой upstream» — вполне хватит и nginx(а у вас именно такой случай). И без rabbitmq. Без rabbitmq вы конечно не сможете быстро переподнимать бекенд при падении прозрачно для пользователя ( они лишь будет ожидать ответ дольше ), но логику повторного запроса требуемых данных с фронта вроде не так уж и сложно организовать?
Полностью согласен, но реализация повторных запросов с фронтенда — это уже уход от темы, я считаю. По моему опыту и по тому, как я пишу микросервисы, я предпочитаю возлагать на фронтенд минимум всего и делать важные вещи на бэкенде (тот же повтор запросов).

По поводу проксирования у нас с вами разные мнения: вы хотите реализовывать хранение сессии в микросервисе, а я в гейтвее. В вашем случае нджиникс действительно будет эффективнее.
Не нужно никакого повторного запроса, в нормальных системах, если нода упала, то поток запросов автоматический распределяется между живыми нодами, так же и плавный апргрейд — нода выводится из потока, обновляется и вводится обратно в систему, потом следующая нода.
Конечно распределяется. Но кто возьмет запросы, которые уже обрабатывал эта нода? Их и стоит повторить. И это делается вполне прозрачно на фронте.
Если нода упала, то nginx (балансер) продублирует запрос на другую ноду (зависит от настроек).
Для запросов на чтение — бесспорно. Для запросов на запись — спорное решение.
Sign up to leave a comment.

Articles