Когда использовать монолиты, а когда микросервисы

3 июля 2015

Я тут недавно решил послушать 86-ой выпуск подкаста «Разбор Полетов». Помимо прочего, в выпуске обсуждался пост Фаулера MonolithFirst, в результате чего разгорелась довольно длительная дискуссия на тему «монолиты против микросервисов». Большая проблема с микросервисами заключается в том, что многие вещи, которые с их помощью пытаются решить, либо успешно решаются и без микросервисов, либо к микросервисам и монолитам отношения вообще не имеют. Если вам интересны мои размышления по этой теме, добро пожаловать под кат.

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

  1. Простота поддержки. В действительности, распиливание кода на отдельные артефакты (пакеты, библиотеки) дадут вам те же самые преимущества. Точно так же, как у микросервиса есть свой API, свой интерфейс есть и у библиотеки. Вы точно так же компилируете ее отдельно от всего остального, прогоняете отдельные тесты, заливаете в какой-нибудь Nexus, как вы компилируете, тестируете и деплоете микросервисы. Если ваш код стало сложно поддерживать, вам не нужны микросервисы. Просто нарежьте проект на библиотеки, и проблема уйдет;
  2. Распределение работы между командами. Точно так же, нет особой разницы по сравнению с артефактами. Более того, на практике поддержка разных микросервисов разными людьми приносит множество проблем вроде сломанного API, отсутствие документации по нему, ожидание в течение месяца, пока в сервисе появится нужная фича, и так далее. В случае с библиотекой вы хотя бы знаете полный перечень экспортируемых ею классов и методов, а также аргументы каких типов они ожидают. Кроме того, если API случайно сломали, код сломается при компиляции, а не в продакшене. В крайнем случае вы сможете просто пожить какое-то время на более старой версии библиотеки;
  3. Правильный инструмент для каждой задачи. Утверждается, что преимущество микросервисов в том, что одну половину микросервисов можно написать, скажем, на Erlang, а вторую на Scala. На практике же использование зоопарка технологий приводит к довольно существенным проблемам. Как минимум, вам нужны программисты, которые знают как Erlang, так и Scala. Кроме того, для одного микросервиса приходится поддерживать клиентов на двух языках. В общем и целом, никакое это не преимущество. Если уж вам так сильно хочется поиграться с новыми клубочками, ограничьтесь хотя бы клубочками только под JVM или как-то так;
  4. Горизонтальное масштабирование и отказоустойчивость. Никак не связаны с микросервисами. Представьте себе большое и сложное монолитное приложение, которое живет в автоскелинг группе за ELB. Масштабируется? Ну да. Отказоусточиво? Еще бы! Ничто не мешает монолитному приложению быть масштабируемым и отказоусточивым, микросервисы тут вообще ни при чем;
  5. Возможность выкинуть микросервис и написать с нуля, упрощение тестирования, и прочее. Все то же самое, смотри выше про артефакты. Неудачно реализовали библиотеку? Выкидываем и пишем с нуля другую с таким же интерфейсом. Тесты, как уже отмечалось, вы также прогоняете отдельно для каждого артефакта, как прогоняете их отдельно для каждого микросервиса;

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

Дополнение: Проблемы распределенных систем более подробно описаны в заметке Распределенные системы: что может пойти не так.

Так когда же все-таки имеет смысл распиливать систему на микро- или вообще какие-то сервисы? На данный момент мне на ум приходят следующие случаи:

  1. Система в целом потребляет столько ресурсов, что не помещается на одну машину, а также почему-то не может быть масштабируемой горизонтально. Поэтому система разделяется на части, которые разносятся по разным машинам;
  2. Разные части системы используют очень разные ресурсы. Например, одной части нужно много CPU, другой — много сетевого трафика, а третьей — много оперативной памяти. При определенных параметрах вместо того, чтобы хостить эти части вместе на машинах, которые дают много сразу всех ресурсов, становится существенно дешевле разделить систему на сервисы. Каждый из них затем хостится на машинах, дающих только нужные конкретному серсису ресурсы;
  3. Распиливание на микросервисы, как радикальный способ обеспечения безопасности. Например, есть некие очень важные данные — логины и пароли, банковские карты или истории болезни. Доступ к этим данным должно иметь как можно меньше людей. А все сервисы должны работать с этими данными исключительно через строгий API, который отдает только то, что действительно нужно знать тому или иному сервису;
  4. Как радикальный способ борьбы со stop the world при сборке мусора. Чем больше отдельных куч, чем меньше данных в каждой из этих куч, тем быстрее сборка мусора. Кроме того, можно держать несколько экземпляров одного сервиса. Если сервис долго не отвечает на запрос (например, 95-ый процентиль времени ответа), делаем вывод, что он притупил из-за GC и шлем запрос в его соседнюю копию;
  5. Бывает так, что какой-то сервис нужно держать строго в одном экземпляре. Так бывает, когда нужно прогнать фоновую миграцию схемы БД, запускать какие-то куски кода по расписанию, и в ряде других случаев;
  6. Ваша система состоит из двух очень больших частей, которые между собой не взаимодействуют или практически не взаимодействуют. Например, реализовывать Яндекс.Почту и Яндекс.Карты в виде одного монолитного сервиса — явно очень плохая идея. С другой стороны, не факт, что распиливание Twitter’а на десяток микросервисов принесет пользы больше, чем вреда;

Мой совет — если вы пишите монолитное приложение, но у вас не возникает с ним особых проблем, то не заморачивайтесь по поводу распиливания на микросервисы. Если проблемы возникают, проверьте, нельзя ли их как-то решить без микросервисов, например, тюнингом параметров GC (если проблема в GC), оборачиванием запросов к БД в хранимые процедуры (если проблема в безопасности) или распиливанием приложения на отдельные артефакты (множество проблем, названных выше). И только если вы совершенно уверены, что без разделения на сервисы проблему никак не решить, тогда действуйте.

С уважением, ваш Капитан Очевидность.

Метки: , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.