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

Что лучше: монолит или микросервис?

Рассмотрим пример.

Допустим, у нас есть микросервис (лямбда) A, выполняющий авторизационные запросы "имеет ли право пользователь выполнить операцию?".

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

Примерная схема микросервисов (лямбд) показана на рисунке:

Рисунок 1
Рисунок 1

Обе лямбды/микросервиса вместе образуют классический Entity-микросервис: занимающийся инкапсуляцией работы с сущностью "пользователи".

В результате изменений в пользовательских данных (регистрация новых пользователей, ограничения на существующих и т.п.) микросервис B "следит" за актуальностью данных в хранилище, которое использует микросервис A для выполнения авторизационных запросов.

Простая схема. Просто устроена, надёжно работает.

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

  • нагрузка на CPU в микросервисе A

  • нагрузка io-read/select в БД

  • нагрузка на CPU в микросервисе B

  • нагрузка io-write в БД

Вопросы с CPU в микросервисах решаются просто добавлением экземпляров в игру. Здесь масштабирование простое, и не стоит его обсуждать:

Рисунок 2
Рисунок 2

Давайте посмотрим, что будет с ростом нагрузки на микросервис A и B?

В определённый момент времени БД перестанет справляться с потоком запросов на чтение от микросервиса A. При наступлении этих проблем обычно вводят в игру RO-реплики БД:

Рисунок 3
Рисунок 3

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

Но вот вопрос: а что делать, когда микросервис B приведёт master-БД к лимиту, определённому максимумом нагрузки на запись (io-write)?

Вариантов решения этих проблем довольно немного. Все они сводятся к тому, чтоб распределить запись в БД по нескольким хостам. Используем схему шардинга или иной масштабируемый multi-master:

Рисунок 4
Рисунок 4

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

Итого:

По мере роста нагрузки в нашем примере сами микросервисы претерпели немного изменений. Большинство изменений при масштабировании было в хранилище данных.

Если рассмотреть более обобщённо, то при масштабировании микросервисная архитектура сталкивается со следующими проблемами масштабирования:

  1. Ограничения CPU на хостах

  2. Ограничения IO в хранилищах данных

  3. Ограничения пропускной способности сети между хостами

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

Выводы

  1. Микросервисная архитектура не является способом масштабирования проекта. Микросервисная архитектура - это способ разделения проекта на модули и инкапсуляции кода (и данных).

  2. Основу масштабирования практически любого большого проекта следует искать в области хранения и обработки хранящихся данных.

Монолиты и микросервисы: граница

Если рассмотреть развитие аналогичного монолитного сервиса примерно в таком же ключе, как мы рассматривали выше развитие микросервиса, то в результате его развития будут пройдены те же стадии преодоления проблем. В итоге структура монолита будет включать в себя те же самые компоненты. А если взглянуть на серверное разделение, то будут включать в себя выделенный сервер (кластер серверов) авторизации и сервер регистрации пользователей. Однако, эта структура будет оставаться монолитной.

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

Если один и тот же код, не будучи выделен в библиотеку, работает во множестве микросервисов, то это - монолитная архитектура.

Построение проектов с нуля: Монолит vs микросервис

Если не рассматривать вновь запускаемые проекты на лямбдах/FaaS, то можно отметить одну чуть ли не во всех проектах встречающуюся особенность:

Как правило, проект на стадии запуска реализации и на стадии запуска MVP отличается довольно сильно. Видение бизнес-развития проекта в стадии после MVP отличается от стартового ещё сильнее. И, чем больше времени проект развивается, тем сильнее эти отличия.

Бизнес-требования к стартующему проекту обычно меняются прямо в процессе реализации его MVP. Да, это не для всех случаев так, но для огромного пула стартапов это именно так.

Что из этого следует? Из этого следует эмпирическое правило: для запуска стартапов необходимо выбирать технологии, исходя из критериев:

  • в дальнейшем понятно, как масштабировать (в основном, это относится к хранилищу)

  • сравнительно просто рефакторить (это относится к выбору технологии построения кода)

  • простое покрытие автоматическими тестами

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

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