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

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

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

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

  • Отдельные сервисы предельно просты и сфокусированы на решении одной конкретной задачи.

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

  • Построенные таким образом системы по своей природе слабосвязанны.

  • Данная модель позволяет множеству разработчиков и команд работать над функционалом относительно независимо друг от друга.

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

И тем не менее микросервисы — не бесплатный сыр!

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

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

Значительные эксплуатационные затраты

Микросервисная архитектура влечет за собой существенные эксплуатационные затраты.

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

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

А если добавить сюда еще балансировщики нагрузки и уровни обмена сообщениями для организации взаимодействия между сервисами, то вся эта конструкция начинает выглядеть поистине громоздкой в сравнении с единым монолитным приложением, которое предоставляло эквивалентную бизнес-функциональность!

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

Физическая доставка всего этого множества микросервисов через пайплайн в производственное окружение также требует высокого уровня автоматизации процессов выпуска и развертывания.

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

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

Требуются серьезные навыки DevOps

Если раньше команда разработки могла ограничиться развертыванием, скажем, кластера Tomcat и обеспечением его доступности, то эксплуатационные задачи по поддержанию работоспособности микросервисов означают, что теперь вам определенно потребуются высококлассные навыки DevOps и автоматизации выпуска внутри команды разработки.

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

Эффективное использование этой архитектуры также подразумевает, что многим сервисам потребуются собственные хранилища данных. Разумеется, эти хранилища могут быть реализованы на разных технологиях (каждой задаче — свой инструмент!), что означает необходимость замены традиционного администратора баз данных на нескольких разработчиков, обладающих глубокими знаниями в развертывании, запуске, оптимизации и поддержке целого набора NoSQL-продуктов.

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

Неявные интерфейсы

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

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

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

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

Повторюсь: обратная совместимость не является панацеей, как то провозглашают проповедники микросервисов.

Дублирование усилий

Представьте, что появилось новое бизнес-требование — по-другому рассчитывать налог для определенной продуктовой линейки. У нас есть несколько вариантов реализации.

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

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

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

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

Сложность распределенных систем

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

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

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

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

Асинхронность — это сложно!

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

Асинхронные системы чрезвычайно эффективны в тех случаях, когда удается декомпозировать работу на по-настоящему независимые задачи, которые могут выполняться в произвольном порядке и в разное время.

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

Проблемы тестируемости

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

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

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

В заключение

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

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

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

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


  1. CloudlyNosound
    15.09.2025 15:58

    В 2025 наблюдаем взрывной рост популярности попыток оптимизации производительности микросервисов. С отказоустойчивостью разобрались, видимо.


  1. Lewigh
    15.09.2025 15:58

    Судя по тому что статья 2014 года, то сыр не просто бесплатный но еще и протухший.


    1. orl31
      15.09.2025 15:58

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