Поделитесь опытом построения микросервисных систем
Всем привет!
Знаю, что в интернете уже есть масса информации.
Тем не менее всегда интересно почитать опыт реальных людей, а не только статьеписателей.
Вопросы следующие
1. Как определяете зону ответственности микросервиса при проектировании?
2. Как тестируете взаимодействие между микросервисами(контракт тестинг? e2e?)
3. Реплицируете ли одни и те же данные в разных сервисах? Если да, как поддерживаете актуальность?
4. Клиент работает с микросервисами напрямую или через гейтвей?
5. Использует ли кто-то концепцию микрофронтендов?
6. Если кто-то налаживал процесс релизов таких распределенных систем(чтобы версия X микросервиса была зарелизена именно с совместимой версией микроверсиа Y) — тоже будет крайне интересно почитать.
Понимаю, что универсального ответа нет. Интересует именно ваш опыт.
Спасибо
25 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарівВот так и определяем: за что он в основном отвечает. Какие новые сущности производит, какие просто копирует извне.
Могут быть вторичные ответственности, которые со временем могут отпочковаться в отдельные сервисы или переехать в лучшее место.
Кстати, композиция команд и ответственностей тоже очень важна, не только чисто концептуальные архитектурные вопросы.
В идеале — на продакшине (shadow mode, canary releases, good monitoring and metrics and retry/recovery strategy). Все остальное — суррогат.
Staging среды как правило быстро перестают выполнять свою роль с ростом масштаба. Ни расшаренные, ни обособленные. Причины: слишком много переменных (версии компонентов; корректные данные без компромисов; конфигурация как на про, а не так как другая команда сейчас решила протестить; инфраструктура тоже может вмешиваться и нагрузка отличиями). Иногда полезно, но в целом готовьтесь не полагаться как на единственную стратегию.
Контракт тестирование много шуму, но на практике я лично не встречал пока хорошего долгоиграющего решения, все больше частные случаи. Может пропустил. Но в частных случаях может работать.
В моем текущем контексте больше полагаюсь на компонентное тестирование с внешними зависимостями замоканными на уровне HTTP (WireMock). И больше фокус на быстрой починке обнаруженного дефекта с корректировкой данных, чем на полном избежании.
Да, очень помогает. Особенно разделять сложные системы учета от назойливых клиентов, которые вместо этого хранят копию данных для быстрого поиска в удобного поиска у себя. Настойчиво рекомендую иметь в наборе инструментов, даже если и не для каждого случая.
Поддержка актуальности требует действительно внимания. Нужно хорошо знать особенности своего «репликатора» и как избежать потерянных обновлений.
Из личного опыта, в случае работы с транзакционной «основной» базой удобно складывать исходящие сообщения в таблички «исходящее» в той же транзакции что и измененные данные, и отсылать отдельным процессом вне этой транзакции. Таким образом относительно просто гарантировать «at least once» отсылку обновления и избежать преждевременную до коммита.
Распределенные транзакции не прижились, кстати. Медленные и не без багов.
Хорошая книга на тему, ИМХО www.oreilly.com/...plications/9781491903063
Вопрос не понял. Внешний клиент или внутренний / другой сервис?
Используется. После разрастания пары монстро-монолитов поверх микросервисных бекендов.
Такие зависимости очень сильно не рекомендуются по многим причинам. Микросервисы должны быть в состоянии задиплоиться независимо. Смотрим API backwards compatibility и feature toggle. Feature toggle может включать новую/измененную фичу когда все готовы и должен быть строго один, во избежания мук синхронизации.
на всякий случай контекст: много людей, много команд, много изменений, много всего. В контексте одной/двух команд с несколькими доменами все может работать совсем по-другому.
Если проект находится в активной стадии разработки и основное АПИ меняется от версии к версии это нереально. Беквардс не всегда возможно в принципе.
Брехня. Не знаете как? Спросите. Лучше в другом топике. Тут тоже ниже в общем прописывали.
реально и не сложно. Другое дело, что если несколько (микро)-сервисов очень активно вместе меняются, то может быть удобно забить на обратную совместимость и диплоить вместе. Но только как исключение, а не стандартная практика ибо плохая привычка.
в догонку, если сервисы релизятся вместе, то это не МС-архитектура. Это монолит, распиленный на кусочки, от чего он становится еще хуже из-за инфраструктурного овехеда без толку. Определяющее качество микросервисов — независимость в диплойменте. Как технически, так и организационно.
Был небольшой опыт с микросервисами, скорее негативный.
Проблема в том, что при росте системы усложняется доменная модель, и через месяц-полгода-год между данными, которые изначально опеределялись как зона ответственности разных МС, могут возникнуть связи, которым архитектура МС будет только мешать. В нашем случае это привело к тому, что один разжиревший МС пришлось дропать, а его функционал полностью мержить в основной монолит.
Это было больно и долго. Поэтому я считаю что подход «monolith first» правильный — сначала пишите монолит, а потом отрезайте от него кусочки, которые явно не связаны с ним логически.
Тестирование общего взаимодействия МС — та еще боль. В общем случае это значит, что вам нужно локально поднять весь сброд на каком-то тестовом или локальном окружении и тестировать каждый по отдельности. Даже на нескольких МС это _существенно_ увеличивает сложность и тестирования и деплоя.
Деплой всего микросервисного зоопарка — отдельная песня. Версионность тоже.
По вопросам
1. Пока никак. Мы сходили по неправильному пути «а давайте это будет в микросервисе». Теперь мы не видим особой необходимости выделять новые МС на данном этапе. И врядли увидим.
2. Подняли весь зоопарк, на каждом МС в отдельности прогнали интеграционные.
3. Очень сложный вопрос. Если данные в разных МС связаны логически — частичное дублирование неизбежно. Имхо, само это указывает на избыточность МС в данном случае и на то, что МС, в котором появилась необходимость дубликации, нуждается в слиянии с другим сервисом.
4. Зависит от роли сервиса. Может взаимодействовать напрямую, а может и через несколько звеньев
5. Нет юая.
6. Нормального СИ/СД нет, поэтому «Вася, деплой А, В, С версий 3, 100500 и 9001 соответственно». Но вообще больно, да.
Без Докера можно сразу застрелиться :^)
Я полностью согласен с подходом
. Вопрос в том когда монолит становится огромным, релизы выкатываются с трудом, связанность огромная, машстабировать команду очень сложно и т.д. и т.п.
Конечно проблемы монолита можно решать в монолите, но имхо всеравно есть критический размер приложения когда ты или начинаешь его разделять или перестаешь вообще релизить новые фичи чтобы ничего не сломать.(и то остается куча работы по поддержке существующих фич)
Релизы всегда выкатываются с трудом, и в 99% разработка тут не при чем, банально клиенты тупят учитывать изменения.
Был случай, когда клиент не мог убрать единственное поле из джсона 4 месяца (!!! при том что поле перестало играть какую-либо роль спустя 2 недели), и мы даже создали персональный костыль и назвали его по имени клиента.
Имхо это больше вопрос не самого монолита, а того, как этот монолит писали :)
Наш монолит упирался в такую ситуацию несколько раз, и каждый раз переживал рефакторинг разной степени радикальности, после чего можно было пилить дальше. И это не проблема монолита как архитектурного подхода, а проблема ошибок проектирования отдельных сервисов, фичей, структур данных и т.п.
Я не спорю, что некоторые части можно вынести в МС, но они для этого действительно должны подходить.
Помимо этого, не стоит забывать, что МС это всегда + накладные расходы на слой передачи данных.
Про какую связность мы говорим? Связность данных или связность компонентов монолита?
Связность компонентов — зло, связность данных — неизбежна. В бизнесе это де-факто стандарт. Рано или поздно у бизнеса возникает желание связать все со всем, ваше дело сделать так, чтобы это не привело в сильной связности компонентов.
Бизнес/тех требования разбиваю на простейшие сущности по принципу single responsibility (адаптированным под микросервисы)
Использую uri versioning с обратной совместимостью. Поэтому коммуникация существующий сервисов никогда не ломается.
Новые URL тестирую использую curl, e2e, функциаональные тесты
Репликация не требуется- так как я использую микросервисы на кубернетес- т.е. при необходимости поднимаю инстанс с источником данных с prepopulated data.
Все клиенты общаются через Load Balancer
Каждый новый UI модуль разрабатывается как отдельный микросервис и подвязывается в Load Balancer. С точки зрения клиента — всё может выглядеть как один единный UI так как куча независивых приложений.
В Ansible настраиваются зависимости для каждого микросевиса. В гитлабе вешаеттся хук на push to master- для деплоймента на выбранный environment
Жутко удобная архитектура — мне очень нравится- можно сказать — что прям таки тащусь от самой идеи. Не раз показывала свою состоятельность- особенно в связке с докер ранчер кубернетес.
И сколько версий одновременно поддерживаете? Есть процесс отказа от устаревших версий API ?
Тесты пишете на конкретный кусок функциональности(сервис) или есть тесты покрывающие всю систему целиком?
Уточню. Допустим у меня есть сервис с товарами, есть сервис с клиентами и сервис с заказами.
Когда я на клиенте хочу показать заказы, мне надо сходить на заказы, получить их, потом получить клиентов и потом товары, все это смерджить и отдать на клиент.
Есть ли практика задублировать какие нибудь данные из клиентов в заказы, чтобы избавиться от лишнего сетевого взаимодействия при запросе с клиента?
На данный момент- самай глубина истории — 4- так как есть внешние клиенты- ну с ооооочень медленным циклом разработки- новую версию выкатывают раз в 3 месяца и большой болью. Для них на проде держу 1 версия сервиса интеграции с их подделкой- тогда как как на остальных environment-ах уже поддерживаю их новую спецификацию т.к. я использую URI versioning — то мне не проблема деплоить один и тот же код на все environment-ы. Каждая версия работает независимо.
Как только сторонний вендор выдохнет- и откажется от старого 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 ) . Таким способом- я строю легко расширяемую систему- без необходимости править существующие микросервисы- при добавлении новой фичи. Получается сеть из сервисов оркестрации о зонами ответственности (сервисы исполнения).
Кубернетес — это слой оркестрации инфраструктуры. Я же говорю о сервисах оркестрации приложения работающего поверх кубернетес.
Это фишка отлита кровью и и нервами моего опыта- когда закачик приходит говорит- ко мне вчера приходили сейлзы оракла и сказали что сделают хорошую скидку на оракл экзадата- при условии подписания контракта на 5 лет с оплатой upfront. и заказчик соглашается на это. И это при наличии MS SQL, MySQL, Kafka, Influxdb, Informix etc.
В итоге надо работать с целым зоопарком источников данных- и поэтому vendor specific CRUD operations реализованы в виде отдельных сервисов.- ну и обращаться к ним могут множество других сервисов при необходимости.
Доступ к данным остаётся- только обращаться к данным сервис будет путем вызова соответствующего CRUD микросервиса — так проще поддержку осуществлять. Ну и секьюирити никто не отменял- незачем сервису бизнеслогики ходить в consul vault за строкой подключения с базе данных.
По функционалу. Например сервис для оповещений — отдельно, сервис для общения с внешней системой — отдельно. Главное не делать ошибки, когда делается сервис для одной сущности, т.е. по сути join заменяется http вызовом.
Есть Kubernetes кластер в обалке, деплоится туда вся система и тестируется.
Разве что конфиги и feature флаги.
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-ом проксі клієнта)
Також я спостерігав і невдалі вирішення — коли здалеку виглядало і рекламувалося як мікросервісна архітектура, а зблизька — збочений моноліт з проблемами тестування і розгортання.
micro-frontends.org
Этот подход манит своей простотой, но пугает необходимость поддерживать кучу версий одного и того же эндпоинта для различных клиентов. Когда захочется отказаться от самой древней версии — придется проводить масштабные раскопки )
5.
Ні, мікрофронтендів не було. Я згадав цю тему, виглядає симпатично, але врешті решт впирається в реалізацію «service bus», тільки на фронтенді , і доволі «обмежені» віджети. Врешті решт виходить новий друпал, або щось, що би бачимо в Джирі. Це добре для якогось конструктора, де більшість екранів це форми вводу, або якщо кожна підсистема є повністю незалежна і живе під своєю підпапочкою.
6. Як я написав — якщо це, наприклад, OpenAPI3 (екс сваггер) — то в залежності від версії клієнт користується певним версійним проксі компонентом, згенерованим на основі версі ендпоінта.
Коли клієнту не вистачає методів — переповзає на новий.
У випадку зовсім breaking changes — проксі компонент просто починає валитися на ініціалізації (update required)
Спасибо! От микрофронтендов пока только положительное впечатление. Фронтенды интегрируются друг с другом через браузерные ивенты, никаких ограничений не заметили. Иногда есть сложности, которых не было бы в монолитном фронтенде. Но когда приложение большое, сложности возникают что в одном что в другом случае.