Конференция DSE Fest — технично и понятно про data science для разработчиков. Смотри детали на сайте
×Закрыть

Поделитесь опытом построения микросервисных систем

Всем привет!

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

Вопросы следующие
1. Как определяете зону ответственности микросервиса при проектировании?
2. Как тестируете взаимодействие между микросервисами(контракт тестинг? e2e?)
3. Реплицируете ли одни и те же данные в разных сервисах? Если да, как поддерживаете актуальность?
4. Клиент работает с микросервисами напрямую или через гейтвей?
5. Использует ли кто-то концепцию микрофронтендов?
6. Если кто-то налаживал процесс релизов таких распределенных систем(чтобы версия X микросервиса была зарелизена именно с совместимой версией микроверсиа Y) — тоже будет крайне интересно почитать.

Понимаю, что универсального ответа нет. Интересует именно ваш опыт.

Спасибо

LinkedIn
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Как определяете зону ответственности микросервиса при проектировании?

Вот так и определяем: за что он в основном отвечает. Какие новые сущности производит, какие просто копирует извне.

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

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

Как тестируете взаимодействие между микросервисами(контракт тестинг? e2e?)

В идеале — на продакшине (shadow mode, canary releases, good monitoring and metrics and retry/recovery strategy). Все остальное — суррогат.

Staging среды как правило быстро перестают выполнять свою роль с ростом масштаба. Ни расшаренные, ни обособленные. Причины: слишком много переменных (версии компонентов; корректные данные без компромисов; конфигурация как на про, а не так как другая команда сейчас решила протестить; инфраструктура тоже может вмешиваться и нагрузка отличиями). Иногда полезно, но в целом готовьтесь не полагаться как на единственную стратегию.

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

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

Реплицируете ли одни и те же данные в разных сервисах? Если да, как поддерживаете актуальность?

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

Поддержка актуальности требует действительно внимания. Нужно хорошо знать особенности своего «репликатора» и как избежать потерянных обновлений.

Из личного опыта, в случае работы с транзакционной «основной» базой удобно складывать исходящие сообщения в таблички «исходящее» в той же транзакции что и измененные данные, и отсылать отдельным процессом вне этой транзакции. Таким образом относительно просто гарантировать «at least once» отсылку обновления и избежать преждевременную до коммита.

Распределенные транзакции не прижились, кстати. Медленные и не без багов.

Хорошая книга на тему, ИМХО www.oreilly.com/...​plications/9781491903063

Клиент работает с микросервисами напрямую или через гейтвей?

Вопрос не понял. Внешний клиент или внутренний / другой сервис?

Использует ли кто-то концепцию микрофронтендов?

Используется. После разрастания пары монстро-монолитов поверх микросервисных бекендов.

Если кто-то налаживал процесс релизов таких распределенных систем(чтобы версия X микросервиса была зарелизена именно с совместимой версией микроверсиа Y) — тоже будет крайне интересно почитать.

Такие зависимости очень сильно не рекомендуются по многим причинам. Микросервисы должны быть в состоянии задиплоиться независимо. Смотрим API backwards compatibility и feature toggle. Feature toggle может включать новую/измененную фичу когда все готовы и должен быть строго один, во избежания мук синхронизации.

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

Микросервисы должны быть в состоянии задиплоиться независимо.

Если проект находится в активной стадии разработки и основное АПИ меняется от версии к версии это нереально. Беквардс не всегда возможно в принципе.

Беквардс не всегда возможно в принципе.

Брехня. Не знаете как? Спросите. Лучше в другом топике. Тут тоже ниже в общем прописывали.

Если проект находится в активной стадии разработки и основное АПИ меняется от версии к версии это нереально.

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

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

Был небольшой опыт с микросервисами, скорее негативный.

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

Это было больно и долго. Поэтому я считаю что подход «monolith first» правильный — сначала пишите монолит, а потом отрезайте от него кусочки, которые явно не связаны с ним логически.

Тестирование общего взаимодействия МС — та еще боль. В общем случае это значит, что вам нужно локально поднять весь сброд на каком-то тестовом или локальном окружении и тестировать каждый по отдельности. Даже на нескольких МС это _существенно_ увеличивает сложность и тестирования и деплоя.

Деплой всего микросервисного зоопарка — отдельная песня. Версионность тоже.

По вопросам
1. Пока никак. Мы сходили по неправильному пути «а давайте это будет в микросервисе». Теперь мы не видим особой необходимости выделять новые МС на данном этапе. И врядли увидим.
2. Подняли весь зоопарк, на каждом МС в отдельности прогнали интеграционные.
3. Очень сложный вопрос. Если данные в разных МС связаны логически — частичное дублирование неизбежно. Имхо, само это указывает на избыточность МС в данном случае и на то, что МС, в котором появилась необходимость дубликации, нуждается в слиянии с другим сервисом.
4. Зависит от роли сервиса. Может взаимодействовать напрямую, а может и через несколько звеньев
5. Нет юая.
6. Нормального СИ/СД нет, поэтому «Вася, деплой А, В, С версий 3, 100500 и 9001 соответственно». Но вообще больно, да.

Деплой всего микросервисного зоопарка

Без Докера можно сразу застрелиться :^)

Я полностью согласен с подходом

monolith first

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

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

релизы выкатываются с трудом

Релизы всегда выкатываются с трудом, и в 99% разработка тут не при чем, банально клиенты тупят учитывать изменения.

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

есть критический размер приложения когда ты или начинаешь его разделять или перестаешь вообще релизить новые фичи

Имхо это больше вопрос не самого монолита, а того, как этот монолит писали :)

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

Я не спорю, что некоторые части можно вынести в МС, но они для этого действительно должны подходить.

Помимо этого, не стоит забывать, что МС это всегда + накладные расходы на слой передачи данных.

связанность огромная

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

1. Как определяете зону ответственности микросервиса при проектировании?

Бизнес/тех требования разбиваю на простейшие сущности по принципу single responsibility (адаптированным под микросервисы)

2. Как тестируете взаимодействие между микросервисами(контракт тестинг? e2e?)

Использую uri versioning с обратной совместимостью. Поэтому коммуникация существующий сервисов никогда не ломается.
Новые URL тестирую использую curl, e2e, функциаональные тесты

Реплицируете ли одни и те же данные в разных сервисах? Если да, как поддерживаете актуальность?

Репликация не требуется- так как я использую микросервисы на кубернетес- т.е. при необходимости поднимаю инстанс с источником данных с prepopulated data.

4. Клиент работает с микросервисами напрямую или через гейтвей?

Все клиенты общаются через Load Balancer

Использует ли кто-то концепцию микрофронтендов?

Каждый новый UI модуль разрабатывается как отдельный микросервис и подвязывается в Load Balancer. С точки зрения клиента — всё может выглядеть как один единный UI так как куча независивых приложений.

Если кто-то налаживал процесс релизов таких распределенных систем(чтобы версия X микросервиса была зарелизена именно с совместимой версией микроверсиа Y) — тоже будет крайне интересно почитать.

В Ansible настраиваются зависимости для каждого микросевиса. В гитлабе вешаеттся хук на push to master- для деплоймента на выбранный environment

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

Использую uri versioning с обратной совместимостью. Поэтому коммуникация существующий сервисов никогда не ломается.
Новые URL тестирую использую curl, e2e, функциаональные тесты

И сколько версий одновременно поддерживаете? Есть процесс отказа от устаревших версий API ?
Тесты пишете на конкретный кусок функциональности(сервис) или есть тесты покрывающие всю систему целиком?

Репликация не требуется- так как я использую микросервисы на кубернетес- т.е. при необходимости поднимаю инстанс с источником данных с prepopulated data.

Уточню. Допустим у меня есть сервис с товарами, есть сервис с клиентами и сервис с заказами.
Когда я на клиенте хочу показать заказы, мне надо сходить на заказы, получить их, потом получить клиентов и потом товары, все это смерджить и отдать на клиент.
Есть ли практика задублировать какие нибудь данные из клиентов в заказы, чтобы избавиться от лишнего сетевого взаимодействия при запросе с клиента?

И сколько версий одновременно поддерживаете?

На данный момент- самай глубина истории — 4- так как есть внешние клиенты- ну с ооооочень медленным циклом разработки- новую версию выкатывают раз в 3 месяца и большой болью. Для них на проде держу 1 версия сервиса интеграции с их подделкой- тогда как как на остальных environment-ах уже поддерживаю их новую спецификацию т.к. я использую URI versioning — то мне не проблема деплоить один и тот же код на все environment-ы. Каждая версия работает независимо.

Есть процесс отказа от устаревших версий API

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

Тесты пишете на конкретный кусок функциональности(сервис) или есть тесты покрывающие всю систему целиком?

Пишу три вида тестов
1. Юнит тесты — каждого публичного метода в каждом классе.
2. Функциональные тесты — тестится связка одно или нескольких сервисов одновременно — тест фич другими словами
3. Бэк тесты — т.к. я пишу AI систему- то после добавления каждой новой фичи — необходимо проверить как это отразилось на рассчетах проведенных в прошлом (обратная совместимость на уровне данных)

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

Допустим у меня есть сервис с товарами, есть сервис с клиентами и сервис с заказами.

Я не знаю всей подноготной, но думаю здесь тоже можно применить принцип декомпозиции.
Думаю моожно сделать примерно так:
1. Разбиваешь задачи на независимые сущности
2. Создаешь сервисы оркестрации взаимодействия между всеми сущностями
3. Создаешь сервисы доступа к данным
4. Создаешь сервисы безопасности
5. Создаешь сервисы взаимодествия с third party systems (if any)
5. Создаешь сервисы фасады для общения с клиентами (web, sockets, udp/ tcp, etc)

Цепочка взаимодействия может выглядеть примерно так
клиент ->(your prefered protocol) -> Load Balancer -> сервисы фасада ->(raw message)-> сервисы безопасности -> сервисы оркестрации -> (выбор что надо делать в паралель/последовательно) сервисы бизнес логики -> сервисы доступа к данным/сервисы взаимодествия с third party systems

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

Внутри кластера (виртуализации) — сетевой обмен это небольшая проблема- т.к. организовано все это внутри кластера (3 и больше хостов организуют кластер, которые обычно соединены между собой high speed fiber channel)
После того, как сообщения прошло проверку безопасности- оно дублируется всем сервисам в исходном виде — на выходе сервисы оркестрации собируют результаты со всех сервисов что уложились в SLA- и формируют ответ клиенту ( с заглушками если кто то опоздашка)

Спасибо за детальное описание. А чем конкретно занимаются сервисы оркестрации? Kubernetes это оно?
И разделение на сервисы бизнес логики и сервисы доступа к данным, в чем фишка? Если сервис реализует бизнес логику, разве у него не должно быть доступа к данным(если они нужны конечно)?

А чем конкретно занимаются сервисы оркестрации?

Это набор сервисов — которые знают свою зону ответственности и могут направлять данные на обработку сервисам о которых они знают. (serviceorientation.com/...​soaglossary/orchestration) Допустим есть входное сообщение, self describing json- сервис оркестрации читает сообщение и если понял какую либо инструкцию- перенаправляет сообщение соответствующим сервисам на исполнение в паралель (топология- звезда), собирает результаты выполнения и отправляет обратно клиенту. В случае если сообщение не понято/частично не понято- сервисом оркестрации — перенаправляется следующему в цепочке сервису оркестрации( en.wikipedia.org/wiki/Pipeline_(software ) . Таким способом- я строю легко расширяемую систему- без необходимости править существующие микросервисы- при добавлении новой фичи. Получается сеть из сервисов оркестрации о зонами ответственности (сервисы исполнения).

Kubernetes это оно?

Кубернетес — это слой оркестрации инфраструктуры. Я же говорю о сервисах оркестрации приложения работающего поверх кубернетес.

разделение на сервисы бизнес логики и сервисы доступа к данным, в чем фишка?

Это фишка отлита кровью и и нервами моего опыта- когда закачик приходит говорит- ко мне вчера приходили сейлзы оракла и сказали что сделают хорошую скидку на оракл экзадата- при условии подписания контракта на 5 лет с оплатой upfront. и заказчик соглашается на это. И это при наличии MS SQL, MySQL, Kafka, Influxdb, Informix etc.
В итоге надо работать с целым зоопарком источников данных- и поэтому vendor specific CRUD operations реализованы в виде отдельных сервисов.- ну и обращаться к ним могут множество других сервисов при необходимости.

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

Доступ к данным остаётся- только обращаться к данным сервис будет путем вызова соответствующего CRUD микросервиса — так проще поддержку осуществлять. Ну и секьюирити никто не отменял- незачем сервису бизнеслогики ходить в consul vault за строкой подключения с базе данных.

1. Как определяете зону ответственности микросервиса при проектировании?

По функционалу. Например сервис для оповещений — отдельно, сервис для общения с внешней системой — отдельно. Главное не делать ошибки, когда делается сервис для одной сущности, т.е. по сути join заменяется http вызовом.

2. Как тестируете взаимодействие между микросервисами(контракт тестинг? e2e?)

Есть Kubernetes кластер в обалке, деплоится туда вся система и тестируется.

3. Реплицируете ли одни и те же данные в разных сервисах? Если да, как поддерживаете актуальность?

Разве что конфиги и feature флаги.

4. Клиент работает с микросервисами напрямую или через гейтвей?

Gateway конечно. Выставлять сервисы напрямую это ад со стороны security.

6. Если кто-то налаживал процесс релизов таких распределенных систем(чтобы версия X микросервиса была зарелизена именно с совместимой версией микроверсиа Y) — тоже будет крайне интересно почитать.

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

Есть Kubernetes кластер в обалке, деплоится туда вся система и тестируется.

А какой тип тестов используется?

Главное не делать ошибки, когда делается сервис для одной сущности, т.е. по сути join заменяется http вызовом.

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

Gateway конечно. Выставлять сервисы напрямую это ад со стороны security.

А в чем проблема? На всех сервисах можно подключать shared либу отвечающую за аутентификацию.

Не работал с микросервисами, но зато работал со связками серверов, которые ещё и написаны на разных языках. Друг с другом они по http общались, json-ы друг другу передавали. :^) На мой взгяд, микросервисы это переусложнение системы, которое приведёт к регрессии через пару лет разработки. А если ещё разные бд использовать, то это абсолютный лютый кошмар. XA-транзакции не от хорошей жизни придумали.

Есть системы которые так уже работают и живут дольше 2х лет :)
Я бы не заводил разговор, если бы все было хорошо с толстыми монолитами. Но с ними не все хорошо )

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

Вся прелесть распределенных систем состоит в том- что при их разработке- изначально закладывается опредленных уровень прочности, т.к. Распределенная система будет фейлить- и это должно быть учтено дизайном. Trust no one.
Каждый сервис желательно включить поддержку Retry,Circuit Breakers, heartbeat etc

если бы все было хорошо с толстыми монолитами. Но с ними не все хорошо

А что именно с ними не хорошо?

1. Обережно )
2. Комбінація і тестування контракту, і e2e якщо це мікросервіс з UI
3. Сервіс, це чорний ящик, що реалізує певний контракт. Як він це робить — його особиста відповідальність, аби контракт був виконаний.
4. Гейтвей (в декількох проектах був kong, але якщо є прив’язка до конкретного cloud — можна дивитись там). Це може розвантажити рівень авторизації-підпису-валідації запитів.
5. Що саме ви маєте на увазі ? (посилання на визначення)
6. Тут кожен стартап наступає на свої власні граблі. Але загалом працює підхід інтерфейсів з ООП — після опублікування інтерфейс не може змінюватися. У випадку змін публікується нова версія інтерфейсу.
Далі — залежно, що придумав архітектор з командою )

може — підтримуються «features» — клієнт попереджає сервіс, які функціонали йому потрібні на етапі «handshake»

може — реалізується якийсь sem-versioning, і клієнти знають з якою версією апі вони сумісні. (UPD: якщо це якесь OpenAPI, то від клієнта це ховається, наприклад, за stub-ом проксі клієнта)

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

5. Що саме ви маєте на увазі ? (посилання на визначення)

micro-frontends.org

6. Тут кожен стартап наступає на свої власні граблі. Але загалом працює підхід інтерфейсів з ООП — після опублікування інтерфейс не може змінюватися. У випадку змін публікується нова версія інтерфейсу.
Далі — залежно, що придумав архітектор з командою )

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

5.
Ні, мікрофронтендів не було. Я згадав цю тему, виглядає симпатично, але врешті решт впирається в реалізацію «service bus», тільки на фронтенді , і доволі «обмежені» віджети. Врешті решт виходить новий друпал, або щось, що би бачимо в Джирі. Це добре для якогось конструктора, де більшість екранів це форми вводу, або якщо кожна підсистема є повністю незалежна і живе під своєю підпапочкою.

6. Як я написав — якщо це, наприклад, OpenAPI3 (екс сваггер) — то в залежності від версії клієнт користується певним версійним проксі компонентом, згенерованим на основі версі ендпоінта.
Коли клієнту не вистачає методів — переповзає на новий.

У випадку зовсім breaking changes — проксі компонент просто починає валитися на ініціалізації (update required)

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

Подписаться на комментарии