Преимущества и недостатки микросервисной архитектуры

27 января 2014

Если вкратце, микросервисная архитектура (Micro Service Architecture, MSA), это когда ваше приложение представляет собой много-много небольших (буквально несколько сотен строк кода) сервисов, взаимодействующих между собой путем обмена сообщениями. Это могут быть сообщения Erlang‘а, Cloud Haskell‘я, Akka, или же REST API, Protobuf, Thrift, MessagePack и так далее. Давайте же попытаемся понять, в каких задачах может быть целесообразно использовать такой подход, чем он хорош, и, конечно же, чем он плох.

Но сначала остановимся немного поподробнее на вопросе «что такое MSA».

В действительности, идея не особо нова. Есть такая штука, как SOA (Service-oriented architecture). По всей видимости, все отличие SOA от MSA заключается в размере сервисов. В случае с MSA исходный код каждого отдельного сервиса не должен превышать нескольких сотен строк кода. SOA же такого ограничения вроде бы не накладывает, поэтому в каждом сервисе могут быть десятки или сотни тысяч строк кода. Видимо, MSA — это частный случай SOA.

Идеи, заложенные в MSA, давно знакомы программистам на Erlang. И вправду, редкий gen_server содержит в себе более нескольких сотен строк кода. И общение с внешним миром действительно производится путем обмена сообщениями. Erlang даже позволяет нам обновить код gen_server’а, не повлияв при этом на другие процессы в системе. Однако MSA предполагает, что каждый сервис представляет собой совершенно независимое приложение. Сервисы в системе могут быть написаны на различных языках программирования и общаться между собой, используя различные протоколы. С высокой степенью точности можно сказать, что процессы и сообщения, предлагаемые Erlang’ом — это частный случай MSA.

Наконец, есть такая давно известная и проверенная временем штуковина, как философия UNIX. Она гласит следующее. Пишите программы, которые делают одну вещь и делают ее хорошо. Пишите программы, которые работают вместе. Пишите программы, работающие с текстовыми потоками данных, потому что это универсальный интерфейс. Фактически, MSA, использующий REST, представляет собой философию UNIX, перенесенную на серверсайд.

Чтобы не возникало каши в голове, отметим, что Erlang — это такой прикольный язык программирования, Akka и Cloud Haskell — библиотеки, модель акторов — это математическая модель, MSA — это архитектура. Идея примерно одна и та же, но слова и кое-какие детали меняются, в зависимости от того, с кем и на каком уровне абстракции вы общаетесь.

Чем же так хороша микросервисная архитектура?

  • Писать и поддерживать небольшие сервисы всегда проще, чем большие. Чем меньше кода, тем легче уместить его в голове;
  • Если вы решили использовать REST API, то получаете все присущие ему преимущества. Подробности можно найти в этой и этой заметках;
  • Поскольку каждый микросервис представляет собой отдельный проект, вы можете практически как угодно распределить их между командами разработчиков. Вы можете строить иерархии из сервисов. То есть, некие сервисы будут использованы только парой сервисов, предоставляющих внешний API для других сервисов. Это масштабируемый подход, то есть, над системой могут одновременно трудиться многие десятки программистов. И никаких проблем с разрешением конфликтов или поиском сложных логических ошибок, появляющихся в результате двух коммитов, сделанных разными программистами;
  • Если вы что-то помните о бизнес-моделировании и EPC-диаграммах, то можете к своему удивлению обнаружить, что бизнес-процессы хорошо ложатся на MSA;
  • Для каждого сервиса можно выбрать язык и библиотеки, подходящие конкретно для решаемой этим сервисом задачи. Если нужна скорость, берем C++, если просто гоняем туда-сюда данные, берем Erlang, если нужна какая-то необычная библиотека, берем Clojure;
  • Практически из коробки получаем горизонтально масштабируемый и отказоустойчивый код, да и к тому же с конвейерным параллелизмом. Даже если вы пишите на Python или OCaml. При «монолитном» же подходе часто в целях оптимизации хочется по-быстрому впилить тут кэшик, там кэшек, и в итоге система становится не масштабируемой. Потому что кэши так просто выпилить нельзя, а поддерживать их в актуальном состоянии, когда в системе N копий одного сервиса, не так-то просто. Горизонтальное масштабирование, как известно, приводит к экономии денег, так как система может работать на множестве сравнительно недорогих машин. Более того, под каждый конкретный микросервис можно подобрать железо подходящей конфигурации;
  • Если в программе всего лишь несколько сотен строк кода, в ней не так-то просто сделать ошибку. Необходимость юниттестов становится сомнительной, а использовать языки с динамической типизацией уже вроде как и не страшно. Упрощается тестирование, ибо полностью покрыть тестами API одного сервиса не представляет большого труда. Можно с легкостью замокать внешние сервисы. Понятно, что один мок-сервис можно повторно использовать при тестировании других зависящих от него сервисов. Не требуется специальное тестовое окружение, сервис можно разрабатывать прямо у себя на макбуке;
  • Никто не говорил, что абсолютно все сервисы нужно обязательно писать самому. Всякие там Graphite, RabbitMQ, Riak, PostgreSQL и MySQL, или даже Nginx, отдающий статику с JavaScript, который ходит в наш REST API, также можно рассматривать, как сервисы, являющиеся частью системы. Хотя, конечно же, в них далеко не пара сотен строк кода;
  • Модульность. Захотел сделать апдейт — катишь сервис. Если по метрикам видишь, что что-то не так, откатываешь. В какой-то момент заметили, что в сервис больше никто не ходит — просто выключаем его. Вместо рефакторинга проще выбросить сервис и написать его с нуля. Если где-то в системе крутится ужасный легаси-код, но при этом он работает, пускай себе работает, глаза при этом он никому не мозолит;
  • Нетрудно отследить зависимости между сервисами. Если все сервисы зависят друг от друга, наверное, что-то в архитектуре вашей системы не так. Если количество связей не намного превышает количество узлов, значит вы на верном пути. Также нетрудно найти критические пути, единые точки отказа и так далее;
  • Не страшно экспериментировать с новыми технологиями. Захотел попробовать новый веб-фреймворк, ORM или, например, протащить в проект Haskell — переписываешь существующий сервис и смотришь, насколько это будет работать. Если не работает, откатываешь к старой версии. Попробуйте так просто взять и перейти с Django на другой фреймворк в «монолитном» проекте;
  • Сравнительно нетрудно переписать легаси систему в соответствии с MSA. Ставим перед системой зонтик/прокси. Затем потихоньку выносим компоненты системы в сервисы, а прокси следит за тем, чтобы клиенты ходили туда, куда нужно;
  • Даже если ты пилишь тупо сайтик, с MSA чувствуешь себя крутым разработчиком распределенной системы :)

Очевидный недостаток всего этого хозяйства заключается в необходимости гонять данные между микросервисами. Если накладные расходы на обмен сообщениями, а также их сериализацию и десериализацию, слишком велики, нужно либо оптимизировать протокол, либо подвинуть взаимодействующие сервисы друг к другу поближе, либо объединить их в один микросервис. Не так-то просто все это админить, хотя, если ваши админы осилили Chef или Ansible, то, скорее всего, особых проблем быть не должно. Очевидно, при использовании MSA следует серьезно отнестись к логированию (вероятно, в отдельный пул сервисов) и мониторингу. Притом те, кто используют MSA, рекомендуют в первую очередь собирать бизнес метрики — количество продаж, результаты А/Б тестирования и так далее. Все это, впрочем, справедливо в отношении любой серьезной системы.

Если ваши микросервисы используют какую-нибудь общую библиотеку, а в этой библиотеке, например, обнаружилась бага, то нужно обновить версию библиотеки во всех сервисах, а потом все их раскатить. Поэтому при использовании MSA общего кода должно быть как можно меньше. Общий код должен быть либо предельно простым, либо меняться крайне редко. Иногда, если сервисы делают похожие вещи, код лучше скопипастить, потому что в будущем эти вещи могут стать не такими уж и похожими. Ну и, конечно же, ни в коем случае не используйте какие-нибудь общие инклудники с объявлениями record’ов, как это порой делают в Erlang.

Ссылки по теме:

  • Пост Micro Service Architecture в блоге James Hughes;
  • Длинный, но преинтереснейший пост о платформах, SOA, Amazon, Microsoft, Facebook и Google;
  • Micro Service Architecture, презентация Eduards Sizovs;
  • Fred George рассказывает о MSA, Clojure, Kafka и утверждает, что можно катить микросервисы в продакшн каждые 10 минут (каждый прогер — дважды в день). Слабо верится, но звучит интересно. Также из этого доклада вы узнаете о хороших практиках MSA, например, что каждый сервис должен отдавать информацию о своем текущем состоянии;
  • Доклад James Lewis под названием Micro Services: Java, the Unix Way. Субъективно послабее, чем у Fred George, но также заслуживает внимания. Вся суть начинается где-то с 21-ой минуты;

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

Скорее всего, не во всякой задаче можно или нужно использовать микросервисную архитектуру. Но там, где есть такая возможность, как мне кажется, преимущества существенно перевешивают недостатки.

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

Метки: .


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