Pull to refresh

Comments 18

Спасибо за систематизацию. Опытным путем пришел к 3 группам чекистов по 8 пунктов в каждом. И не так уж много вариантов выбора на самом деле.

Сделал мой день! Может даже два! Спасибо!

Спасибо огромное за материал! Жалко 10 плюсиков статье не могу поставить, только один.

И маленькая просьба - тот постер / cheatsheet, который был в соседней статье для привлечения внимания - можно его в векторе (pdf) выложить и тут ссылку дать? Я распечатаю и на стенку повешу, чтобы сверяться с.

Микросервисы — это когда каждая часть системы живёт отдельно, со своей БД, своей логикой, своей жизнью.

Пример: ты пишешь бэкенд для интернет-магазина, и в одном проекте у тебя и пользователи, и товары, и корзина, и админка.

Я похоже отстал от веб-разработки (10 лет сижу только в базах данных), но неужели каждый микросервис юзает СВОЮ бд? В вашем примере интернет-магазина разве будет не одна общая база? Неужели отдел продаж имеет свой список покупок, отдел бухгалтерии - свой список платежей, а пользователи - свой?
А как же их синхронизировать? А если отмена платежа? И главное - зачем дублировать?
Я бы спроектировал одну базу на все отделы интернет-магазина.
В чем я не прав?

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

Обычно в крупном бизнесе бывает так нужно протестировать какую то идею на ограниченной аудитории, например 500–1000 пользователей. Делают MVP иногда прямо на коленке, часто на PHP потому что быстро. Выкатывают первую версию, смотрят, как всё работает. Если гипотезы подтверждаются начинают двигаться дальше выстраивают архитектуру, режут монолит на части, готовятся к росту.

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

Например, сервис заказов хранит только заказы, сервис платежей только информацию о платежах, сервис пользователей только данные профилей. Если заказ создан, один сервис кидает событие, а другие на него реагируют. Так и всё синхронизируется. Подходы могут быть разные.

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

Так что все ОК. Просто вы смотрите на задачу с позиции одного общего проекта то есть монолита. Такой подход отлично подходит для MVP, быстрых запусков и проверки гипотез. Но когда речь идёт о миллионах пользователей, монолит режут на части, чтобы система работала не только быстро, но и предсказуемо.

Когда пользователей становится больше, растёт и инфраструктура появляется много сервисов, API, очередь задач, логирование, мониторинг, CI/CD, облака и т.д. В этот момент один человек который разработал MVP на коленках уже не может всё успевать. Формируется команда из бекенд разработчиков, devOps, аналитики, тестировщики, тим лиды, архитекторы ...

Одна СУБД на несколько сервисов - это необязательно монолитная архитектура.

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

Например, сайт продолжит работать, а пользователь просто увидит сообщение вроде "Платежи временно недоступны".

А в монолите так нельзя сделать разве? Если упал модуль, ответственный за платежи - кинуть ошибку "Платежи временно недоступны"

Можно никто не запрещает :) Типа да, ты можешь там отловить ошибку самой платежной системы если причина на стороне платежки, показать оповещение “платежи недоступны”. Но один сбой в монолите может повлиять на что угодно ну то есть на все :) А в микросервисах всё реально разделено.

Упал сервис email рассылки ну и ладно, письма потом отправим, ничего критичного. Главное, чтобы заказ оформили.
Лёг сервис уведомлений ничего страшного, просто не пришло пуш уведомление, пользователь всё равно заказ сделал и оплатил. Потом доставим.
Каталог товаров работает, но упал сервис рекомендаций ну нет блока “вам также может понравиться” и что? Магазин всё ещё работает.
Слетел сервис аналитики система не умирает, просто временно не считаем метрики. Пользователю вообще пофиг.
Проблемы с чатом поддержки ничего, добавим “Извините, чат временно недоступен, напишите на почту”.
Да даже если сервис отзывов/комментариев лёг тоже не страшно. Временная заглушка, типа “не удалось загрузить отзывы”, и поехали дальше. И т д там примеров куча на самом деле.

Ну и плюс обновлять, масштабировать, мониторить удобнее. Выкатил один сервис остальное не трогаешь. Да, сложнее на старте, но зато стабильнее на росте. В монолите тоже можно что то похожее сделать, просто это всё усложняется и начинает напоминать микросервисы внутри монолита а это уже звоночек :) Это как раз про устойчивость. Монолит легче сделать, но он сложнее масштабируется и хуже переживает частичные сбои. А в микросервисах у тебя появляется гибкость один сервис масштабируешь, другой выкатываешь без остановки всей системы, третий деплоишь в экспериментальном режиме и т д.

Упал сервис email рассылки ну и ладно, письма потом отправим, ничего критичного. Главное, чтобы заказ оформили.

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

Каталог товаров работает, но упал сервис рекомендаций ну нет блока “вам также может понравиться” и что? Магазин всё ещё работает.

Слетел сервис аналитики система не умирает, просто временно не считаем метрики. Пользователю вообще пофиг.

Проблемы с чатом поддержки ничего, добавим “Извините, чат временно недоступен, напишите на почту”.

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

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

Интересно как вы собираетесь продолжать ловить catch, если у вас весь монолит уже лег? Причём лег так плотно что уже ничего не приходится ловить потому что сайт тупо недоступен ни одна страница. И лег сайт именно тогда когда вы пошли спать)

если у вас весь монолит уже лег? Причём лег так плотно что уже ничего не приходится ловить потому что сайт тупо недоступен ни одна страница

Это ведь другой уровень проблемы. Тоже самое если б и BFF-сервер\app-сервер лёг. Неважно - монолит оно называется или нет.

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

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

Я так вижу эту ситуацию.

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

Спасибо за статью. Очень полезно.

Но про монолиты - спорно.

Один сбой — всё рушится.

Монолит легко масштабируется. Ничто не мешает вам запусть И копий монолита за лоад-балансером. И вот вам сразу и балансировка и Fault Tolerance.
Да, это может быть не очень эффективно по ресурсам (Сложно масштабировать по частям - да это правда)
Но это работает. А учитывая тот факт что монолиту никто не запрещает использовать CDN, Redis и т.п. - такая инфрастуктура может сжирать крайне впечатляющие нагрузки и быть вполне надежной.

А неэффективность использование ресурсов может вполне компенсироваться простотой в поддержке и разработке.

Тяжело работать в команде, мешают друг другу

Ну очень спорно. То что у вас монолит не значит что внутри это единый кусок кода. Он может ( и наверняка будет) разбит на модули/библиотеки и т.п.
И каждый такой модуль может разрабатываться и поддерживаться разными командами практически независимо.
В итоге это все собирается в единое приложение - да. Но не более того.

И будем честны:
Монолит не просто проще отлаживать: eго радикально проще отлаживать, мониторить и разрабатывать. Распределенная архитектура(такая как микросервисы) добавляет вам целую пачку не существующих в монолите, сложных проблем.

Это не значит, что монолит - всегда хорошо, а микросервисы - всегда плохо. НЕТ.
Но "Монолит — хорош для MVP, старта проекта или очень простой логики." Это прям очень большое приуменьшение.

Монолит может быть масштабируем особенно если его грамотно спроектировать. Но всё же есть важный момент представьте, что ваше приложение обрабатывает миллиард запросов в секунду. Что вы будете делать? В монолите придётся копировать всё приложение целиком, чтобы масштабироваться. Да, можно поставить его за load balancer, и да, это будет работать. Но ресурсов будет тратиться куда больше, а куда больше затратно чем если бы вы увеличили ресурсы для конкретной части программы.Вы масштабируете не только "горячие" участки но и весь остальной код, который вообще может почти не использоваться. Это неэффективно. В микросервисной архитектуре же можно масштабировать точечно только те сервисы, которые под нагрузкой. Это экономит ресурсы и деньги. Легче уж собрать большую команду для микросервисной архитектуры и сэкономить кучу денег на ресурсах. И микросервисы позволяют собирать команды под конкретные области ответственности. В монолите, даже если он разбит на модули, вы всё равно завязаны на единый релиз, общую сборку, технологический стек... Это сказывается на скорости разработки. Вы правы, что микросервисы добавляют сложности сетевые ошибки, tracing, devops инфраструктура. Но у крупных компаний выбор не между "простота" и "сложность", а между "управляемо сложно" и "неуправляемый монстр". И да, вы правы монолит это не всегда плохо. Но его "простота" часто оказывается затратной, если смотреть в даль где у вас милионы пользователей генерируют миллиарды запросов в секунду, тут вы уже начинаете думать как экономить деньги

На самом деле, это большая дискуссия достойная отдельной статьи.

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

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

Еще 10 лет назад я имел дело с довольно здоровым монолитом который работал за NGINX на нескольких совершенно рядовых, копеечных серверах, и запросто сжирал нагрузку около 10000 запросов в секунду (без учета статики, разумеется, статические запросы разруливаются до приложения: CDN и вот это всё).

Вы скажите: "ха! 10000 - это же не миллиард".
Да. Но правда в том, что в мере вообще нету сервисов которые испытывают нагрузку в миллиард запросов в секунду. Даже Google Search испытывает нагрузки на 3 а то и на 4 порядка меньшие.

10000 в секунду - это уровень сайта из национального TOP 500(а то и TOP 100) страны вроде Германии, например. Это очень серьезно.

Я нехочу повторять банальность, но: вы не Google, не Amazon и не Netflix. 9 из 10 web-сервисов никогда не дорастут до нагрузок даже в 10000 запросов в секунду. А в непубличных, всяких внутренних Enterprise-приложениях - часто даже речь о таких нагрузках не идет: там другие проблемы.

Далее.
Идея о том, что я могу разные части системы писать на разном технологическом стеке звучит круто и гибко.
Но вот в реальности, вы хотите ровно обратного - вы хотите максимально сузить используемый технологический стек.
Потому что чем шире стек - тем сложнее поддерживать этот зоопарк.
Пример: Netflix. Евангелисты микросервсов, которых у них ~3000. За исключением буквально нескольких случаев (там где действительно были жирные причины) всё это Java + Spring Boot. Наверняка это так, не потому что там дураки консервативные сидят.

Вертикальное масштабирование то самое.
Не стоит пренебрежительно относится к этой теме. Это отлично работающая и очень дешевая тактика. Железо развивается. Системный софт развивается. И простой апгрейд сервака раз в несколько лет запросто даст существенно(бывает что и в разы) возросшую пропускную способность приложения. За даром. Если взрывного роста нет и не предвидеться - так можно жить десятилетиями компенсируя растущие нагрузки просто новым железом и новыми, более производительными, версиями системного софта. Я знаю десятки таких примеров.

Так вот. Подводя итог.
Стратегия Monolith First.
Делайте аккуратный монолит думая о том и как и какие его части можно будет выделить в отдельные сервисы в будущем. Понадобиться - выделите без больших проблем: ваша архитектура будет готова к этому. Но есть серьезные шансы, что не понадобиться.

супер статья вобще спасибо!

очень грамотно всё расписано и хорошие примеры. очень круто!

последние лет 10 делаю монолиты но с service-ready архитектурой:
все данные разделены на контексты, хоть и в одной базе но изолированные.
всё общение с базой только через DAL - можно запускать несколько инстансов со своими базами и синхронизацию прикручивать отдельно когда нужно масштабирование.
совместные запросы к нескольким контекстам - через транзакцию.
потенциально длинные задачи хорошо ложатся в HangFire
SignalR сообщения. всё это на порядок проще чем полноценные микросервисы но если надо будет перевести монолит из такого состояния в сервисы то это гораздо проще.
тестирование делаю на тестовой базе через DAL и не заморачиваюсь с моками
никакой логики в базе - беру все данные и вычисляю всё в самих моделях, это позволяет хорошо писать юнит тесты без использования базы чисто на логику классов если чтото сложное

Sign up to leave a comment.

Articles