В большом количестве статей, источников микросервисы, помимо всего прочего, представляются как способ построить масштабируемое решение. Рассмотрим на примерах, почему это не так. А так же попытаемся внести свою лепту в извечный вопрос:
Что лучше: монолит или микросервис?
Рассмотрим пример.
Допустим, у нас есть микросервис (лямбда) A
, выполняющий авторизационные запросы "имеет ли право пользователь выполнить операцию?".
Поскольку изолированно такой микросервис существовать не может, то в паре с ним существует другой микросервис (лямбда) B
, который сохраняет в хранилище список соответствий пользователи-права.
Примерная схема микросервисов (лямбд) показана на рисунке:
Обе лямбды/микросервиса вместе образуют классический Entity-микросервис: занимающийся инкапсуляцией работы с сущностью "пользователи".
В результате изменений в пользовательских данных (регистрация новых пользователей, ограничения на существующих и т.п.) микросервис B
"следит" за актуальностью данных в хранилище, которое использует микросервис A
для выполнения авторизационных запросов.
Простая схема. Просто устроена, надёжно работает.
Предположим, что количество пользователей, подключающихся к нашей системе, растёт. Каковы "узкие" места в этой архитектуре?
нагрузка на CPU в микросервисе
A
нагрузка io-read/select в БД
нагрузка на CPU в микросервисе
B
нагрузка io-write в БД
Вопросы с CPU в микросервисах решаются просто добавлением экземпляров в игру. Здесь масштабирование простое, и не стоит его обсуждать:
Давайте посмотрим, что будет с ростом нагрузки на микросервис A
и B
?
В определённый момент времени БД перестанет справляться с потоком запросов на чтение от микросервиса A
. При наступлении этих проблем обычно вводят в игру RO-реплики БД:
Поскольку микросервис A
не модифицирует записи в БД, то добавлением реплик к БД можно решить практически все вопросы масштабирования этого микросервиса.
Но вот вопрос: а что делать, когда микросервис B
приведёт master-БД к лимиту, определённому максимумом нагрузки на запись (io-write)?
Вариантов решения этих проблем довольно немного. Все они сводятся к тому, чтоб распределить запись в БД по нескольким хостам. Используем схему шардинга или иной масштабируемый multi-master:
Вместо одной БД у нас имеется X
шардов БД, позволяющих масштабировать нагрузку на запись, и к каждому шарду - реплики (всего - Y
), позволяющие масштабировать нагрузку на чтение.
Итого:
По мере роста нагрузки в нашем примере сами микросервисы претерпели немного изменений. Большинство изменений при масштабировании было в хранилище данных.
Если рассмотреть более обобщённо, то при масштабировании микросервисная архитектура сталкивается со следующими проблемами масштабирования:
Ограничения CPU на хостах
Ограничения IO в хранилищах данных
Ограничения пропускной способности сети между хостами
Способы преодоления этих проблем масштабирования ничем не отличаются от способов, применяемых в немикросервисных архитектурах. Мало того, третья проблема встречается в основном именно в микросервисной архитектуре.
Выводы
Микросервисная архитектура не является способом масштабирования проекта. Микросервисная архитектура - это способ разделения проекта на модули и инкапсуляции кода (и данных).
Основу масштабирования практически любого большого проекта следует искать в области хранения и обработки хранящихся данных.
Монолиты и микросервисы: граница
Если рассмотреть развитие аналогичного монолитного сервиса примерно в таком же ключе, как мы рассматривали выше развитие микросервиса, то в результате его развития будут пройдены те же стадии преодоления проблем. В итоге структура монолита будет включать в себя те же самые компоненты. А если взглянуть на серверное разделение, то будут включать в себя выделенный сервер (кластер серверов) авторизации и сервер регистрации пользователей. Однако, эта структура будет оставаться монолитной.
В чём же отличие? Почему монолит, разделённый на сервисы, остаётся монолитом? Потому что во всех его сервисах используется единая кодовая база.
Если один и тот же код, не будучи выделен в библиотеку, работает во множестве микросервисов, то это - монолитная архитектура.
Построение проектов с нуля: Монолит vs микросервис
Если не рассматривать вновь запускаемые проекты на лямбдах/FaaS, то можно отметить одну чуть ли не во всех проектах встречающуюся особенность:
Как правило, проект на стадии запуска реализации и на стадии запуска MVP отличается довольно сильно. Видение бизнес-развития проекта в стадии после MVP отличается от стартового ещё сильнее. И, чем больше времени проект развивается, тем сильнее эти отличия.
Бизнес-требования к стартующему проекту обычно меняются прямо в процессе реализации его MVP. Да, это не для всех случаев так, но для огромного пула стартапов это именно так.
Что из этого следует? Из этого следует эмпирическое правило: для запуска стартапов необходимо выбирать технологии, исходя из критериев:
в дальнейшем понятно, как масштабировать (в основном, это относится к хранилищу)
сравнительно просто рефакторить (это относится к выбору технологии построения кода)
простое покрытие автоматическими тестами
Для рефакторинга и простоты покрытия тестами монолиты подходят идеально - позволяют работать в режиме "сперва взлетаем, а затем думаем о том, какие крылья нам лучше использовать".
И, исходя из написанного, энергию вечного спора "монолит vs микросервис" на стадии запуска проекта надо направить в русло проработки хранилища данных с изначальной ориентацией на масштабирование. А в процессе развития монолит и микросервис будут иметь весьма похожую архитектуру. Настолько похожую, что отличить их друг от друга будет сложно.
Joka
У вас в примере распределённый монолит, а не микросервис. Особенностью микросервиса является независимый деплой и хранилище. А у вас хранилище одна. Два сервиса зависит друг от друга через схему хранилища. Это никак не микросервисы.
git-merge Автор
здесь по факту описан один микросервис.
но поделен он на два как раз из за нагрузки.
Практически любой микросервис с хранилищем данных — будет иметь
Соответственно с ростом нагрузки разделение на два сервиса как может появиться так и не появляться.
Я в статье считал что оно объективно нужно.
Joka
Разделение выполнено неверно. Сервис авторизации должен просить у сервиса регистрации данные при первом запросе и потом кешировать у себя. Или событийная структура. Или пушить данные в сервис авторизации при изменениях в сервисе регистрации. И тд.
git-merge Автор
кеширование по сути и выполняет B.
кеширование в любом случае происходит в БД (если данных много), а все проблемы масштабирования придут к БД
все эти варианты не решают проблем масштабирования
Joka
это смотря в каком ключе посмотреть
микросервисы решают проблему масштабирования команд, потому как, обычно, каждая команда отвечает за микросервис целиком. Команда просто исполняет возложенный на нее контракт (ну допустим АПИ) а что происходит внутри сервиса это уже никого не волнует.
ну а если вы говорите о масштабировании техническом в виде возможность выдержать большие нагрузки, то тут все те же проблемы что и у монолита, потому что каждый отдельный микросервис — это монолит.
вы можете использовать БД как кеширующий слой, но обычно это является оверхедом и в большинстве случаев роль кеширующего случая выполняют такие хранилища как memcached, redis и тд.
git-merge Автор
редис — это же тоже БД
и у редиса точно так же проблемы с RPS на запись и RPS на чтение
LarexSetch
Не все проблемы можно решить redis memcached, что если у тебя база из 10 мин пользователей и информация о каждом занимает 10Кб. Что бы этот объем положить в память потребуется ~100Gb оперативной памяти.
Собственно нужно отталкиваться ситуации в проекте. Может для пользователя достаточно хранить и 1кб данных, а это уже 10Gb, а значит стратегия масштабирования будет другой.
Поэтому в некоторых кейсах использование бд как основной источник данных это вполне логичный подход.
Кстати мы можем даже изменить стратегию хранения данных в кэше, например узнав что активное число пользователей не все 10млн, а 100тыс. А значит мы можем хранить только 100тыс пользователей остальных (самых старых) выкидывать из кэша.
peacecoder85
Вы забываете о таком понятии как партицирование и реплики. Вуаля и ваша БД снова летает
git-merge Автор
Так Вы подтверждаете то что написано в статье: Вы решили проблему масштабирования использовав реорганизацию базы данных.
Об этом ведь и статья!
peacecoder85
То, что я написал, это масштабирование БД, если БД не справляется в рамках одного микросервиса. Смотрите мой комментарий ниже по поводу Вашей ситуации в статье
phtaran
думаю это верное замечание, но насколько я понял, автор немного о другом — если затык в базе то микросервисами его нельзя преодолеть. Что например делать если разбивка правильная но на микросервис идёт такой объём который не держит база позади него?
Ещё, хм, есть некоторое сомнение по части вот этой труёвости разбивки. Мне кажется что на практике такое случается что разные микросервисы работают с одной и той же субд. Я не уверен что все контраргументы против определённой технологии или подхода можно парировать говоря «это просто не тру-реализация». Т.к. в таком случае это софистическая ловушка, т.к. это не учитывает что на практике невозможно всегда выполнить 100% идеальное проектирование, где-то будут компромиссы, и если идеологическую труёвость сложно достичь — это тоже можно отнести к недостаткам технологии/подхода, и он бы прямо так и звучал: «сложно выполнить правильно, а если неправильно — преимущества существенно нивелируются»
(заголовок статьи)
ну в теории считается что после разбивки монолита отдельные его части (микросервисы) можно масштабировать по разному. Если на 1 функцию монолита нагрузка 1Х а на другую 40Х то можно вынести тот кусок что 40Х и скейлить его до нужного capacity. Если конечно получится обеспечить это на уровне субд.