Pull to refresh

Comments 24

Из личного опыта - если это стартап, не делайте свою жизнь и жизнь того кому придется поддерживать это микро сервисное чудо через 3-5 лет когда бизнес модель измениться 2-3 раза, когда стартап только начинает понимать что у него core, какая у него нагрузка, планы на будущее в плане масштабируемости (масштабируемости бизнеса) и где ему реально нужны плюшки от микросервисов.

Это не значит что нужно писать монолит-какашку. Начните с монолита, но с нормальной архитектурой. Не нужно писать спагетти. Разделите на модули, сервисы, отделите логику от отображения и стораджа (3tier, Onion, whatever), и в будут сможете малой кровью вынести core в микро сервисы.

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

Что бы вам не говорили, микросервисы это сложно.

Действительно, микросервисы это сложно. Действительно, стартовать проще монолит. Я не призываю никого начинать новые проекты на микросервисах в этой статье.

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

Как и где вы джойните данные для сайта в такой архитектуре? например для отображении публикации

нужно взять статью из сервиса "статьи"

и внизу отобразить "профиль автора" который хранится в сервисе "юзеры"

Не очень понятно в чём сложность и каверзность вашего вопроса. На одной странице много статей с профилями?

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

методы такие как :

join на клиента

join на gateway

join на основе cache при добавлении записей

стриминговый join пререндеринг такие как join kafka stream

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

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

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

В случае монолита и явном вызове функции приведенный вопрос равносилен тому, что вы меняете сигнатуру - и не озадачиваетесь проверить, а в каких местах она используется. Проще просто не менять сигнатуру. Независимость достигается именно разграничением границ и сохранением их неизменности

Переформулирую вопрос. Что случится с сервисом "статьи" если сервис "юзеры" упадет? А если взять из вашей схемы сервис "пользователи" и уронить его - перестанут корректно работать сервис "продажи" и в следствии сервис "каталог". Получается у ваших микросервисов структуры данных жестко связаны друг с другом. Приходится так же думать об их согласованности(кстати, как у вас с этим дела?). В таком случае не лучше и легче ли объединить все в один микросервис, но раскидать по разным компонентам с межпроцессорными взаимодействиями?
Я не в коем случае не указываю как делать, я скорее ученик в этом плане, хочу узнать ваше мнение/опыт :)

Что случится с сервисом "статьи" если сервис "юзеры" упадет?

В самом простом случае, если не озадачиться, то упадет. Но никто не мешает программно руками обрабатывать внутреннюю ошибку и 1) заполнять поля дефолтными/пустыми значениями 2) придумать механизм трансляции ошибки в ответе рядом с основной структурой.

Согласованность данных так же нужно программно отслеживать, так же обрабатывая ответ нижестоящего микросервиса и откатывая в своем в случае ошибки. Тем не менее, при таком подходе у нас данные согласованы 99.9% времени, в отличие от подхода подписки на обновления, где этот лаг может быть выше.

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

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

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

Плакать хочется... Где перформанс? Эвэилабилити? Независимая разработка? Скейлинг? Или это проекты на 2 посетителя в день? Ну не нужны вам тогда микросервисы вообще...

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

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

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

Почитайте про DDD, CQRS.

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

Например, вам нужно вернуть в апишке статью и данные автора. Сервис статей и сервис авторов находятся в разных доменах.

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

Подписываетесь на изменения автора. Если автор изменил фамилию - ваши сабскрайберы обновляют вьюшку.

В итоге, если сервис авторов крашится, или еще чего - на уже существующий функционал это не влияет.

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

Ну это в кратце...

Потому как сейчас я не вижу преимуществ того что вы называете микросервисами.

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

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

А что если мои запросы убивают тот второй сервис потому что он был не готов к таким фильтрам и сортировками? Хороше, он добавит индексы для меня, и еще +100500 клиентов, у убьет свой перформанс на write side .

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

Тут конечно стоит упомянуть eventual consistency...

В итоге, если сервис авторов крашится, или еще чего - на уже существующий функционал это не влияет.

На функционал да, но данные будут неактуальными, это лишь скрытие проблемы. Что лучше при ошибке обновления данных - оставить то же значение или показать пустое значение / текст ошибки?

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

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

Время ответа моего апи будет зависеть от времени ответа чужого апи.

Это правда.

А что если мои запросы убивают тот второй сервис потому что он был не готов к таким фильтрам и сортировками?

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

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

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

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

На функционал да, но данные будут неактуальными, это лишь скрытие проблемы. Что лучше при ошибке обновления данных - оставить то же значение или показать пустое значение / текст ошибки?

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

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

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

А теперь представьте у банка проблема и один из их сервисов упал и его будут чинить сутки. Вы предпочтете видеть 0 или устаревшие данные с надписью - "Данные актуальны на ... и могут не содержать результаты последних операций"?

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

Вы мыслите как программист а не как домейн эксперт. В большинстве случаем старые данные намного ценнее странички с пустыми данными.

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

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

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

Да, поэтому там свои решения в виде схема реджистри и коррапшен лэйер и т.д. И опять же, это сложно.

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

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

з.ы.

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

Но я прекрасно понимаю что есть трейдофы, и иногда приходится ради фронтендеров городить backend for frontend, хоть я их и не люблю всей душой.

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

З.Ы.

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

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

А теперь представьте у банка проблема и один из их сервисов упал и его будут чинить сутки. Вы предпочтете видеть 0 или устаревшие данные с надписью - "Данные актуальны на ... и могут не содержать результаты последних операций"?

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

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

Да, еще один хороший аргумент для децентролизации данных или заведения кеша

Любая проблема на этом сервисе влияет на всю систему. Это реальная жопа.

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

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

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

Что мешает тот же монолит загрузить на нужное железно, открыть только определенное АПИ где будет юзаться ваше железо, дать отдельную днску и поставить под отдельным лоад балансером, и вуаля - проблема решена. Юзай как отдельный сервис. Никто снаружи не обязан знать что это монолит.

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

так и делали в былые времена

А как сейчас решаете задачу с упиранием в базу? По идее решение должно одинаково подходить как к монолиту так и к микросервисам.

делю базу на мастер и слейв. Пока хватает! Монолит у меня тоже есть, но он и не дико нагружен, хватает мастера

хоститься в облаке)

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

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

Индексы же имеют отношение к хранению данных, а значит речь в первую очередь подразумевается БД. Ее обычно имеет смысл держать на отдельной машине, потому что база всегда ресурсы требует, независимо от архитектуры. Разница лишь в том, что при монолите у вас скорее всего будет развеситая схема с кучей констрейнтов и внешних ключей, а в случае микросервисов - набор слабосвязанных таблиц, в которых нужные констрейнты не существуют, а реализуются программно, и достигается "согласованность в конечном счете". Да, в монолите проще написать неэффективный SQL, который будет долго исполняться. А в микросервисах вы вообще просто данные потеряете и база будет несогласована.

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

Sign up to leave a comment.