В большом количестве статей, источников микросервисы, помимо всего прочего, представляются как способ построить масштабируемое решение. Рассмотрим на примерах, почему это не так. А так же попытаемся внести свою лепту в извечный вопрос:
Что лучше: монолит или микросервис?
Рассмотрим пример.
Допустим, у нас есть микросервис (лямбда) 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 микросервис" на стадии запуска проекта надо направить в русло проработки хранилища данных с изначальной ориентацией на масштабирование. А в процессе развития монолит и микросервис будут иметь весьма похожую архитектуру. Настолько похожую, что отличить их друг от друга будет сложно.