Микросервисы — за и против
Одни команды разработчиков находят в микросервисной архитектуре преимущества перед монолитной. Другие же считают микросервисы лишней нагрузкой, снижающей производительность. Как и каждый архитектурный стиль, микросервисы несут в себе и преимущества, и недостатки. В этой статье мы расскажем в каких конкретных случаях лучше применять микросервисную архитектуру, чтобы выбор оказался разумным.
Микросервисы несут в себе преимущества... | ... но включают и недостатки |
---|---|
Жесткие границы модулей: Микросервисы – четкая модульная структура, что крайне удобно для больших команд. | Распределенность: Распределенные системы сложнее программировать, так как удаленные вызовы работают медленно и невозможно исключить риск сбоя. |
Независимое развертывание: Микросервисы проще в развертывании, и поскольку они автономны, меньше шансов вызвать сбои системы, когда они работают некорректно. | Согласованность в конечном счете: Для распределенной системы сложно поддерживать строгую согласованность. Поэтому каждый обспечивает ее самостоятельно. |
Технологическое разнообразие: С микросервисами легко смешивать языки программирования, среды разработки и технологии хранения данных. | Сложность эксплуатации: Для управления микросервисами, которые регулярно переразвертываются, требуется зрелая группа эксплуатации/поддержки. |
Жесткие границы модулей
Первое преимущество микросервисов – жесткие границы модулей. Это важное, но давольно странное преимущество, поскольку в теории непонятно, почему микросервисы должны иметь более жесткие границы модулей, чем монолитный код.
Так что же я имею в виду под жесткими границами модулей? Я думаю вы согласитесь, что разделение ПО на модули (фрагменты программного кода, которые отделены друг от друга) – полезное занятие. Если мне требуется изменить систему, то я разбираюсь как работает только небольшая ее часть. И найти эту часть давольно легко.
Хорошая модульная структура полезна для каждой программы, но только с ростом команды разработчиков и увеличением размера программного обеспечения она становится жизненно необходимой.
Сторонники микросервисов сразу вспоминают закон Конвея (Conways Law), согласно которому система отражает коммуникационную структуру организации, в который она разрабатывалась.
С ростом команд (особенно, если они территориально распределены) важно так структурировать ПО, чтобы коммуникации между отдельными группами были менее часты и более формальны, чем внутри групп. И микросервисы позволяют соответствовать такому шаблону коммуникаций, обеспечивая каждой команде работу с относительно независимыми элементами системы.
Как сказано выше, нет никаких причин считать, что монолитная система не может иметь хорошую модульную структуру. Но мы то знаем, что это случается крайне редко, а самая распространенная архитектурная концепция у монолитных систем – "запутанный клубок" (Big Ball of Mud), который все чаще становится причиной для разворота в сторону микросервисов.
Разделение в этом случае работает, потому что границы модуля обеспечиваются барьером для межмодульных ссылок. В монолитной системе, как правило, такой барьер легко обойти. Это позволяет быстро добавлять новые функции, но при широком использовании этот подход подрывает модульную структуру и снижает производительность команды. Выделение модулей в отдельные сервисы делает границы более жесткими и обходить их становится сложнее.
Другой аспект – постоянные данные. Одна из ключевых характеристик микросервисов - децентрализованное управление данными, согласно которому каждый сервис использует свою собственную БД и любые другие сервисы получают к ним доступ только через API сервиса-владельца данных. Это устраняет интеграционные базы данных, которые выступают основным источником вредных связей в больших системах.
Еще раз подчеркну, что существование четких границ модулей возможно и в монолитном коде, но это требует дисциплины. Аналогично "запутанный клубок" может состоять и из микросервисов, но в этом случае потребуется больше усилий.
Я смотрю на это так – использование микросервисов повышает вероятность получить лучшую модульность системы. Если вы уверены в дисциплине коллег, то это преимущество незначительно. Но по мере роста команды поддерживать дисциплину станет сложнее.
Это преимущество может стать помехой, если вы не определили границы правильно. Это одна из двух главных причин для стратегии сначала монолит, и даже те, кто предпочитает начинать с микросервисов, указывают, что делать это можно только при полном понимании предметной области.
Но я еще не закончил с оговорками по этому вопросу. Сказать, насколько хорошо система поддерживает модульность, можно только по прошествии времени. То есть, у систем, просуществовавших хотя бы пару лет.
Кроме того, ранние адепты новых идей обычно более талантливы, что даёт дополнительную задержку для оценки модульных преимуществ микросервисных систем, написанных средними командами разработчиков.
Но даже и тогда мы обязаны признать, что средние команды пишут среднее программное обеспечение, поэтому вместо сравнения результатов с топ-командами лучше сравнить полученное программное обеспечение с тем, как оно же выглядело бы в монолитной архитектуре, а это непростая гипотетическая ситуация для сравнения.
Все, что сейчас можно сказать основано на свидетельствах людей, которых я знаю, и которые использовали этот стиль. Они говорят, что им поддерживать модули с микросервисами значительно легче.
Один пример будет особенно интересен. Команда сделала неправильный выбор, используя микросервисы для системы, которая не была достаточно сложна, чтобы оправдать издержки микросервисов. Проект попал в беду и нуждался в спасении. На это было брошено намного больше людей, чем планировалось изначально.
На тот момент микросервисная архитектура стала полезной, так как система смогла справиться с быстрым притоком разработчиков, а группа разработки смогла использовать большое количество команд гораздо легче, чем при монолитном подходе.
В результате проект ускорился в большей мере, чем можно было бы ожидать с монолитным подходом, что позволило команде догнать график. Общий результат, однако, был на тот момент отрицательным, т.к. на программное обеспечение ушло больше человеко-часов, чем это было бы на монолитной архитектуре.
Распределенность
Микросервисы используют распределенный подход для улучшения модульности. Но у распределенного программного обеспечения есть существенный недостаток — оно распределенное.
Как только вы выбираете распределенность, получите в догонку букет сложностей.
Первое — производительность. В наше время мы должны быть в очень необычном месте, чтобы обнаружить, что внутренний вызов функции стал узким местом производительности системы, но удаленные вызовы работают медленно.
Если сервис вызывает пять удаленных сервисов, каждый из которых вызывает еще пять удаленных сервисов, то общее время задержки может стать пугающим. Конечно, для смягчения этой проблемы придуман не один способ.
Во-первых, можно делать меньше удаленных вызовов, увеличив их детализацию, но это усложняет модель программирования, так как теперь нужно думать о том, как объединять взаимодействия между сервисами. У этого подхода тоже есть лимит – каждый взаимодействующий сервис всё равно потребуется вызвать хотя бы один раз.
Второй подход к смягчению проблемы - асинхронный подход. Если сделать шесть асинхронных вызовов одновременно, то общее время ожидания будет определяться самым медленным вызовом, а не суммой всех.
Это может дать большой выигрыш в производительности, но влечет другую проблему – асинхронность сложно программировать и ещё труднее отлаживать.
Но в большинстве примеров использования микросервисов, о которых я слышал, стоит использовать асинхронность, чтобы получить приемлемую производительность.
Следующий пункт после скорости - надежность.
Вы ожидаете, что внутренние вызовы функций всегда будут работать, но удаленный вызов может отказать в любой момент. С ростом количеством микросервисов потенциальных точек отказа становится больше. Мудрые разработчики знают это и проектируют с учетом отказов. К счастью техники, которые нужны для реализации асинхронного взаимодействия, также хорошо подходят для работы с отказами, повышая отказоустойчивость.
Однако, это не полная компенсация, т.к. по-прежнему остаются дополнительные сложности по выявлению последствий отказа для каждого внешнего вызова. И это только две основные проблемы распределенных вычислений.
Несколько комментариев к этому. Во-первых, озвученные проблемы возрастают в монолитных системах по мере их роста. Мало монолитных систем действительно самодостаточных. Как правило, существуют прочие системы, с которыми приходится взаимодействовать.
Взаимодействие с ними происходит по сети и мы сталкиваемся с теми же проблемами. Именно поэтому команды, которые работают с удаленными системами, склонны быстрее переходить на микросервисы. В этом вопросе сильно помогает опыт - более умелые команды лучше справляются с проблемами распределенности.
Резюмируя, распределенность добавляет сложность и затраты. Я неохотно принимаю решение об ее использовании и считаю, что слишком часто люди недооценивают связанные с этим проблемы.
Согласованность в конечном счете
Я уверен, что вы знаете веб-сайты, которые требуют терпения. Вы изменяете что-то, экран обновляется, но ваше изменение отсутствует. Вы ждете одну-две минуты, обновляете экран снова и, наконец, видите свои изменения.
Это очень раздражающая проблема юзабилити, и она почти всегда связана с согласованностью кода. Ваше изменение было получено розовым узлом (Pink node), но запрос Get был обработан зеленым узлом (Green node). До тех пор, пока зеленый узел не получит обновление от розового, сохраняется ситуация несогласованности. В конечном счете согласованность достигается, но до тех пор можно подумать, что что-то пошло не так.
Подобные несогласованности сильно раздражают, но они могут быть и гораздо более серьезными. Бизнес-логика может оказаться в ситуации принятия решений на основе противоречивой информации. Если это случится, может быть очень трудно выяснить, что пошло не так, потому что любое расследование будет сильно после того, как ситуация несогласованности завершилась.
Микросервисы подвержены проблеме несогласованности из-за их достойной уважения настойчивости на децентрализованном управлении данными. В монолитной системе можно обновить множество объектов в одной транзакции. С микросервисами потребуется несколько ресурсов для обновления и распределенные транзакции не одобряются (по веским причинам). Как результат, теперь разработчики должны помнить о возможности несогласованности и думать о том, как определять моменты рассинхронизации, прежде чем программировать что-то, о чем потом можно будет пожалеть.
Монолитный мир не свободен от этих проблем. По мере роста системы растет необходимость использования кэширования для повышения производительности, а недействительность кэша является другой трудной задачей. Большинству приложений требуются оффлайн-блокировки, чтобы избежать длительно выполняющихся транзакций базы данных. Внешние системы требуют обновлений, которые не могут быть согласованы с помощью менеджера транзакций. Бизнес - процессы часто более терпимы к несогласованности, чем вы думаете, потому что компании обычно предпочитают доступность согласованности (бизнес - процессы давно инстинктивно поняли теорему CAP).
Так же, как с другими проблемами распределенности, монолитные системы не полностью избегают несогласованности, но они страдают от этого гораздо меньше, особенно, когда они небольшие.
Независимое развертывание
Компромисс между границами модулей и сложностями распределенных систем присутствовал в течение всей моей карьеры.
Но одна вещь, которая заметно изменилась только в последнее десятилетие - развертывание production-окружения.
В двадцатом веке поставка новых версий ПО была почти повсеместно болезненным и редким событием, когда во время выходных все пытались поместить какой-то странный кусок программного обеспечения туда, где он может сделать что-то полезное.
Сейчас квалифицированные команды практикуют непрерывную поставку, что позволяет им выпускать обновления несколько раз в день.
Этот сдвиг оказал глубокое влияние на индустрию программного обеспечения, и он тесно переплетен с движением сторонников микросервисов.
Использование микросервисов в том числе было вызвано сложностью развертывания больших монолитных систем, где небольшое изменение в части системы может привести к неудаче всего развертывания.
Ключевой принцип микросервисов - сервисы могут развертываться независимо друг от друга. Так что теперь, когда вы делаете изменения, вам нужно протестировать и развернуть только микросервис. Если вы запутались в нем, вы не обрушите всю систему.
В конце концов, из-за необходимости проектирования с учетом отказов, даже полный отказ компонента не должен останавливать другие части системы от работы, хотя, конечно, он приведет к некоторой потере функций или снижению производительности.
Это улица с двусторонним движением. Если у вас есть много микросервисов, которые нужно часто развертывать, важно, чтобы инфраструктура развертывания работала без сбоев.
Вот почему быстрое развертывание приложений и быстрое выделение ресурсов инфраструктуры являются предварительными требованиями для использования микросервисов.
Для хоть сколь-нибудь сложных систем вы должны использовать непрерывную поставку. Большим преимуществом непрерывной поставки является сокращение цикла от идеи до работающего программного обеспечения.
Организации, которые делают это, могут быстро реагировать на изменения рынка, а также вводить новые функции быстрее конкурентов.
Хотя многие люди указывают непрерывную поставку в качестве причины для использования микросервисов, важно отметить, что непрерывная поставка может применяться даже для крупных монолитных систем. Facebook и Etsy - два наиболее известных примера.
Есть также много примеров неудачных попыток использования микросервисных архитектур при независимом развертывании, когда релизы нескольких сервисов должны быть тщательно скоординированы.
Хотя от многих людей я слышу, что гораздо проще сделать непрерывную поставку при использовании микросервисов, я менее убежден в этом по сравнению с их практической пользой для модульности - хотя, естественно, модульность сильно коррелирует со скоростью поставки.
Сложность эксплуатации
Возможность быстро развернуть небольшие независимые единицы кода является большим благом для разработчиков, но это создает дополнительную нагрузку на эксплуатацию, поскольку пять больших приложений превращаются в сотни маленьких микросервисов.
Организации считают поддержку такого количества быстро меняющихся элементов непомерно высокой нагрузкой.
Это подчеркивает важность непрерывной поставки. Она - ценный навык для монолитов, затраты на который почти всегда окупаются, но для серьезного проекта на микросервисах это жизненная необходимость.
Вы просто не сможете справиться с десятками сервисов без автоматизации и совместной работы, входящих в непрерывную поставку. Эксплуатационная сложность увеличивается также в связи с повышением требований к управлению этими сервисами и их мониторингу.
Опять же это уровень зрелости, который полезен для монолитных приложений и необходим, если появляются микросервисы.
Сторонники микросервисов говорят, что поскольку каждый сервис небольшой, его проще понять. Но опасность заключается в том, что сложность не устраняется, она просто смещается на взаимосвязи между сервисами.
Это может маскировать увеличение сложности эксплуатации, например, трудности при отладке функций, которые включают несколько сервисов. Хороший выбор границ сервисов уменьшает эту проблему, а неправильный выбор границ делает её гораздо сложнее.
Решение таких эксплуатационных проблем требует множества новых навыков (в большей степени) и инструментов. Инструменты пока ещё не достигли зрелости, но мой опыт подсказывает мне, что даже при наличии лучших инструментов нижний порок по навыкам в среде микросервисов выше.
Тем не менее, эта потребность в навыках и инструментах не является самой сложной частью проблем эксплуатации. Для эффективной работы нужно ввести культуру DevOps: более тесное сотрудничество между разработчиками, службой эксплуатации и всеми остальными участниками процесса поставки.
Культурные изменения трудны, особенно в больших и давно существующих организациях. Если вы не сделаете эти культурные изменения и не повысите навыки разработчиков, ваши монолитные приложения будут испытывать небольшие проблемы, но ваши приложения на базе микросервисов серьезно пострадают.
Технологическое разнообразие
Поскольку каждый микросервис - независимо развертываемый элемент, у вас есть большая свобода в выборе технологии внутри него.
Микросервисы могут быть написаны на разных языках, использовать разные библиотеки и различные хранилища данных. Это позволяет командам выбрать подходящий инструмент для работы, так как некоторые языки и библиотеки лучше подходят для определенных задач.
Обсуждение технического разнообразия обычно сосредотачивается на выборе лучшего инструмента для работы, но часто большим преимуществом микросервисного подхода является возможность использования разных версий.
В монолитной системе вы можете использовать только одну версию библиотеки, что часто приводит к проблемам при обновлениях.
Одной части системы может потребоваться обновление библиотеки, чтобы использовать свои новые возможности, но это нельзя сделать, так как такое обновление ломает другую часть системы.
Работа с версиями библиотек является одной из тех проблем, которые экспоненциально растут по мере роста количества кода. Существует опасность того, что с таким технологическим разнообразием компания-разработчик может быть перегружена.
Большинство организаций, которых я знаю, поощряют ограниченный набор технологий. Это поощрение поддерживается путем предоставления общих инструментов для таких вещей, как мониторинг. В результате сервисы проще делать в небольшом портфеле общих сред разработки.
Не стоит недооценивать важность поддержки экспериментов. В монолитной системе ранние решения о языках и средах разработки трудно изменить. После десяти лет или около того такие решения могут загнать команды разработчиков в рамки неудобных технологий.
Микросервисы позволяют командам экспериментировать с новыми инструментами, а также постепенно мигрировать системы по одному сервису за раз, когда актуальной становится другая технология.
Второстепенные факторы
Я рассматриваю указанные выше моменты как основные. Вот еще несколько вещей, которые, как я думаю, менее важны.
Сторонники микросервисов часто говорят, что сервисы проще масштабировать, так как если один сервис получает слишком большую нагрузку, то можно масштабировать только его, а не все приложение.
Однако я не могу вспомнить реальный проект, который убедил бы меня, что было на самом деле более эффективно делать это избирательное масштабирование по сравнению с типовым масштабированием путем копирования полного приложения.
Микросервисы позволяют отделить конфиденциальную информацию и добавить к ней больше безопасности. Кроме того, если защитить весь трафик между микросервисами, злоумышленникам будет сложнее использовать уязвимости.
По мере роста важности проблем безопасности это может стать одной из главных причин использования микросервисов. Даже без этого в преимущественно монолитных системах часто создают отдельные сервисы для обработки конфиденциальных данных.
Самое главное здесь - иметь дисциплину, чтобы относиться к тестированию серьезно, так как на самом деле различия между тестированием монолитных приложений и приложений на базе микросервисов не столь существенны.
Итоги
Любая общая статья об архитектурных подходах ограничивается общими рекомендациями.
Статьи, подобные этой, не дадут вам готовых решений, но они могут помочь в рассмотрении факторов, которые стоит учитывать. Каждое преимущество и недостаток будет иметь различный вес для различных систем.
Иногда преимущество станет недостатком и наоборот (например, жесткие границы модулей хороши в сложных системах, но излишняя нагрузка для простых).
Решения, которое вы принимаете, зависят от применения таких критериев к вашей ситуации, оценки факторов критических для вашей системы и того, как они влияют на результаты.
Кроме того, опыт микросервисных архитектур относительно невелик. Обычно взвешенные выводы по архитектуре можно делать только после того, как эта архитектура созрела, и вы узнали, каково в ней работать через годы после начала разработки. Пока у нас не много примеров долгоживущих микросервисных архитектур.
Монолит vs. Микросервисы - сложный выбор из двух вариантов. Оба являются нечеткими определениями, и многие системы оказываются в размытой граничной области. Также есть другие системы, которые не вписываются ни в одну из этих категорий.
Большинство людей, включая меня, противопоставляют микросервисы монолитным системам, потому что имеет смысл сравнивать их с более традиционным подходом, но мы должны помнить, что существуют системы, которые нельзя четко отнести ни к микросервисам, ни к монолитам.
Я рассматриваю монолитные системы и микросервисы как две области в пространстве архитектур. Их стоит выделять, потому что они обладают интересными характеристиками, которые полезны для обсуждения, но ни один разумный архитектор не рассматривает их в качестве полного разбиения архитектурного пространства.
Тем не менее, один общий вывод, который, как кажется, признается большинством - издержки микросервисов: микросервисы более трудоемки, и это окупается только для более сложных систем. Поэтому, если вы можете справиться со сложностью вашей системы с помощью монолитной архитектуры, вам не следует использовать микросервисы.
Говоря о микросервисах, давайте не забывать о более важных вопросах, которые определяют успех или провал проектов разработки ПО. Такие факторы, как качество людей в команде, насколько хорошо они взаимодействуют друг с другом, насколько они общаются с экспертами в предметной области, будут иметь большее влияние на проект, чем использование или не использование микросервисов.
На чисто техническом уровне важнее сосредоточить внимание на таких вещах, как чистый код, хорошее тестирование и внимание к развитию архитектуры.
(via)