Сравниваем особенности микросервисной и монолитной архитектуры, их преимущества и недостатки. Статья подготовлена для Хабра по материалам нашего митапа Hot Backend, который прошел в Самаре 9 февраля 2019 года. Мы рассматриваем факторы выбора архитектуры в зависимости от конкретной задачи.

Еще лет 5 назад никто и не слышал о микросервисах. Однако, их популярность увеличивается из года в год, согласно статистике Google Trends.



Монолит и микросервисы: примеры


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



Микросервисная архитектура предполагает разбивку на модули, которые запускаются как отдельные процессы и могут иметь отдельные серверы. Каждый микросервис работает со своей базой данных, и все эти сервисы могут общаться между собой как синхронно (http), так и асинхронно. При этом для оптимизации архитектуры желательно минимизировать взаимосвязи между сервисами.

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



Микросервисы: преимущества


Можно выделить как минимум четыре плюса микросервисной архитектуры:

Независимое масштабирование и развертывание

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

Независимая разработка

Каждый микросервис (например, модуль памяти) можно разработать силами одной команды, чтобы увеличить скорость создания программного продукта.

Устойчивость

Отказ одного микросервиса не влияет на работоспособность других модулей.

Разнородность

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

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

Давайте разберемся, действительно ли всем следует переходить от монолита к микросервисам по примеру крупнейших брендов?

Переход на микросервисы: возможные затруднения


Проблема первая: декомпозиция

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

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



Проблема вторая: транзакции

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

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

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



Проблема третья: построение отчетов

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

Например, нам нужно вывести список компаний с определенными метриками. При выводе простого списка компаний все работает. А если нужно добавить метрики, которые лежат в другой базе данных? Да, мы можем сделать дополнительный запрос и по ИНН запросить метрики. А если этот список нужно фильтровать и сортировать? Список компаний может оказаться очень большим, и тогда нам приходится вводить дополнительный сервис со своей базой данных — отчеты.



Проблема четвертая: высокая сложность разработки

Работа над распределенными сервисами сложнее: все запросы осуществляются по сети и могут «заглючить», нужно предусмотреть callback механизм (будет ли он осуществлять вызов повторно? Сколько раз?). Это «кирпичики», которые постепенно накапливаются и вносят свой вклад в увеличение сложности проекта.

Сервисы могут быть разработаны несколькими разными командами, и нужно документировать их, поддерживать документацию в актуальном состоянии, предупреждать другие команды при смене версии. Это дополнительные трудозатраты.

Если у каждой команды независимый деплой, нужно поддерживать как минимум предыдущую версию и отключать ее только после того, как все потребители сервиса перейдут на новое API.
Конечно, мы можем вынести все API в некий артефакт, который будет общедоступен. Но, во-первых, сервисы могут быть написаны на разных языках, во-вторых, так делать не рекомендуется. Например, в одном из наших проектов мы отказались от этого по желанию заказчика, связанному с соображениями безопасности. Каждый микросервис имеет отдельный репозиторий, причем заказчик не дает доступ к ним.

В процессе разработки все может работать корректно, а потом – нет. Бывает, что в случае возникновения исключений приложение бесконечно пытается их обработать, и это дает большую нагрузку – вся система «ложится». Чтобы избежать таких ситуаций, нужно все настроить, например, ограничить количество попыток, не возвращать это обращение в очередь в ту же секунду и т.д.





Проблема пятая: сложность тестирования, трассировки и отладки

Чтобы протестировать какую-либо проблему, нужно скачать все задействованные микросервисы. Отладка становится нетривиальной задачей, а все логи нужно собирать где-то в одном месте. При этом логов нужно как можно больше, чтобы разобраться, что случилось. Для отслеживания проблемы нужно понять весь путь, который прошло сообщение. Юнит-тестов здесь недостаточно, поскольку вероятны ошибки на стыке сервисов. При внесении изменений убедиться в работоспособности можно только после прогона на стенде. Мы можем ограничить каждый микросервис некоторым объемом памяти (например, 500 мегабайтов), но бывают моменты пиковой нагрузки, когда потребуется до двух гигабайтов. Бывают моменты, когда система начинает тормозить. В результате ресурсы могут расходоваться на то, что не относится к непосредственным задачам клиента: к примеру, есть всего два бизнесовых микросервиса, а половина ресурсов расходуется на три дополнительных микросервиса, поддерживающих работу остальных.



Микросервис или монолит: критерий выбора


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

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



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

У каждого модуля должен быть свой API, чтобы впоследствии его можно было выделить и сделать модуль микросервисом.



Определив границы модулей, можно переходить к декомпозиции на микросервисы, если это понадобится. Примерно в 90% случаев можно будет остаться на монолите, но при необходимости вам будет легче и дешевле изменить архитектуру.

В нашей практике работы с монолитом и микросервисами мы пришли к следующим выводам:
  • Не переходите на микросервисы только потому, что их используют Netflix, Twitter, Facebook
  • Начните с двух-трех микросервисов, которые взаимодействуют друг с другом, детально проработайте на них все нефункциональные требования (безопасность, отказоустойчивость, масштабируемость и т.п.) и только потом переходите к остальным сервисам
  • Автоматизируйте все, что только возможно
  • Настройте мониторинг
  • Пишите автотесты
  • Не используйте распределенные транзакции (но это не повод отказаться от гарантии целостности данных).
  • Если вы хотите использовать микросервисную архитектуру, будьте готовы к тому, что разработка может обойтись вам примерно в 3 раза дороже, чем на монолите. Однако, обе технологии имеют свои недостатки и преимущества, у каждой из них есть своя ниша.

Комментарии (8)


  1. maxzh83
    29.05.2019 16:00
    +2

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


    1. sotnikdv
      29.05.2019 16:42

      Микросервисы — это размен экспоненциальной сложности разработки при росте монолита на линейную в области инфраструктуры и девопс через процесс декомпозиции, автоматизации деплоя и, обычно, ведущий к облакам (требование simple resources provisioning), и микросервисной платформе (service mesh в апофигее).


      Естественно, что есть точка, где оверхед на деплой и менеджмент сравнивается с оверхедом на сложность монолита.


      Вот с той точки и начинается декомпозиция.


      Коллеги, common, я на такие темы уже даже разговаривать стесняюсь, побьют же за баян. Скажут ты что, в 2019 году припер сюда вопрос 7 летней давности и вещаешь давно известные вещи с видом пророка.


      1. maxzh83
        29.05.2019 18:22

        это размен экспоненциальной сложности разработки при росте монолита на линейную в области инфраструктуры и девопс

        Почему-то всегда забывают про обеспечение целостности данных, если это требуется. А это задача ни разу не тривиальная.


  1. sotnikdv
    29.05.2019 16:34

    Горшочек, не вари.


    Тема микросервисов уже исписана и истоптана напрочь, хватит жить баталиями 7 летней давности.


    При том, что тему истоптали 5 лет назад полностью.


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

    И самое главное, да почитайте наконец пару книг и Фаулера. Там же расписано все. Когда, зачем, как.


    Если человек противопоставляет монолит и микросервисы, сразу понятно, ничего не читал, сидел 5 лет под камнем.


    P.S. Там под камнем еще место есть? Так утомило все :)


    1. powerman
      29.05.2019 22:49

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

      Список правил в студию! А то многие всё ещё мучаются, попадётся какой-нибудь healthcare, и поди, нащупай там границы ответственности между микросервисами и прочий bounded context


    1. slonopotamus
      30.05.2019 01:08
      +2

      микросервисы это способ бороться с экспоненциально возрастающей сложностью


      А поделитесь плиз какими-то пруфлинками этому утверждению. Мне совсем неочевидно каким образом распиливание одного процесса на несколько уменьшает сложность.


      1. powerman
        30.05.2019 15:52

        Весь интернет — один большой пруфлинк. Если бы существующие сайты и сервера разрабатывались не раздельно, а были частью монолита — интернета в текущем виде бы не существовало.


        С микросервисами та же история — если один процесс удаётся распилить на несколько слабо связанных между собой — каждый их них разрабатывать действительно проще… а если они оказываются после распиливания сильно связаны — сложнее. В общем и целом уменьшение сложности здесь является следствием удачного разделения на слабосвязанные части, а уж как эти части оформлены — сетевыми сервисами, встроенными микросервисами внутри монолита, или просто отдельными модулями/библиотеками — не так принципиально. Просто когда это сетевые сервисы становится сложнее работать мимо его API напрямую с его БД, сложнее писать без документации API, без автоматизации выката, etc. — т.е. без всего такого, что внутри монолита обычно делать ленятся. И как следствие этой лени обычно получается так, что с микросервисами действительно нередко удаётся бороться со сложностью более успешно, чем без микросервисов.


      1. lexxpavlov
        30.05.2019 15:58

        Был монолит со сложностью 10. Делим на 10 микросервисов, получается не 10 по 1, а 10 по 2-3. Но проектом со сложностью 2-3 управлять гораздо проще, чем сложностью 10. И плюс добавляем сложность в взаимодействии, если декомпозиция проведена хороша, то взаимодействий от 9, иначе до 90, сложностью 1.
        Получается, вместо сложности 10 выходит 30=20(сервисы)+10(взаимодействия), как и написано в статье («разработка может обойтись вам примерно в 3 раза дороже, чем на монолите», в последнем абзаце). Но управлять 10 монолита уже нереально (невозможно добавить ещё одну, чтобы стало 11), тогда и делают микросервисы.