Comments 54
У вас в примере распределённый монолит, а не микросервис. Особенностью микросервиса является независимый деплой и хранилище. А у вас хранилище одна. Два сервиса зависит друг от друга через схему хранилища. Это никак не микросервисы.
здесь по факту описан один микросервис.
но поделен он на два как раз из за нагрузки.
Практически любой микросервис с хранилищем данных — будет иметь
- модифицирующие БД запросы (иначе зачем БД)
- запросы немодифицирующие
Соответственно с ростом нагрузки разделение на два сервиса как может появиться так и не появляться.
Я в статье считал что оно объективно нужно.
Разделение выполнено неверно. Сервис авторизации должен просить у сервиса регистрации данные при первом запросе и потом кешировать у себя. Или событийная структура. Или пушить данные в сервис авторизации при изменениях в сервисе регистрации. И тд.
Сервис авторизации должен просить у сервиса регистрации данные при первом запросе и потом кешировать у себя.
кеширование по сути и выполняет B.
кеширование в любом случае происходит в БД (если данных много), а все проблемы масштабирования придут к БД
Сервис авторизации должен просить у сервиса регистрации данные при первом запросе и потом кешировать у себя. Или событийная структура. Или пушить данные в сервис авторизации при изменениях в сервисе регистрации. И тд.
все эти варианты не решают проблем масштабирования
все эти варианты не решают проблем масштабирования
это смотря в каком ключе посмотреть
микросервисы решают проблему масштабирования команд, потому как, обычно, каждая команда отвечает за микросервис целиком. Команда просто исполняет возложенный на нее контракт (ну допустим АПИ) а что происходит внутри сервиса это уже никого не волнует.
ну а если вы говорите о масштабировании техническом в виде возможность выдержать большие нагрузки, то тут все те же проблемы что и у монолита, потому что каждый отдельный микросервис — это монолит.
кеширование в любом случае происходит в БД (если данных много)
вы можете использовать БД как кеширующий слой, но обычно это является оверхедом и в большинстве случаев роль кеширующего случая выполняют такие хранилища как memcached, redis и тд.
редис — это же тоже БД
и у редиса точно так же проблемы с RPS на запись и RPS на чтение
Не все проблемы можно решить redis memcached, что если у тебя база из 10 мин пользователей и информация о каждом занимает 10Кб. Что бы этот объем положить в память потребуется ~100Gb оперативной памяти.
Собственно нужно отталкиваться ситуации в проекте. Может для пользователя достаточно хранить и 1кб данных, а это уже 10Gb, а значит стратегия масштабирования будет другой.
Поэтому в некоторых кейсах использование бд как основной источник данных это вполне логичный подход.
Кстати мы можем даже изменить стратегию хранения данных в кэше, например узнав что активное число пользователей не все 10млн, а 100тыс. А значит мы можем хранить только 100тыс пользователей остальных (самых старых) выкидывать из кэша.
А в чем же оверхед, если БД уже есть, а все остальное надо разворачивать и поддерживать?
Или это требование "чистых микросервисов" - не делить базу? Могут ли быть из этого исключения с целью упрощения кода-поддержки в ущерб чистой архитектуре?
Вы забываете о таком понятии как партицирование и реплики. Вуаля и ваша БД снова летает
Так Вы подтверждаете то что написано в статье: Вы решили проблему масштабирования использовав реорганизацию базы данных.
Об этом ведь и статья!
У вас в примере распределённый монолит, а не микросервис. Особенностью микросервиса является независимый деплой и хранилище. А у вас хранилище одна. Два сервиса зависит друг от друга через схему хранилища. Это никак не микросервисы.
думаю это верное замечание, но насколько я понял, автор немного о другом — если затык в базе то микросервисами его нельзя преодолеть. Что например делать если разбивка правильная но на микросервис идёт такой объём который не держит база позади него?
Ещё, хм, есть некоторое сомнение по части вот этой труёвости разбивки. Мне кажется что на практике такое случается что разные микросервисы работают с одной и той же субд. Я не уверен что все контраргументы против определённой технологии или подхода можно парировать говоря «это просто не тру-реализация». Т.к. в таком случае это софистическая ловушка, т.к. это не учитывает что на практике невозможно всегда выполнить 100% идеальное проектирование, где-то будут компромиссы, и если идеологическую труёвость сложно достичь — это тоже можно отнести к недостаткам технологии/подхода, и он бы прямо так и звучал: «сложно выполнить правильно, а если неправильно — преимущества существенно нивелируются»
Микросервисы — не способ масштабироваться
(заголовок статьи)
ну в теории считается что после разбивки монолита отдельные его части (микросервисы) можно масштабировать по разному. Если на 1 функцию монолита нагрузка 1Х а на другую 40Х то можно вынести тот кусок что 40Х и скейлить его до нужного capacity. Если конечно получится обеспечить это на уровне субд.
так это не фолт телеранс, одну базу на все сервисы. надо для каждого стейтфул сервиса базу отдельную и будет масштабирование
Вы с одной стороны правы (микросервисы, действительно не решают проблему горизонтального масштабирования нагрузки), но приходите к «опасным» выводам. Основная выгода от микросервисов проявляется, когда ваша команда становится достаточно большой, чтобы проявлялись проблемы единой кодовой базы (монолита). 10 человек могут работать с монолитом эффективно, 50 - уже не могут: появляются мерж-конфликты, регрессивное тестирование становится дорогим, а релизы - праздником, а не повседневностью как должно быть. Монолит (в смысле деплоя) - это такая штука, которую вроде бы и можно сделать «правильно» - выделив и изолировав модули, - но она не обеспечивает надлежащего контроля над границами этих самых модулей; в условиях «релиз надо на вчера» (как обычно бывает), монолит склонен к скатыватнию в спагетти-код, где всё зависит от всего. Именно поэтому стоит начинать с микросервисов сразу - не дожидаясь пока у вас накопится тонна неподдерживаемого кода. Для этого, конечно, нужна команда, которая умеет правильно готовить devops часть, ну а технологий, сегодня, (благо) хватает, велосипед изобретать не нужно.
А я написал, что микросервис — это способ инкапсуляции/разделения кода на модули.
тут я с Вами согласен.
поэтому стоит начинать с микросервисов сразу
а вот тут нет.
Начиная с микросервисов сразу мы отсрачиваем запуск проекта в целом.
Проект, запущенный позднее == проект не принесший денег за время задержки запуска
Проект, запущенный позднее на неудачной бизнес идее == проект унёсший в разы больше денег на этот эксперимент чем мог бы
Если проект удачный (с точки зрения бизнеса), то более ранний запуск проекта с лихвой окупит последующий рефакторинг (если он понадобится)
Ну и
10 человек могут работать с монолитом эффективно, 50 — уже не могут
Проектам редко нужно 50 человек. Скорее всего эти 50 пилят 5 разных проектов. Смежных, но разных.
Если это не так, то тут в консерватории надо что-то поправить.
в "Мифическом человеко-месяце" хорошо это расписано :)
Начиная с микросервисов сразу мы отсрачиваем запуск проекта в целом.
Именно поэтому я добавил в конце оговорку про «умеют готовить devops» - если у команды есть опыт в этой сфере, то накладные расходы будут приемлемыми. Если опыта нет, то вы правы и надо пилить как получается в надежде получить опыт и прибыль от MVP; однако, надо помнить что в будущем таки придётся переделывать, если стартап взлетит; дальнейшие соображения зависят от специфики продукта, количества инвестиций и т.п.
Проектам редко нужно 50 человек. Скорее всего эти 50 пилят 5 разных проектов. Смежных, но разных.
Склонен с вами не согласится - «типичным магазинам» не нужен, а вот успешный энтерпрайз-грейд SaaS стартап - это как раз и есть человек 50, при этом все работают над одним продуктом (но над разными фичами). В таких ситуациях микросервисы являются той самой штукой, которая делает отдельные команды независимыми при условии правильного разделения зон ответственности.
Не стоит безапелляционно говорить "не могут". GitLab - 400 инженеров, монолит с одним вынесенным сервисом (gitaly), деплои два раза в день
А, скажем, могомодульный монолит не решит проблему спутанности, при этом оставив простоту взаимодействия?
Есть куча проектов, которые в одной репе разрабатываются спокойно сотнями людей. ОС (Linux, Windows), десктопные приложения (Chromium), всякие БД и прочие кубернетесы. Просто куча примеров.
И часто это проекты посложнее этих наших всяких веб-приложений - десктоп или ОС писать посложнее веба.
Также есть много успешных бизнесов, которые пишут (в т.ч. и на хабре), как распиливали монолит на части. Часто - когда уже стали большие и богатые. А вот обратного - бизнесов, которые пишут "мы начали с микросервисов, и пришли к успеху" - я что-то не очень вижу.
В общем, с чего все взяли, что если код разложить по репам и обмазать докером - это путь к успеху? С чего все взяли, что если код сложить в одну репу - будет помойка?. Внутри монорепы можно точно также построить границы, обозначить API между ними. Если только от того, что с монорепой на порядочек деньги можно не тратить, а когда много реп - ты вынужден нанять девопсов и строить хоть какие-то API.
Делать микросервисы на проекте от входа - в 90% случаев - самоубийство для бизнеса:
микросервисное приложение предельно сложно рефакторить. Цена ошибки типа "мы не угадали как попилить на микросервисы" - предельно высока. А когда только начинают - в большинстве случаев даже не знают чего хотят и куда приедем
несмотря на все девопсы, разработка все равно будет дороже. Хотя-бы потому что на надо кому-то этим девопсом заниматься.
весь это цирк с breaking changes в API - либо деплоим все одновременно, либо заморачиваемся backward compatibility в API (на старте проекта, да, когда все старые версии все равно выкинутся)
пока эти все девопсы устаканятся, пока это все поедет - конкуренты уже на луне будут
С микросервисов начинать иногда можно, например если требования предельно ясны и меняться точно не будут, и уже понятно что система будет огромной, и ей надо люто-бешенно скейлиться. Но такие кейсы - это единицы процентов.
del
делить код на модули микросервисованием? При таком подходе код становится сложно читать именно из-за распределенности логики. Вам прилетело неправильное значение в одном из полей запроса? Авторы сервиса - отправителя не в курсе, лезете в код. Тратите N времени. прослеживаете поле сквозь код всего сервиса вместе с запросом только чтобы найти, что оно такое прилетело вообще из третьего сервиса, цикл...
А масштабируемость микросервисов в другом заключается - упираясь в CPU микросервиса А, диск B или память C, вы просто раскатываете контейнеры A/B/C на пропорционально большее число машинок, сводя реальное потребление ресурсов максимально близко к необходимому.
Для понимания прохождения данных через цепочку сервисов есть трассировка.
Пробираться через спагетти сложно вне зависимости от того, монолит это или микросервисы, в микросервисах даже проще поскольку у сервиса короткий промежуток между входом и выходом, а границы сервиса покрыты логированием даже если в коде его не сделали каким нибудь nginx.
Вы взяли за основу неправильно построенные микросервисы и доказали, что вы их неправильно разделили. Причем проблему хапнули как раз в том месте, где неправильно - бд
так, что тут неправильного?
давайте по порядку.
- система построенная на лямбдах/FaaS — является микросервисной? да является
- в системе на лямбдах будут 2+ лямбды смотрящие в одну БД? обязательно будут
- можно считать лямбду микросервисом? можно. это именно микросервис и есть
Википедия: Микросервисная архитектура — вариант сервис-ориентированной архитектуры программного обеспечения, направленный на взаимодействие насколько это возможно небольших, слабо связанных и легко изменяемых модулей — микросервисов, получивший распространение в середине 2010-х годов в связи с развитием практик гибкой разработки и DevOps
Микросервисы должны быть независимыми не только в плане кодовой базы, но и в плане хранилища данных
- кому должны?
- кто это определил?
- можно ли считать лямбду микросервисом?
- чужая БД, нарисованная на схеме как БД может представляться каскадом микросервисов. И "читать чужую БД" будет соответствовать "делать запрос по HTTP с установленным протоколом"
Если же вы сознательно завязываетесь на детали чужой имплементации, то вы сам себе злобный буратин.
предложите правильное разделение на микросервисы аналогичной архитектуры. Как бы Вы не разделили, решать проблемы масштабирования Вы будете в районе хранилища данных.
Вообще полезно даже в рамках одного продукта рассматривать другие (микро-)сервисы как 3rd-party. У вас есть АПИ, а остальное — черный ящик.
то же относится и к обычному программированию. Делите программу на модули/классы и рассматриваете их как 3rd-party. В этом случае программа будет максимально редактируема.
Тогда и не придется лазить в другие сервисы, чтоб понять, что там не так и с масштабированием проблем не будет.
Берём ЛЮБОЙ микросервис и увеличиваем нагрузку. Проблемы будут.
Вам чуть ли не в каждом комменте указывают на проблему, а вы в упор не видите. Смысл вам что-то объяснять?)
я не воспринимаю критику вида "это неправильно" — это не конструктивно
я хорошо воспринимаю критику вида "правильно будет вот так"
пока все предложенные варианты "вот так" не противоречат моей статье: проблемы масштабирования сосредоточены в области хранилища данных
Как я (и не только я) писал выше, тут нужны раздельные хранилища.
- как раздельные хранилища помогут в описанной проблеме?
- нагрузка на чтение, создаваемая лямбдой A и на запись, создаваемая лямбдой B от разделения БД на части не поменяется. Плюс появится транспорт C из БД B в БД A.
Разделение хранилищ предотвратит проблему как таковую.
каким образом?
- кто будет наполнять хранилище лямбды/микросервиса A?
- трафик write этого наполнения будет меньше? почему?
- появляется вопрос консистентности данных в хранилищах A и B
Вы вероятно хотели сказать "разделение хранилищ усугубит проблему"?
Про лямбды не понял ваш тезис.
можно считать что микросервис A и микросервис B — это две лямбды. FaaS итп.
Почему монолит, разделённый на сервисы, остаётся монолитом? Потому что во всех его сервисах используется единая кодовая база.
Так давайте поговорим, сначала, о модульности «кодовой базы», а потом о ее масштабировании.
Я недавно озадачился модульностью проекта на С++, пишущегося с нуля. Речь идет о десктопном приложении, для которого, в первую очередь, надо построить модульный графический интерфейс пользователя. Как это сделать красиво? Обычно предлагается система плагинов, но хороших примеров мало, тем более с ориентацией на «форточки», а не на консоль.
Размышления приводят к выводу, что помимо внешних плагинов, в dll, можно вести речь и о внутренних плагинах, т.е., о независимых модулях «кодовой базы».
Но, что мешает созданию модулей? Думаю, это сильная связь между программными компонентами. Которая заключается в трех вещах:
1. Общие (глобальные) переменные.
2. Общее меню.
3. Регистрация и создание компонентов, а также их обработка в главном цикле сообщений приложения.
Внутренние и внешние плагины отличаются способом подключения модулей (бинарных либо кодовых), и способом их регистрации (статической либо динамической). Способ универсальной обработки, не изменяемый для любых видов плагинов, может быть общим.
В общем, интересно посмотреть хотя бы простейший пример на эту тему, либо изобрести собственный велосипед, если Интернет здесь не поможет. Пока я лично склоняюсь ко второму варианту.
Цитата из той же Википедии:
Философия микросервисов фактически копирует философию Unix, согласно которой каждая программа должна «делать что-то одно, и делать это хорошо» и взаимодействовать с другими программами простыми средствами: микросервисы минимальны и предназначаются для единственной функции.
Я скажу даже проще:
Микросервис - это независимая часть большой программы, которая владеет данными и функционалом их модификации и больше никто этого делать не может. Если говорить в рамках MVC, то микросервис это MC
В вашем случае идёт пересечение владения данными, которые используются двумя частями. Разделите их на пользователей и авторизации и сразу все упростится. В идеале это две разные БД на разных машинах. Надо проверить, с микросервиса авторизации, существует ли пользователь? Обращаетесь к сервису пользователей, а не лезете в ее БД.
Если другими словами: в любой момент любой микросервис может поменять модель хранения данных и если лезть в них, обходя ответственного, все сломается. Всегда помните это и МС архитектура подстроится сама собой
Разделите их на пользователей и авторизации и сразу все упростится. В идеале это две разные БД на разных машинах.
проблемы, описанные в статье, никуда не денутся (умножатся на два разве что).
Мы ведь в статье обсуждаем нагрузки такого уровня, что даже разделяем сервисы пишущие и читающие по разным машинам. И БД получается кластером. См. последний рисунок.
Как уже вам много раз говорили, микросервисная архитектура предполагает выделение под каждый микросервис отдельной базы данных. Это не соображение, которое можно доказать, а часть определения. Тем не менее вы можете почитать внешние источники, например https://microservices.io/patterns/microservices.html
Each service has its own database in order to be decoupled from other services
Почему shared database является антипаттерном можно почитать там же https://microservices.io/patterns/data/shared-database.html TLDR: расшаренная база повышает взаимосвязь сервисов и предположения друг о друге, сложно деплоить и создаёт проблемы с безопасностью
Почему shared database является антипаттерном можно почитать там же microservices.io/patterns/data/shared-database.html TLDR: расшаренная база повышает взаимосвязь сервисов и предположения друг о друге, сложно деплоить и создаёт проблемы с безопасностью
ну часто говорят что микросервисы — это масштабирование разработки (я не смотрел но думаю это тоже часть определения). Предположим ситуацию: cервис большой, логики в нём много, и решили чтоб масштабировать разработку, разделить этот сервис на 2 — не суть важно по какому признакму, может отдельно рид и райт, может разделили по доменной логики. Но например с точки зрения данных — это одни и те же данные и смысла делить их на две базы нет, а может даже и невозможно. Тогда, следуя логике этого же определения, нужно для 2-го сервиса завести отдельную базу (даже если эта база будет точной копией базы другого сервиса)? По-моему тут уже есть конфликт двух максим из одного и того же определения.
У многих вообще есть эти чистые в вакууме микросервисы? По-моему у большинства гибриды
Как уже вам много раз говорили, микросервисная архитектура предполагает выделение под каждый микросервис отдельной базы данных.
предположим что это так (хотя это не так).
Однако как Ваш тезис опровергает основной тезис статьи о том, что масштабирование систем чаще всего лежит около системы хранения данных?
Почему shared database является антипаттерном можно почитать там же
я с этим согласен, однако если Вам так не нравится Вы можете
- рассматривать весь пример как один микросервис с двумя методами: update/get (об этом написано в статье).
- предложить True вариант и показать что он масштабируется лучше, не?
В посте описан какой-то антипаттерн, если бы там была озвучена мысль "не стройте микросервисную архитектуру по таким шаблонам", то мне бы зашло. А так не зашло.
Если конкретную реализацию обсуждать, то по хорошему должен быть микросервис авторизации, который, например выпускает JWT для клиентов и должен быть второй микросервис, который проверяет подпись приходящих запросов, проверяя их публичным ключом первого микросервиса.
В такой реализации не нужна база данных чтобы синхронизировать состояния, достаточно при запуске второго мс скачать себе публичный ключ.
Ну да, там есть проблема с отзывами ключей, но она тоже решается аналогом CRL.
1. А давай-те мы не дублировать код в разных проектах, но будем повторно? Здравая мысль. придумали библиотеки. появился DLL Hell, в том или ином виде. Или коллизии возникали или распихивали разные версии DLL по разным местам. Компромисс достигли, но ценой overhead по месту\памяти. Зато «удобно» получилось для всяких RPC\DCOM\Corba и т.д. — надо ресурсы, поднял инстанс. Прикрутили IDL в качестве метаописания.
2. А давайте мы не будем дублировать функционал? Это же так удобно — дернул функцию и получил результат. Сделали web сервисы — это же так удобно масштабировать, сервер поднял и все довольны, вот тебе новые ресурсы. Все начиналось также «A +B = ...». Опять фигня — опять коллизии, опять вопросы версионности, только уже не DLL, а… сервисов. Зато удобно получилось для всяких SOA и прочих шинных архитектур — надо ресурсы, поднял инстанс. Прикрутили WSDL в качестве метаописание.
2.1. Web service — сложно, надо думать проектировать. Давайте выкинем «не нужное» и быстро налепим WebAPI\REST. Вышло убогенько (по началу), проблем кардинально не решило ни с версионность, ни с самоописанием, ни с масштабированием. Ну прикрутили метаописание Swagger\OpenAPI. Надо ресурсы, поднял инстанс. Проблемы с масштабируемостью тоже не решило.
4. Давайте нарежем на куски «изолированный функционал» и сделаем, что бы оно «само» магией поднималось? Сделали микросервисы… и опять фигня получается. Хотя сюда уже прикрутили… «домены предметной области».
Все время натыкались на одни и те же вопросы: как на границе систем сделать так, что бы связанность было минимум, при этом не получив граблями «не верных данных» в лоб и как бы «вот этот кусочек» сделать побыстрее? Допустим — бинарную декларацию IDL заменили на OpenAPI, «жесткое» бинарное связывание в рамках одного процесса OS\PC заменили на «мягкое» связывание в рамках «бизнес решения» и «приватного облака» в дата центре.
Сделать кусочек побыстрее — заколебались ковыряться с профайлерами и выравнивать «по страницам» и регистрам CPU, решили добавить CPU по «быстрому».
Если смотреть на этот цирк с исторической ретроспективой — 30 лет не могут найти «каноническую архитектуру» распределенных приложений и масштабирования, так что бы подходило всем. Может стоит смотреть на бизнес-цели, на стоимость владения решением, на профит? Не пытаться достичь придуманного «единорога в вакууме»?
Это похоже попытку заставить кирпич двигаться со скоростью света — поначалу просто и дешево, но чем дальше, тем больше проблем и больше усилий, непропорционально результату, надо прилагать. Есть же критерий разумной достаточности.
2.1. Web service — сложно, надо думать проектировать. Давайте выкинем «не нужное» и быстро налепим WebAPI\REST. Вышло убогенько (по началу), проблем кардинально не решило ни с версионность, ни с самоописанием, ни с масштабированием. Ну прикрутили метаописание Swagger\OpenAPI. Надо ресурсы, поднял инстанс. Проблемы с масштабируемостью тоже не решило.
я свичнулся в серверную разработку когда REST уже уверенно шагал (т.е. я немного не в конктексте). Но есть ощущение что REST для микросервисов очень неудобен — много ручного хозяйства, которое как кажется можно было бы переложить на инструмент. Например валидация контрактов между сервисами выглядит как костыль над отсутствием четкого контракта из коробки. Если нужно что-то сделать — нужно мыслить в терминах сущностей, и часто REST представляет собой грязную версию RPC — об этом есть наверное не одна статья. Ретраи, circuit breaker-ы -тоже выглядит так что это должно настраиваться просто и без написания логики на уровне приложения. Ну и самое главное напоследок — если уж система распределённая, то напрашивается сделать работу с удалёнными сервисами по возможности похожей на работу с локальными сервисами (в том же инстансе) и RPC смотрится навскидку лучше. Возможно старые варианты RPC имели какие-то недостатки, но нынешний gRPC кажется неплохим решением для вызовов между микросервисами. Наружу можно пробросить REST а внутри за gateway работать по gRPC
Но есть ощущение что REST для микросервисов очень неудобен — много ручного хозяйства, которое как кажется можно было бы переложить на инструмент.
Чем в общем-то WSDL и занимался, как и всякие расширения вроде WS-AtomicTransaction, WS-Security, WS-Context & etc. Если смотреть в исторической перспективе WS в изначальном виде не сохранял контекст (Stateless), практически эквивалентно REST, и при этом не был ограничен транспортом — хоть почтовыми голубями на перфокарте отправляй сообщения. Со временем «оброс» примочками (WS- Extensions) для решения проблем, и стал «тяжелый». На самом деле — просто заставлял проектировать перед тем как что-то имплементировать.
И заявленные цели REST были:
- performance in component interactions, which can be the dominant factor in user-perceived performance and network efficiency;
- simplicity of a uniform interface;
- reliability in the resistance to failure at the system level in the presence of failures within components, connectors, or data.
Да, сети «выросли» по надежности и пропускной способности, но по итогу сейчас «легкий» сериализованный объект порядка 300 мб json не вызывает вопросов, а задержки и гарантии доставки сообщения даже не принимается во внимание, не говоря реализации на уровне протокола.
Одну штуку делали специалисты с опытом, другую — хипстеры-стартаперы на самокатиках с закономерным итогом.
Микросервисы — не способ масштабироваться