От переводчика: с момента выхода популярной статьи Мартина Фаулера «Микросервисы» (перевод на Хабре) прошло уже достаточно времени, чтобы автор смог дополнить свои наблюдения свежим опытом проектирования и разработки микросервисов в различных компаниях, и рассказать о нем в новом посте, чей перевод представляется вашему вниманию.
Многие команды разработчиков нашли архитектурный стиль микросервисов подходом, превосходящим монолитную архитектуру; другие команды выяснили, что для них микросервисы — лишняя обуза, подрывающая производительность разработки. Как и у любого стиля архитектуры, у микросервисов есть свои плюсы и минусы. Для того, чтобы делать осознанный выбор, вы должны понимать эти свойства и уметь рассматривать их на фоне собственных конкретных условий.
Первая серьезная выгода от микросервисов — это строгие границы модулей. Это важное преимущество, хотя в то же время и странное — потому что, в теории, нет такой причины, по которой микросервисы должны иметь более строгие границы модулей, чем монолит.
Итак, что я подразумеваю под жесткими границами модулей? Я думаю, большинство из нас согласится с тем, что программы лучше разбивать на модули — фрагменты ПО, отделенные друг от друга. Ваши модули должны быть такими, чтобы мне при необходимости изменения части системы чаще всего было нужно разобраться только с небольшой ее частью для внесения изменений, и чтобы нужную мне маленькую часть я смог бы найти достаточно быстро. Хорошая модульная структура будет полезна в случае любого ПО, но значимость этого свойства растет экспоненциально вместе с ростом размера самого ПО. Возможно, эта значимость растет по той причине, что команда, занимающаяся разработкой программы, также растет в размерах.
Защитники микросервисов рады будут рассказать вам о законе Конвея, утверждающем, что структура программной системы отражает коммуникационную структуру внутри компании, которая ее построила. В случае с большими командами — в частности, распределенными географически — важно строить структуру ПО так, чтобы она отражала тот факт, что коммуникация между разными командами будет более редкой и более формальной, чем та, что ведется внутри обычной команды. Микросервисы позволяют каждой команде заботиться об относительно независимых единицах при помощи этого коммуникационного паттерна.
Как я говорил раньше, не существует такой причины, по которой монолитная система должна строиться без хорошей модульной структуры. [1] Но многочисленные наблюдения показывают, что подобное делается довольно-таки редко, поскольку Большой комок грязи (Big Ball of Mud) — это самый популярный архитектурный паттерн. Фрустрация от этого явления, вместе с одинаково печальной судьбой многих монолитов, привела отдельные команды разработчиков на путь микросервисов. Принцип независимости модулей работает потому, что границы модуля служат барьером для ссылок между модулями. Проблема состоит в том, что в случае с монолитной системой не составляет особого труда подлезть под этот барьер; подобный поступок может быть полезным тактическим приемом, предоставляя короткий путь к быстрой разработке и запуску каких-то функций системы, но в перспективе это подрывает основы модульной структуры и существенно ухудшает продуктивность команды. Раскладывание модулей по различным сервисам делает границы тверже, создавая дополнительные препятствия на пути любителей сомнительных решений для срезания пути.
Важным аспектом этой связи является постоянство данных (persistent data). Одна из ключевых характеристик микросервисов — это децентрализованное управление данными; этот принцип означает, что каждый сервис управляет своей базой данных и любой другой сервис должен использовать API этого сервиса, чтобы добраться до нее. Подобное решение позволяет избавиться от интеграционных баз данных, которые являются основными источниками неприятных связей в больших системах.
Важно подчеркнуть, что вполне возможно иметь «честные» границы модулей внутри монолита, но это требует серьезной дисциплины. При должном усердии, можно построить и Большой комок микросервисной грязи, однако на этот раз придется основательно постараться и приложить существенно больше усилий, чем в случае монолита. Как мне это представляется — использование микросервисов увеличивает вероятность того, что с модульностью в вашем проекте все будет в порядке. Если вы уверены в дисциплине внутри вашей команды, то, возможно, это преимущество не стоит принимать во внимание; однако, по мере роста команды соблюдать дисциплину будет все труднее — в то время как важность поддержки границ модулей будет продолжать расти.
Это преимущество становится помехой, если вы неправильно определите границы. Это одна из двух главных причин стратегии "Сначала монолит", и даже тем, кто предпочитает начинать сразу с микросервисов, стоит это делать только в том случае, если они имеют дело с хорошо знакомой предметной областью.
Но я еще не закончил с предостережениями. Вы сможете сказать, удачно ли в системе используется модульность только после того, как пройдет время. Таким образом, оценить реально ли микросервисы приводят к лучшей модульности систем мы сможем только после того, как эти системы проработают как минимум несколько лет. Более того, ранние последователи технологий обычно являются более талантливыми разработчиками, так что придется ждать еще дольше, прежде чем мы увидим, что станет с микросервисными системами, написанными средними командами. Даже тогда, мы должны признать что средние команды пишут среднее ПО, поэтому вместо сравнения их результатов с достижениями топовых команд, мы должны будем сравнивать с аналогичным ПО в монолитной архитектуре — а это уже является сложной гипотетической ситуацией, слабо годящейся для оценки.
Все факты, что мне известны на данный момент — это предварительные данные, которые я собрал от своих знакомых, уже использующих в работе данный стиль; по их словам, поддержка модулей упростилась весьма значительно.
Один услышанный мною кейс был достаточно интересным. Команда сделала неверный выбор, использовав микросервисы для системы, которая не была достаточно сложной, чтобы получить от этого решения выгоду. Проект попал в беду и его нужно было спасать, поэтому на него было брошено еще больше людей. В этот момент микросервисная архитектура неожиданно повернулась положительной стороной: система смогла проглотить приток разработчиков, и команда могла управлять разработкой гораздо проще, чем обычно бывает в аналогичных случаях с монолитными архитектурами. В результате, проект ускорился до продуктивности большей, чем можно было бы ожидать даже от монолита, давая команде шанс наверстать упущенное. Исход оказался негативным — итоговое ПО стоило больше человеко-часов, чем если бы оно было построено монолитом; однако, микросервисы показали свою способность к наращиванию скоростей.
Итак, микросервисы используют распределённую систему для улучшения модульности. Но распределённое ПО имеет большой недостаток: он заключается в том факте, что оно — распределённое. Как только вы разыграете карту распределённости, вы навлечете на себя много сложностей. Вряд ли комьюнити вокруг микросервисов относится к этим издержкам так же наивно, как движение за распределённые объекты в свое время, но сложности от этого никуда не уходят.
Первая из них — производительность. В наши дни вам гораздо реже доведется увидеть, что слабым местом системы станут тормоза вызовов функций внутри процессов; однако, удаленные вызовы — медленные: если ваш сервис вызывает пять удаленных сервисов, каждый из которых в свою очередь вызывает другие пять удаленных сервисов, то время ответа от них создаст ужасные задержки в масштабе всей системы.
Разумеется, можно много чего сделать для уменьшения вреда от этой проблемы. Во-первых, можно увеличить гранулярность вызовов, чтобы нужно было делали меньшее их количество. Этот шаг сделает программную модель запутаннее: теперь вам придется думать, как собирать в пакеты ваши межсервисные взаимодействия. Кроме того, это не слишком далеко уведет вас от исходной проблемы, поскольку вы все равно должны будете вызвать каждый взаимодействующий сервис хотя бы раз.
Следующий способ уменьшить боль — это использовать асинхронность. Если сделать шесть асинхронных вызовов параллельно, то вы замедлитесь до скорости самого медленного вызова — вместо того, чтобы замедлиться до суммы всех их задержек, как было раньше. Это может дать большой прирост производительности, но будет стоить дополнительных умственных усилий. Асинхронное программирование — сложный предмет: в нем тяжело разбираться, в его случае гораздо труднее проводить отладку. Но в большей части рассказов про микросервисы, что мне довелось слышать, асинхронность потребовалась для получения приемлемой производительности.
Следом за скоростью идет надежность. Ожидается, что функции внутри процессов работают как надо, в то время как удаленные вызовы могут потерпеть неудачу в любой момент. Чем больше микросервисов используется, тем больше появляется потенциальных точек отказа. Мудрые разработчики знают про это и проектируют для отказа. К счастью, те же тактики, что требуются для асинхронного программирования, также неплохо подходят и для обработки отказов, и в результате их применения возможно улучшение «эластичности» системы. Однако, компенсацией издержек это назвать сложно — вам остается дополнительная сложность в выяснении последствий отказа для каждого удаленного вызова.
И это я перечислил всего лишь два важнейших заблуждения о распределённых вычислениях (на русском).
У описанной проблемы есть и свои нюансы. Во-первых, многие из этих вопросов возникают в монолитных архитектурах по мере их роста. Немногие монолиты действительно содержатся «сами в себе»; обычно же в них присутствуют и другие системы — зачастую это легаси-системы, с которыми нужно работать. Взаимодействие с ними вовлекает походы по сети и попадание в те же проблемы. Вот почему многие склонны как можно быстрее переехать на микросервисы для поддержки взаимодействия с внешними удаленными системами. С этой задачей помогает справиться опыт — опытная команда будет способна лучше справиться с проблемами распределенности.
Но распределённость — это всегда издержки. Я всегда неохотно разыгрываю ее карту, и, как по мне, многие слишком поспешно делают выбор в пользу распределённость, поскольку недооценивают ее проблемы.
Я уверен, что вы знакомы с веб-сайтами, для работы с которыми нужно иметь крепкие нервы. Вы апдейтите что-нибудь на странице, сайт обновляет страницу и вашего обновления нет. Вы ждете минуту или две, делаете обновление страницы, и ваш апдейт наконец появляется.
Эта весьма раздражающая проблема юзабилити почти наверняка возникает из-за рисков так называемой «согласованности в конечном счете». Ваш апдейт получила розовая нода, но запрос обрабатывала зеленая нода, и пока зеленая нода получала свой апдейт от розовой, вы застряли в окне несогласованности. Рано или поздно все согласуется, но до этого момента вы будете задаваться вопросом, не пошло ли вдруг что-то не так.
Подобные несогласованности не только причинять раздражение, но и нести гораздо более серьезные угрозы. Например, бизнес-логика может принять решения, основываясь на несогласованной информации — и когда это случится, будет чрезвычайно трудно выяснить, что пошло не так, потому что расследование начнется гораздо позже того момента, когда закроется окно несогласованности.
Микросервисы включают в себя проблемы консистентности в конечном счете — это происходит за счет «похвальной» настойчивости микросервисов на децентрализованном управлении данными. В случае с монолитом, вы можете обновить несколько вещей вместе в одной транзакции. Микросервисы в этом случае требуют обновления нескольких ресурсов, и распределенные транзакции вызывают неодобрение (по уважительным причинам). Так что теперь разработчикам приходится быть в курсе вопросов согласованности и выяснить, как обнаруживать несинхронизованность происходящего прежде, чем делать исправления в коде, о которых они потом пожалеют.
Мир монолитов тоже несвободен от этих проблем. С ростом систем, появляется всё большая необходимость в кэшировании для улучшения производительности, а инвалидация кэша — это еще одна Сложная Проблема. Большинство приложений нуждается в автономных блокировках для избежания долгоживущих транзакций баз данных. Внешние системы нуждаются в обновлениях, которые не могут быть согласованы при помощи менеджера транзакций. Бизнес-процессы гораздо терпимее к несогласованностям, чем вы думаете, поскольку бизнесы чаще отдают предпочтение доступности (бизнес-процессы давно обладали интуитивным пониманием CAP-теоремы).
Пусть монолитам и не удается полностью избежать проблем несогласованности (как и других вопросов распределённости), однако они страдают от них гораздо меньше — особенно когда сами системы достаточно невелики.
Компромиссы между модульными границами и сложностями распределенных систем преследуют меня всю мою карьеру в этом бизнесе. Но самое значительное изменение за последние десять лет — это возросшая роль релиза в продакшн. В двадцатом столетии релизы в продакшн повсеместно были болезненным и редким событием, когда все задерживались на работе на ночь или на выходные для того, чтобы засунуть необычный кусочек ПО туда, где он сможет делать что-то полезное. Однако, в наши дни опытные команды часто релизятся в продакшн, и многие организации практикуют Continuous Delivery, что позволяет им делать продакшн релизы много раз за день.
Этот сдвиг имел глубокое воздействие на индустрию разработки ПО, и он тесно переплетен с движением микросервисов. Отдельные случаи попытки создания микросервисов были спровоцированны сложностью деплоя больших монолитов, когда небольшое изменение в части монолита могло привести к неудаче деплоймента проекта в целом. Ключевой принцип микросервисов состоит в том, что сервисы — это компоненты, которые можно деплоить по отдельности; поэтому теперь, когда требуется сделать изменение, необходимо протестировать и задеплоить только маленький сервис. В случае неудачи, вы не положите всю систему. В конце концов, из-за проектирования под отказ, даже полный отказ компонента не должен препятствовать работе других частей системы, пусть даже с некоторой изящной деградацией (graceful degradation).
Эти отношения работают в двух направлениях. Когда требуется часто деплоить много микросервисов, необходимо чтобы деплоймент работал согласованно. Вот почему быстрый деплой приложений и быстрое резервирование инфраструктуры — это необходимые условия микросервисов. Для любых задач сложнее элементарных вам потребуется прибегнуть к continuous delivery.
Важнейшее преимущество continuous delivery — это уменьшение продолжительности цикла между идеей и рабочим ПО. Компании, которые используют continuous delivery, могут быстро отвечать на изменения рынка и представлять новые функции быстрее, чем их конкуренты.
Несмотря на то, что многие люди называют continuous delivery одной из причин выбора микросервисов, необходимо помнить, что даже большие монолиты тоже могут быть доставлены непрерывно; самые известные случаи — Facebook и Etsy. Также хватает случаев, когда попытки перехода на микросервисную архитектуру спотыкаются о независимый деплоймент, когда для нескольких сервисов требуется внимательно координировать их релизы [2]. Несмотря на то, что я постоянно слышу о том, что continuous delivery с микросервисами становится проще, сам я не слишком убежден в этом; большую практическую ценность несет модульность — хотя обычно модульность тесно связана со скоростью релизов.
Возможность оперативно деплоить небольшие независимые единицы — это большое благо для разработки, но она накладывает дополнительную нагрузку на плечи эксплуатации, поскольку старые добрые полдюжины приложений теперь превращаются в сотни мелких микросервисов. Для многим компаний трудность регулярных попыток справиться с роем быстро меняющихся средств оказывается непомерно высокой.
Это усиливает роль continuous delivery. Если для монолитов continuous delivery — просто полезная штука, почти всегда стоящая затраченных на нее усилий, то в случае серьезной микросервисной архитектуры — это уже жизненная необходимость. Без автоматизации и сотрудничества, которые предполагает continuous delivery, справиться с десятками сервисов будет невозможно. Эксплуатационная сложность также возрастает из-за возрастающей необходимости управления этими сервисами и их мониторингом. И снова, здесь эти необязательные показатели уровня зрелости монолитов становятся необходимостью, если мы примешиваем к делу микросервисы.
Сторонники микросервисов любят указывать на то, что, благодаря тому, что каждый сервис маленький, их проще понимать. Опасность в том, что сложность при этом никуда не исчезает — она просто смещается в сторону взаимосвязей между сервисами. Вылезти это может в виде выросшей эксплуатационной сложности — например, при трудностях с отладкой поведения сервисов. Грамотные решения насчет разделения границ сервисов помогут уменьшить масштаб этой проблемы, но границы в неправильных местах могут сделают жизнь в разы хуже.
Управление возросшей эксплуатационной сложностью требует множества новых навыков и средств — причем больший акцент здесь идет на навыки. Интуиция подсказывает мне, что даже с более совершенными средствами (сейчас средства пока еще незрелые), минимальный порог вхождения для микросервисного окружения выше.
Но и необходимость продвинутых навыков и средств не является сложнейшей частью управления эксплуатационной сложностью. Для того, чтобы делать это эффективно, нужно внедрять в компании культуру девопс: серьезно улучшать степень взаимодействия разработчиков, эксплуатации и всех ответственных за релиз ПО. Изменения культуры в компании — тема больная, особенно для больших и старых организаций. Если вы не произведете эти изменения в части навыков и культуры, то, в случае монолитных приложений, процесс будет продвигаться с затруднениями; в случае же микросервисов — он будет серьезно травмирован.
Поскольку каждый микросервис — это независимо развертываемая единица, у вас есть значительная свобода в выборе технологий для его построения. Микросервисы могут быть написаны на разных языках, использовать различные библиотеки и различные хранилища данных. Это позволяет командам использовать подходящее средство для выполнения своей работы, поскольку некоторые языки и библиотеки лучше подходят для решения определенных проблем, чем другие.
Дискуссии на тему возможности использования различных технологий обычно фокусируются на выборе лучшего средства для работы, часто упуская из виду то, что важнейшее преимущество микросервисов гораздо более прозаично и заключается в версионировании. В монолите вы можете использовать только одну версию библиотеки, и эта ситуация часто ведет к проблемам с обновлениями; одна часть системы может требовать обновления для использования новых функций, в то время как это же обновление будет ломать другую часть системы. Работа с версионированием библиотек — это одна из тех проблем, которые становятся экспоненциально тяжелее по мере того, как растет кодовая база.
Существует опасность того, что большой зоопарк технологий может привести к перегрузке отдела разработки. Большая часть организаций, которые мне известны, поощряют использование ограниченного списка технологий. Это позволяет использовать общие средства для таких вещей, как мониторинг, что дает сервисам возможность придерживаться небольшого числа общих окружений.
Не стоит недооценивать ценность поддержки экспериментов. В случае с монолитной системой, принятые в начале решения о языках и фреймворках спустя некоторое время изменить становится уже тяжело. После десятка-другого подобных решений, команды могут «застрять» с неудобными для себя технологиями. Микросервисы позволяют командам экспериментировать с новыми средствами, а системам — постепенно мигрировать по одному сервису на передовые технологии (когда те начинают давать преимущества).
Последователи микросервисов часто говорят, что сервисы проще масштабировать, поскольку если на один сервис ложится большая часть нагрузки, то вы можете масштабировать только его, а не все приложение целиком. Однако, я с трудом могу вспомнить хоть один серьезный пример, который бы убедил меня в том, что это действительно более эффективно по сравнению с простым дублированием всего приложения.
Микросервисы позволяют отделить конфиденциальные данные и использовать их более безопасно. Более того, за счет обеспечения того, что весь трафик между микросервисами защищен, микросервисный подход может сделать эксплоит более трудной задачей. По мере роста важности фактора безопасности, со временем это может стать важной причиной использования микросервисов. Для преимущественно монолитных систем нет ничего необычного в том, чтобы создавать отдельные сервисы для работы с конфиденциальными данными.
Любой пост, касающийся любого архитектурный стиля и носящий общий характер, будет страдать от ограничений совета общего плана. Чтение постов вроде этого не сможет дать вам конкретный ответ; однако, подобные статьи помогут убедиться, что вы в курсе тех важных факторов, которые вам следует принять во внимание для предстоящего выбора. Каждый перечисленный фактор будет иметь разный вес для различных систем, причем иногда плюсы и минусы будут меняться местами (например, строгие границы модулей хороши в более сложных системах, но становятся помехой для простых). Любое ваше решение будет зависеть от применения критериев к вашим конкретным условиям — от значимости для вашей системы и влияния на нее в перспективе. Кроме того, наш опыт с микросервисной архитектурой относительно невелик. Обычно судить об архитектурных решениях можно не раньше, прежде чем система «повзрослеет» и вы поймете, каково работать с ней годы спустя после начала разработки; пока что у сообщества не набралось достаточного количества анекдотов о системах-долгожителях, основанных на микросервисных архитектурах.
Монолиты и микросервисы — это не черно-белый выбор. Оба определения — нечеткие, а это означает, что многие системы будут лежать в зоне размытой границы. Будут и системы, которые не впишутся ни в одну из категорий. Большинство, включая меня самого, говорит о микросервисах в контрасте с монолитами, потому что имеет смысл противопоставлять их более привычному стилю; однако, мы должны помнить, что существуют системы, которые не вписываются ни в одну из этих категорий. Я размышляю о монолитах и микросервисах как о двух областях в одном большом пространстве архитектур. Эти области заслуживают обозначения потому, что у них есть интересные характеристики, по которым их можно обсуждать и сравнивать, но ни один компетентный архитектор не станет рассматривать их как полноценное разделение всего архитектурного пространства.
Основным выводом, который должен быть широко принят общественностью, должно стать признание факта Микросервис Премиум: микросервисы накладывают цену на продуктивность, которая может быть скомпенсирована только в более сложных системах. И если вы можете справиться со сложностью вашей системы при помощи монолитной архитектуры, то вы не должны прибегать к использованию микросервисов.
Впрочем, популярные в последнее время дискуссии вокруг темы микросервисов не должны отвлекать от более важных проблем, которые приводят к успехам и провалам проектов по разработке ПО. Общие факторы — к примеру, качество людей в команде, как хорошо они взаимодействуют в команде друг с другом, степень доступности эксперта в предметной области для общения — будут иметь большее значение, чем решение об использовании или не-использовании микросервисов. Касаемо чисто технического уровня, здесь важнее сделать фокус на таких вещах как чистый код, хорошее тестирование и внимание к эволюционной архитектуре.
В книге Сэма Ньюмана «Создание микросервисов» в первой главе приводится подробный список преимуществ микросервисной архитектуры.
Пост Бенджамина Вутэна «Микросервисы: бесплатных ланчей не бывает!» на High Scaleability известен как один из первых и все еще лучший список недостатков микросервисов.
1. Некоторые люди считают термин «монолит» оскорблением, всегда предполагая под ним бедную модульную структуру. В мире микросервисов большинство не вкладывают в термин этого смысла; они определяют «монолит» чисто как приложение, построенное как отдельная единица. Конечно, отдельные ярые поклонники микросервисов уверены в том, что большинство монолитов заканчивает свой путь Большими комками грязи, но я не встречал ни одного, кто смог бы опровергнуть тезис о том, что построить хорошо структурированный монолит вполне возможно.
2. Возможность независимого деплоя сервисов — это часть определения микросервисов. Поэтому разумно говорить, что набор сервисов, которые необходимо разворачивать в определенном порядке — это не микросервисная архитектура. Также уместно заметить, что многие команды, которые пытаются реализовывать микросервисную архитектуру, попадают в беду, поскольку им приходится прийти к координированному деплойменту сервисов.
Многие команды разработчиков нашли архитектурный стиль микросервисов подходом, превосходящим монолитную архитектуру; другие команды выяснили, что для них микросервисы — лишняя обуза, подрывающая производительность разработки. Как и у любого стиля архитектуры, у микросервисов есть свои плюсы и минусы. Для того, чтобы делать осознанный выбор, вы должны понимать эти свойства и уметь рассматривать их на фоне собственных конкретных условий.
Микросервисы дают преимущества… | …ценою издержек |
---|---|
Жесткие границы модулей Strong Module Boundaries Микросервисы усиливают модульную структуру, что особенно важно для больших команд разработчиков. |
Распределённость Distribution Распределенные системы тяжелее программировать, поскольку удаленные вызовы медленные и всегда рискуют неудачей-отказом. |
Независимый деплоймент Independent Deployment Простые сервисы проще деплоить, и, поскольку они автономны, меньше вероятность отказа системы в случае, если что-то идет не так. |
Консистентность в конечном счете Eventual Consistency Поддержка cтрогой консистентности чрезвычайно сложна для распределённых систем, и это означает, что придется иметь дело с консистентностью в конечном счете. |
Технологическое разнообразие Technology Diversity С микросервисами вы можете смешивать несколько языков, фреймворков и технологий хранения данных. |
Эксплуатационная сложность Operational Complexity Вам потребуется опытная команда эксплуатации для управления множеством сервисов, которые будут регулярно редеплоиться. |
Жесткие границы модулей
Первая серьезная выгода от микросервисов — это строгие границы модулей. Это важное преимущество, хотя в то же время и странное — потому что, в теории, нет такой причины, по которой микросервисы должны иметь более строгие границы модулей, чем монолит.
Итак, что я подразумеваю под жесткими границами модулей? Я думаю, большинство из нас согласится с тем, что программы лучше разбивать на модули — фрагменты ПО, отделенные друг от друга. Ваши модули должны быть такими, чтобы мне при необходимости изменения части системы чаще всего было нужно разобраться только с небольшой ее частью для внесения изменений, и чтобы нужную мне маленькую часть я смог бы найти достаточно быстро. Хорошая модульная структура будет полезна в случае любого ПО, но значимость этого свойства растет экспоненциально вместе с ростом размера самого ПО. Возможно, эта значимость растет по той причине, что команда, занимающаяся разработкой программы, также растет в размерах.
Защитники микросервисов рады будут рассказать вам о законе Конвея, утверждающем, что структура программной системы отражает коммуникационную структуру внутри компании, которая ее построила. В случае с большими командами — в частности, распределенными географически — важно строить структуру ПО так, чтобы она отражала тот факт, что коммуникация между разными командами будет более редкой и более формальной, чем та, что ведется внутри обычной команды. Микросервисы позволяют каждой команде заботиться об относительно независимых единицах при помощи этого коммуникационного паттерна.
Как я говорил раньше, не существует такой причины, по которой монолитная система должна строиться без хорошей модульной структуры. [1] Но многочисленные наблюдения показывают, что подобное делается довольно-таки редко, поскольку Большой комок грязи (Big Ball of Mud) — это самый популярный архитектурный паттерн. Фрустрация от этого явления, вместе с одинаково печальной судьбой многих монолитов, привела отдельные команды разработчиков на путь микросервисов. Принцип независимости модулей работает потому, что границы модуля служат барьером для ссылок между модулями. Проблема состоит в том, что в случае с монолитной системой не составляет особого труда подлезть под этот барьер; подобный поступок может быть полезным тактическим приемом, предоставляя короткий путь к быстрой разработке и запуску каких-то функций системы, но в перспективе это подрывает основы модульной структуры и существенно ухудшает продуктивность команды. Раскладывание модулей по различным сервисам делает границы тверже, создавая дополнительные препятствия на пути любителей сомнительных решений для срезания пути.
Важным аспектом этой связи является постоянство данных (persistent data). Одна из ключевых характеристик микросервисов — это децентрализованное управление данными; этот принцип означает, что каждый сервис управляет своей базой данных и любой другой сервис должен использовать API этого сервиса, чтобы добраться до нее. Подобное решение позволяет избавиться от интеграционных баз данных, которые являются основными источниками неприятных связей в больших системах.
Важно подчеркнуть, что вполне возможно иметь «честные» границы модулей внутри монолита, но это требует серьезной дисциплины. При должном усердии, можно построить и Большой комок микросервисной грязи, однако на этот раз придется основательно постараться и приложить существенно больше усилий, чем в случае монолита. Как мне это представляется — использование микросервисов увеличивает вероятность того, что с модульностью в вашем проекте все будет в порядке. Если вы уверены в дисциплине внутри вашей команды, то, возможно, это преимущество не стоит принимать во внимание; однако, по мере роста команды соблюдать дисциплину будет все труднее — в то время как важность поддержки границ модулей будет продолжать расти.
Это преимущество становится помехой, если вы неправильно определите границы. Это одна из двух главных причин стратегии "Сначала монолит", и даже тем, кто предпочитает начинать сразу с микросервисов, стоит это делать только в том случае, если они имеют дело с хорошо знакомой предметной областью.
Но я еще не закончил с предостережениями. Вы сможете сказать, удачно ли в системе используется модульность только после того, как пройдет время. Таким образом, оценить реально ли микросервисы приводят к лучшей модульности систем мы сможем только после того, как эти системы проработают как минимум несколько лет. Более того, ранние последователи технологий обычно являются более талантливыми разработчиками, так что придется ждать еще дольше, прежде чем мы увидим, что станет с микросервисными системами, написанными средними командами. Даже тогда, мы должны признать что средние команды пишут среднее ПО, поэтому вместо сравнения их результатов с достижениями топовых команд, мы должны будем сравнивать с аналогичным ПО в монолитной архитектуре — а это уже является сложной гипотетической ситуацией, слабо годящейся для оценки.
Все факты, что мне известны на данный момент — это предварительные данные, которые я собрал от своих знакомых, уже использующих в работе данный стиль; по их словам, поддержка модулей упростилась весьма значительно.
Один услышанный мною кейс был достаточно интересным. Команда сделала неверный выбор, использовав микросервисы для системы, которая не была достаточно сложной, чтобы получить от этого решения выгоду. Проект попал в беду и его нужно было спасать, поэтому на него было брошено еще больше людей. В этот момент микросервисная архитектура неожиданно повернулась положительной стороной: система смогла проглотить приток разработчиков, и команда могла управлять разработкой гораздо проще, чем обычно бывает в аналогичных случаях с монолитными архитектурами. В результате, проект ускорился до продуктивности большей, чем можно было бы ожидать даже от монолита, давая команде шанс наверстать упущенное. Исход оказался негативным — итоговое ПО стоило больше человеко-часов, чем если бы оно было построено монолитом; однако, микросервисы показали свою способность к наращиванию скоростей.
Распределённость
Итак, микросервисы используют распределённую систему для улучшения модульности. Но распределённое ПО имеет большой недостаток: он заключается в том факте, что оно — распределённое. Как только вы разыграете карту распределённости, вы навлечете на себя много сложностей. Вряд ли комьюнити вокруг микросервисов относится к этим издержкам так же наивно, как движение за распределённые объекты в свое время, но сложности от этого никуда не уходят.
Первая из них — производительность. В наши дни вам гораздо реже доведется увидеть, что слабым местом системы станут тормоза вызовов функций внутри процессов; однако, удаленные вызовы — медленные: если ваш сервис вызывает пять удаленных сервисов, каждый из которых в свою очередь вызывает другие пять удаленных сервисов, то время ответа от них создаст ужасные задержки в масштабе всей системы.
Разумеется, можно много чего сделать для уменьшения вреда от этой проблемы. Во-первых, можно увеличить гранулярность вызовов, чтобы нужно было делали меньшее их количество. Этот шаг сделает программную модель запутаннее: теперь вам придется думать, как собирать в пакеты ваши межсервисные взаимодействия. Кроме того, это не слишком далеко уведет вас от исходной проблемы, поскольку вы все равно должны будете вызвать каждый взаимодействующий сервис хотя бы раз.
Следующий способ уменьшить боль — это использовать асинхронность. Если сделать шесть асинхронных вызовов параллельно, то вы замедлитесь до скорости самого медленного вызова — вместо того, чтобы замедлиться до суммы всех их задержек, как было раньше. Это может дать большой прирост производительности, но будет стоить дополнительных умственных усилий. Асинхронное программирование — сложный предмет: в нем тяжело разбираться, в его случае гораздо труднее проводить отладку. Но в большей части рассказов про микросервисы, что мне довелось слышать, асинхронность потребовалась для получения приемлемой производительности.
Следом за скоростью идет надежность. Ожидается, что функции внутри процессов работают как надо, в то время как удаленные вызовы могут потерпеть неудачу в любой момент. Чем больше микросервисов используется, тем больше появляется потенциальных точек отказа. Мудрые разработчики знают про это и проектируют для отказа. К счастью, те же тактики, что требуются для асинхронного программирования, также неплохо подходят и для обработки отказов, и в результате их применения возможно улучшение «эластичности» системы. Однако, компенсацией издержек это назвать сложно — вам остается дополнительная сложность в выяснении последствий отказа для каждого удаленного вызова.
И это я перечислил всего лишь два важнейших заблуждения о распределённых вычислениях (на русском).
У описанной проблемы есть и свои нюансы. Во-первых, многие из этих вопросов возникают в монолитных архитектурах по мере их роста. Немногие монолиты действительно содержатся «сами в себе»; обычно же в них присутствуют и другие системы — зачастую это легаси-системы, с которыми нужно работать. Взаимодействие с ними вовлекает походы по сети и попадание в те же проблемы. Вот почему многие склонны как можно быстрее переехать на микросервисы для поддержки взаимодействия с внешними удаленными системами. С этой задачей помогает справиться опыт — опытная команда будет способна лучше справиться с проблемами распределенности.
Но распределённость — это всегда издержки. Я всегда неохотно разыгрываю ее карту, и, как по мне, многие слишком поспешно делают выбор в пользу распределённость, поскольку недооценивают ее проблемы.
Консистентность в конечном счете
Я уверен, что вы знакомы с веб-сайтами, для работы с которыми нужно иметь крепкие нервы. Вы апдейтите что-нибудь на странице, сайт обновляет страницу и вашего обновления нет. Вы ждете минуту или две, делаете обновление страницы, и ваш апдейт наконец появляется.
Эта весьма раздражающая проблема юзабилити почти наверняка возникает из-за рисков так называемой «согласованности в конечном счете». Ваш апдейт получила розовая нода, но запрос обрабатывала зеленая нода, и пока зеленая нода получала свой апдейт от розовой, вы застряли в окне несогласованности. Рано или поздно все согласуется, но до этого момента вы будете задаваться вопросом, не пошло ли вдруг что-то не так.
Подобные несогласованности не только причинять раздражение, но и нести гораздо более серьезные угрозы. Например, бизнес-логика может принять решения, основываясь на несогласованной информации — и когда это случится, будет чрезвычайно трудно выяснить, что пошло не так, потому что расследование начнется гораздо позже того момента, когда закроется окно несогласованности.
Микросервисы включают в себя проблемы консистентности в конечном счете — это происходит за счет «похвальной» настойчивости микросервисов на децентрализованном управлении данными. В случае с монолитом, вы можете обновить несколько вещей вместе в одной транзакции. Микросервисы в этом случае требуют обновления нескольких ресурсов, и распределенные транзакции вызывают неодобрение (по уважительным причинам). Так что теперь разработчикам приходится быть в курсе вопросов согласованности и выяснить, как обнаруживать несинхронизованность происходящего прежде, чем делать исправления в коде, о которых они потом пожалеют.
Мир монолитов тоже несвободен от этих проблем. С ростом систем, появляется всё большая необходимость в кэшировании для улучшения производительности, а инвалидация кэша — это еще одна Сложная Проблема. Большинство приложений нуждается в автономных блокировках для избежания долгоживущих транзакций баз данных. Внешние системы нуждаются в обновлениях, которые не могут быть согласованы при помощи менеджера транзакций. Бизнес-процессы гораздо терпимее к несогласованностям, чем вы думаете, поскольку бизнесы чаще отдают предпочтение доступности (бизнес-процессы давно обладали интуитивным пониманием CAP-теоремы).
Пусть монолитам и не удается полностью избежать проблем несогласованности (как и других вопросов распределённости), однако они страдают от них гораздо меньше — особенно когда сами системы достаточно невелики.
Независимый деплоймент
Компромиссы между модульными границами и сложностями распределенных систем преследуют меня всю мою карьеру в этом бизнесе. Но самое значительное изменение за последние десять лет — это возросшая роль релиза в продакшн. В двадцатом столетии релизы в продакшн повсеместно были болезненным и редким событием, когда все задерживались на работе на ночь или на выходные для того, чтобы засунуть необычный кусочек ПО туда, где он сможет делать что-то полезное. Однако, в наши дни опытные команды часто релизятся в продакшн, и многие организации практикуют Continuous Delivery, что позволяет им делать продакшн релизы много раз за день.
Этот сдвиг имел глубокое воздействие на индустрию разработки ПО, и он тесно переплетен с движением микросервисов. Отдельные случаи попытки создания микросервисов были спровоцированны сложностью деплоя больших монолитов, когда небольшое изменение в части монолита могло привести к неудаче деплоймента проекта в целом. Ключевой принцип микросервисов состоит в том, что сервисы — это компоненты, которые можно деплоить по отдельности; поэтому теперь, когда требуется сделать изменение, необходимо протестировать и задеплоить только маленький сервис. В случае неудачи, вы не положите всю систему. В конце концов, из-за проектирования под отказ, даже полный отказ компонента не должен препятствовать работе других частей системы, пусть даже с некоторой изящной деградацией (graceful degradation).
Эти отношения работают в двух направлениях. Когда требуется часто деплоить много микросервисов, необходимо чтобы деплоймент работал согласованно. Вот почему быстрый деплой приложений и быстрое резервирование инфраструктуры — это необходимые условия микросервисов. Для любых задач сложнее элементарных вам потребуется прибегнуть к continuous delivery.
Важнейшее преимущество continuous delivery — это уменьшение продолжительности цикла между идеей и рабочим ПО. Компании, которые используют continuous delivery, могут быстро отвечать на изменения рынка и представлять новые функции быстрее, чем их конкуренты.
Несмотря на то, что многие люди называют continuous delivery одной из причин выбора микросервисов, необходимо помнить, что даже большие монолиты тоже могут быть доставлены непрерывно; самые известные случаи — Facebook и Etsy. Также хватает случаев, когда попытки перехода на микросервисную архитектуру спотыкаются о независимый деплоймент, когда для нескольких сервисов требуется внимательно координировать их релизы [2]. Несмотря на то, что я постоянно слышу о том, что continuous delivery с микросервисами становится проще, сам я не слишком убежден в этом; большую практическую ценность несет модульность — хотя обычно модульность тесно связана со скоростью релизов.
Эксплуатационная сложность
Возможность оперативно деплоить небольшие независимые единицы — это большое благо для разработки, но она накладывает дополнительную нагрузку на плечи эксплуатации, поскольку старые добрые полдюжины приложений теперь превращаются в сотни мелких микросервисов. Для многим компаний трудность регулярных попыток справиться с роем быстро меняющихся средств оказывается непомерно высокой.
Это усиливает роль continuous delivery. Если для монолитов continuous delivery — просто полезная штука, почти всегда стоящая затраченных на нее усилий, то в случае серьезной микросервисной архитектуры — это уже жизненная необходимость. Без автоматизации и сотрудничества, которые предполагает continuous delivery, справиться с десятками сервисов будет невозможно. Эксплуатационная сложность также возрастает из-за возрастающей необходимости управления этими сервисами и их мониторингом. И снова, здесь эти необязательные показатели уровня зрелости монолитов становятся необходимостью, если мы примешиваем к делу микросервисы.
Сторонники микросервисов любят указывать на то, что, благодаря тому, что каждый сервис маленький, их проще понимать. Опасность в том, что сложность при этом никуда не исчезает — она просто смещается в сторону взаимосвязей между сервисами. Вылезти это может в виде выросшей эксплуатационной сложности — например, при трудностях с отладкой поведения сервисов. Грамотные решения насчет разделения границ сервисов помогут уменьшить масштаб этой проблемы, но границы в неправильных местах могут сделают жизнь в разы хуже.
Управление возросшей эксплуатационной сложностью требует множества новых навыков и средств — причем больший акцент здесь идет на навыки. Интуиция подсказывает мне, что даже с более совершенными средствами (сейчас средства пока еще незрелые), минимальный порог вхождения для микросервисного окружения выше.
Но и необходимость продвинутых навыков и средств не является сложнейшей частью управления эксплуатационной сложностью. Для того, чтобы делать это эффективно, нужно внедрять в компании культуру девопс: серьезно улучшать степень взаимодействия разработчиков, эксплуатации и всех ответственных за релиз ПО. Изменения культуры в компании — тема больная, особенно для больших и старых организаций. Если вы не произведете эти изменения в части навыков и культуры, то, в случае монолитных приложений, процесс будет продвигаться с затруднениями; в случае же микросервисов — он будет серьезно травмирован.
Технологическое разнообразие
Поскольку каждый микросервис — это независимо развертываемая единица, у вас есть значительная свобода в выборе технологий для его построения. Микросервисы могут быть написаны на разных языках, использовать различные библиотеки и различные хранилища данных. Это позволяет командам использовать подходящее средство для выполнения своей работы, поскольку некоторые языки и библиотеки лучше подходят для решения определенных проблем, чем другие.
Дискуссии на тему возможности использования различных технологий обычно фокусируются на выборе лучшего средства для работы, часто упуская из виду то, что важнейшее преимущество микросервисов гораздо более прозаично и заключается в версионировании. В монолите вы можете использовать только одну версию библиотеки, и эта ситуация часто ведет к проблемам с обновлениями; одна часть системы может требовать обновления для использования новых функций, в то время как это же обновление будет ломать другую часть системы. Работа с версионированием библиотек — это одна из тех проблем, которые становятся экспоненциально тяжелее по мере того, как растет кодовая база.
Существует опасность того, что большой зоопарк технологий может привести к перегрузке отдела разработки. Большая часть организаций, которые мне известны, поощряют использование ограниченного списка технологий. Это позволяет использовать общие средства для таких вещей, как мониторинг, что дает сервисам возможность придерживаться небольшого числа общих окружений.
Не стоит недооценивать ценность поддержки экспериментов. В случае с монолитной системой, принятые в начале решения о языках и фреймворках спустя некоторое время изменить становится уже тяжело. После десятка-другого подобных решений, команды могут «застрять» с неудобными для себя технологиями. Микросервисы позволяют командам экспериментировать с новыми средствами, а системам — постепенно мигрировать по одному сервису на передовые технологии (когда те начинают давать преимущества).
Вторичные факторы
Последователи микросервисов часто говорят, что сервисы проще масштабировать, поскольку если на один сервис ложится большая часть нагрузки, то вы можете масштабировать только его, а не все приложение целиком. Однако, я с трудом могу вспомнить хоть один серьезный пример, который бы убедил меня в том, что это действительно более эффективно по сравнению с простым дублированием всего приложения.
Микросервисы позволяют отделить конфиденциальные данные и использовать их более безопасно. Более того, за счет обеспечения того, что весь трафик между микросервисами защищен, микросервисный подход может сделать эксплоит более трудной задачей. По мере роста важности фактора безопасности, со временем это может стать важной причиной использования микросервисов. Для преимущественно монолитных систем нет ничего необычного в том, чтобы создавать отдельные сервисы для работы с конфиденциальными данными.
Подведение итогов
Любой пост, касающийся любого архитектурный стиля и носящий общий характер, будет страдать от ограничений совета общего плана. Чтение постов вроде этого не сможет дать вам конкретный ответ; однако, подобные статьи помогут убедиться, что вы в курсе тех важных факторов, которые вам следует принять во внимание для предстоящего выбора. Каждый перечисленный фактор будет иметь разный вес для различных систем, причем иногда плюсы и минусы будут меняться местами (например, строгие границы модулей хороши в более сложных системах, но становятся помехой для простых). Любое ваше решение будет зависеть от применения критериев к вашим конкретным условиям — от значимости для вашей системы и влияния на нее в перспективе. Кроме того, наш опыт с микросервисной архитектурой относительно невелик. Обычно судить об архитектурных решениях можно не раньше, прежде чем система «повзрослеет» и вы поймете, каково работать с ней годы спустя после начала разработки; пока что у сообщества не набралось достаточного количества анекдотов о системах-долгожителях, основанных на микросервисных архитектурах.
Монолиты и микросервисы — это не черно-белый выбор. Оба определения — нечеткие, а это означает, что многие системы будут лежать в зоне размытой границы. Будут и системы, которые не впишутся ни в одну из категорий. Большинство, включая меня самого, говорит о микросервисах в контрасте с монолитами, потому что имеет смысл противопоставлять их более привычному стилю; однако, мы должны помнить, что существуют системы, которые не вписываются ни в одну из этих категорий. Я размышляю о монолитах и микросервисах как о двух областях в одном большом пространстве архитектур. Эти области заслуживают обозначения потому, что у них есть интересные характеристики, по которым их можно обсуждать и сравнивать, но ни один компетентный архитектор не станет рассматривать их как полноценное разделение всего архитектурного пространства.
Основным выводом, который должен быть широко принят общественностью, должно стать признание факта Микросервис Премиум: микросервисы накладывают цену на продуктивность, которая может быть скомпенсирована только в более сложных системах. И если вы можете справиться со сложностью вашей системы при помощи монолитной архитектуры, то вы не должны прибегать к использованию микросервисов.
Впрочем, популярные в последнее время дискуссии вокруг темы микросервисов не должны отвлекать от более важных проблем, которые приводят к успехам и провалам проектов по разработке ПО. Общие факторы — к примеру, качество людей в команде, как хорошо они взаимодействуют в команде друг с другом, степень доступности эксперта в предметной области для общения — будут иметь большее значение, чем решение об использовании или не-использовании микросервисов. Касаемо чисто технического уровня, здесь важнее сделать фокус на таких вещах как чистый код, хорошее тестирование и внимание к эволюционной архитектуре.
Дополнительная литература
В книге Сэма Ньюмана «Создание микросервисов» в первой главе приводится подробный список преимуществ микросервисной архитектуры.
Пост Бенджамина Вутэна «Микросервисы: бесплатных ланчей не бывает!» на High Scaleability известен как один из первых и все еще лучший список недостатков микросервисов.
Примечания
1. Некоторые люди считают термин «монолит» оскорблением, всегда предполагая под ним бедную модульную структуру. В мире микросервисов большинство не вкладывают в термин этого смысла; они определяют «монолит» чисто как приложение, построенное как отдельная единица. Конечно, отдельные ярые поклонники микросервисов уверены в том, что большинство монолитов заканчивает свой путь Большими комками грязи, но я не встречал ни одного, кто смог бы опровергнуть тезис о том, что построить хорошо структурированный монолит вполне возможно.
2. Возможность независимого деплоя сервисов — это часть определения микросервисов. Поэтому разумно говорить, что набор сервисов, которые необходимо разворачивать в определенном порядке — это не микросервисная архитектура. Также уместно заметить, что многие команды, которые пытаются реализовывать микросервисную архитектуру, попадают в беду, поскольку им приходится прийти к координированному деплойменту сервисов.