Микросервисы для начинающих
Оглядываясь примерно на пять лет назад в прошлое, можно заметить, насколько сильно с тех пор изменилось отношение к архитектуре микросервисов. Поначалу они были чрезвычайно популярны. После успеха Netflix, Amazon и Gilt.com разработчики решили, что де-факто разработка микросервисов не отличается от разработки приложений. Теперь же все поняли, что микросервисы представляют из себя новый архитектурный стиль, который эффективен для решения определенных задач, имеет свои плюсы и минусы.
Чтобы понять, что такое микросервисы и в каких случаях их следует использовать, мы обратились к Джейме Буэльта (Jaime Buelta), автору книги «Hands-On Docker for Microservices with Python». Он рассказал о преимуществах этой архитектуры, а также поделился рекомендациями для разработчиков, планирующих перейти на нее с монолитов.

Преимущества и риски
Традиционное монолитное приложение объединяет все свои возможности в едином связанном модуле. В случае микросервисов все наоборот. Приложение делится на более мелкие автономные службы, которые можно независимо развертывать, обновлять и заменять. Каждый микросервис создается для одной бизнес-цели и может взаимодействовать с другими микросервисами с помощью простых механизмов.
Буэльта объясняет: «Микросервисная архитектура — это способ структурирования системы, при которой несколько независимых сервисов общаются друг с другом определенным образом (обычно это происходит с помощью web-сервисов RESTful). Ключевая особенность состоит в том, что каждый микросервис способен обновляться и развертываться независимо от остальных».
Архитектура микросервисов определяет не только то, как вы создаете свое приложение, но и то, как организована ваша команда.
«Одна независимая команда может полностью отвечать за микросервис. Это позволяет организациям расти, не сталкивая разработчиков друг с другом», — объясняет Буэльта.
Одно из основных преимуществ микросервисов заключается в том, что они позволяют внедрять нововведения без особого влияния на систему в целом. С помощью микросервисов вы можете выполнять горизонтальное масштабирование, иметь четкие границы модулей, использовать разнообразные технологии и вести параллельную разработку.
На вопрос о рисках, связанных с микросервисами, Буэльта ответил: «Главная сложность при внедрении архитектуры (особенно при переходе с монолита) заключается в создании дизайна, в котором сервисы действительно будут независимыми. Если этого не удастся добиться, то межсервисные связи станут сложнее, что приведет к дополнительным расходам. Микросервисам нужны профессионалы, которые сформируют направления развития в долгосрочной перспективе. Я рекомендую организациям, которые хотят перейти на такую архитектуру, назначить кого-то ответственным за «общую картину». На микросервисы нужно смотреть более широко», — считает Джейме.
Переход от монолита к микросервисам
Мартин Фаулер, известный автор и консультант по программному обеспечению, советует придерживаться принципа «сначала — монолит». Это связано с тем, что использовать микросервисную архитектуру с самого начала разработки рискованно, поскольку в большинстве случаев она подходит только для сложных систем и больших команд разработчиков.
«Основной критерий, который должен побуждать вас к переходу на новую архитектуру — это численность вашей команды. Небольшим группам не стоит этого делать. В подобных условиях разработчики и так понимают все, что происходит с приложением, и всегда могут задать уточняющий вопрос коллеге. Монолит отлично работает в этих ситуациях, и поэтому практически каждая система начинается с него», — считает Джейме. Это подтверждает «правило двух пицц» Amazon, согласно которому команду, ответственную за один микросервис, можно прокормить двумя пиццами — иначе она слишком большая.
«По мере роста компании и увеличения команд разработчиков может потребоваться лучшая координация. Программисты начинают часто мешать друг другу. Понять цель конкретного фрагмента кода становится сложнее. В таких случаях переход на микросервисы имеет смысл — это поможет разделить обязанности и внести ясность в общую картину системы. Каждая команда может ставить свои собственные цели и работать в основном самостоятельно, выдавая понятный внешний интерфейс. Однако, чтобы такой переход имел смысл, разработчиков должно быть много», — добавляет Буэльта.
Рекомендации по переходу на микросервисы
Отвечая на вопрос о том, какие практические рекомендации могут использовать разработчики при переходе на микросервисы, Буэльта заявил: «Ключом к успешной архитектуре микросервисов является то, что каждый сервис должен быть максимально независим».
Возникает вопрос: «Как вы можете сделать сервисы независимыми?». Лучший способ обнаружить взаимозависимость системы — подумать о новых возможностях: «Если вы хотите добавить новую функцию, можно ли будет ее реализовать, изменив лишь один сервис? Какие виды функций потребуют координации нескольких микросервисов? Они будут использоваться часто или редко? Невозможно создать идеальный дизайн, но, по крайней мере, с его помощью можно принимать правильные и обоснованные решения», — объясняет Буэльта.
Джейме советует переходить на архитектуру правильно, чтобы потом все не переделывать. «После завершения перехода изменить границы микросервисов будет тяжелее. Стоит уделить побольше времени на начальную фазу проекта», — добавляет он.
Переход с одного шаблона проектирования на другой — это серьезный шаг. Мы спросили, с какими проблемами Джейме и его команда сталкивались во время миграции на микросервисы, на что он ответил:
«На деле основные трудности связаны с людьми. Эти проблемы, как правило, недооценивают, но переход на микросервисы фактически меняет способ работы разработчиков. Задача не из легких!». Он добавляет: «Я лично сталкивался с подобными проблемами. Например, мне приходилось обучать и давать советы разработчикам. Особенно важно объяснять, почему необходимы те или иные изменения. Это помогает людям понять причины внедрения всех нововведений, которые могут прийтись им не по душе.
При переходе от монолитной архитектуры много сложностей может возникнуть при развертывании приложения, которое раньше выпускалось в виде единого модуля. Оно требует более тщательного анализа для обеспечения обратной совместимости и минимизации рисков. Справиться с этой задачей порой очень нелегко».
Причины выбора Docker, Kubernetes и Python в качестве технологического стека
Мы спросили Буэльту, какие технологии он предпочитает для внедрения микросервисов. Касательно выбора языка ответ оказался прост: «Python для меня — лучший вариант. Это мой любимый язык программирования. Этот язык хорошо подходит для микросервисов. Его удобно читать и легко применять. Кроме того, Python обладает широким функционалом для веб-разработки и динамичной экосистемой сторонних модулей для любых потребностей. К этим потребностям относится подключение к другим системам, например, к базам данных, внешним API и т.д.».
Docker часто рекламируется как один из самых важных инструментов для микросервисов. Буэльта объяснил, почему:
«Docker позволяет инкапсулировать и копировать приложение в удобных стандартизированных пакетах. Это уменьшает неопределенность и сложность среды. Также это значительно упрощает переход от разработки к производству приложений. Вдобавок ко всему, уменьшается время использования оборудования. Вы можете разместить несколько контейнеров в разных средах (даже в разных операционных системах) в одной физической коробке или виртуальной машине».
«Kubernetes позволяет развертывать несколько контейнеров Docker, работающих скоординированным образом. Это заставляет разработчиков мыслить кластеризованно, помня о производственной среде. Также это позволяет определять кластер с помощью кода, чтобы новые развертывания или изменения конфигурации определялись в файлах. Все это делает возможными методы наподобие GitOps (о них я писал в своей книге), при этом сохраняя полную конфигурацию в системе управления версиями. Каждое изменение вносится определенным и обратимым образом, поскольку оно представляет из себя регулярное git-слияние. Благодаря этому можно очень легко восстанавливать или дублировать инфраструктуру».
«Придется потратить время, чтобы обучиться Docker и Kubernetes, но это того стоит. Оба инструмента очень мощные. К тому же, они поощряют вас работать таким образом, чтобы избежать проблем при производстве», — считает Буэльта.
Многоязычные микросервисы
При разработке микросервисов можно использовать разнообразные технологии, поскольку за каждый из них в идеале отвечает независимая команда. Буэльта поделился своим мнением о многоязычных микросервисах: «Многоязычные микросервисы — это здорово! Это одно из основных преимуществ архитектуры. Типичный пример многоязычного микросервиса — перенос устаревшего кода, написанного на одном языке, на новый. Микросервис может заменить собой любой другой, который предоставляет тот же внешний интерфейс. При этом его код будет совершенно иным. К примеру, я переходил со старых приложений PHP, заменяя их аналогами, написанными на Python». Джейме добавил: «Работа с двумя или более платформами одновременно поможет лучше разобраться в них и понимать, в каких случаях их лучше использовать».
Хотя возможность использовать многоязычные микросервисы — это большое преимущество архитектуры, оно также может увеличить операционные издержки. Буэльта советует: «Надо знать меру. Нет смысла каждый раз использовать новый инструмент и лишать команды возможности делиться знаниями друг с другом. Конкретные цифры могут зависеть от размера компании, но, как правило, нет смысла использовать больше двух или трех разных языков без серьезной на то причины. Не надо раздувать стек технологий — тогда разработчики смогут делиться знаниями и начнут использовать имеющиеся инструменты наиболее эффективно».
Об авторе
Джейме Буэльта (Jaime Buelta) — профессиональный программист и Python-разработчик, который за свою многолетнюю карьеру познакомился со множеством различных технологий. Он разрабатывал программное обеспечение для различных областей и отраслей, включая аэрокосмическую, сетевую и коммуникационную, а также промышленные системы SCADA, онлайн-сервисы для видеоигр и финансовые сервисы.
В составе различных компаний он имел дело с такими функциональными областями, как маркетинг, менеджмент, продажи и геймдизайн. Джейме является ярым сторонником автоматизации и хочет, чтобы всю тяжелую работу выполняли компьютеры, позволив людям сосредоточиться на более важных вещах. В настоящее время он живет в Дублине и регулярно выступает на конференциях PyCon в Ирландии.
Что такое микросервисы: особенности архитектуры, примеры использования, инструменты
Авторизуйтесь
Что такое микросервисы: особенности архитектуры, примеры использования, инструменты
Архитектурный стиль микросервисов — это подход, при котором система строится как набор независимых и слабосвязанных сервисов, которые можно создавать, используя различные языки программирования и технологии хранения данных. Концепция микросервисов позволяет поддерживать слабую связанность сервисов в процессе работы над системой, что определяют паттерны Low Coupling и High Cohesion.
Подробности — в видео и текстовой расшифровке ниже.
Монолит vs микросервисы
При монолитной архитектуре система обычно состоит из 3 блоков: пользовательский интерфейс, хранилище данных и серверная часть. Серверная часть обрабатывает запросы, выполняет бизнес-логику, работает с БД, заполняет HTML-страницы. Любое изменение в системе приводит к обновлению версии серверной части приложения.
В случае с микросервисной архитектурой обновляется только изменённый сервис. Если изменения затрагивают интерфейс сервиса, это потребует координации всех его клиентов. Цель хорошей микросервисной архитектуры — максимально уменьшить необходимость координации сервисов.
Что такое контракт
Контракт — это формализация возможностей взаимодействия с микросервисом. В случае с REST API эндпоинты сервиса и схема данных являются контрактом. Первоначальная разработка архитектуры — это декомпозиция системы на слабосвязанные сервисы, создание интерфейсов и связей между ними, поддержка целостности данных без потери производительности. Помочь с решением данной задачи могут шаблоны Tolerant Reader и Consumer-Driven Contracts.
Микросервисная команда
Команда не должна включать в себя больше людей, чем можно насытить двумя пиццами. Такое правило использовала компания Amazon при распиливания своего монолита в 2002 году. Вполне допустимо и правило developer per service, то есть один разработчик на один микросервис.
18 декабря, Онлайн, Беcплатно
Когда большая система разбивается, часто происходит так, что образовываются команды на базе технологий. При такой ситуации команды размещают логику на тех слоях системы, к которым имеют доступ. Закон Конвея в действии:
«Любая организация, которая проектирует какую-то систему (в широком смысле) получит дизайн, чья структура копирует структуру команд в этой организации»
Микросервисный подход предполагает разбиение системы на сервисы по бизнес-требованиям. Сервисы включают в себя полный набор технологий: UI, storage, backend. Это приводит к созданию кросс-функциональных команд, имеющих достаточно компетенций для реализации всех необходимых сервисов, покрывающих 100% бизнес-функционала. Команды должны отвечать за все аспекты ПО, которое они разрабатывают, включая поддержку его в режиме 24/7. В таком случае возможность проснуться от звонка в 3 часа ночи — это очень сильный стимул писать хороший код.
Насколько большим должен быть микросервис
Логика работы сервиса должна полностью уместиться в голове одного разработчика, независимо от количества кода и людей. Проектируя систему, мы имеем выбор, как разработать каждый микросервис. Например:
Архитектура микросервиса даёт полную свободу в выборе технологий и инструменария.
Инструментарий для реализации микросервисов
В процессе реализации микросервисной архитектуры существенным упрощением будет использование систем CI/CD, системы оркестрации, Service Discovering, мониторинга и сбора логов.
Необходимо быть уверенным в том, что приложение работает правильно. Для этого запускаются автоматические тесты, при этом система разворачивается в отдельной среде (Automated Deployment).
Цепочка синхронных вызовов микросервисов приведет к ожиданию ответов от всех сервисов по очереди. Поэтому используйте правило «Один синхронный вызов на один запрос пользователя», как это сделали в The Guardian, либо полностью асинхронный API, как в Netflix. Один из способов сделать асинхронный API — использовать систему обработки очередей, например, RabbitMQ, Apache Kafka или ActiveMQ.
Го в Go! Как команда PHP взялась писать микросервисы
Всем привет! Меня зовут Алексей Скоробогатый, я системный архитектор в Lamoda. В феврале 2019 года я выступал на Go Meetup еще на позиции тимлида команды Core. Сегодня хочу представить расшифровку своего доклада, который вы также можете посмотреть.
Наша команда называется Core неспроста: в зону ответственности входит все, что связано с заказами в e-commerce платформе. Команда образовалась из PHP-разработчиков и специалистов по нашему order processing, который на тот момент представлял собой единый монолит. Мы занимались и продолжаем заниматься декомпозицией его на микросервисы.
Оформление заказа в нашей системе состоит из связанных компонентов: есть блок доставки и корзина, блоки скидок и оплаты, — и в самом конце есть кнопка, которая отправляет заказ собираться на склад. Именно в этот момент начинается работа системы order processing, где все данные заказа будут провалидированы, а информация агрегирована.
Внутри всего этого — сложная многокритериальная логика. Блоки взаимодействуют между собой и влияют друг на друга. Непрерывные и постоянные изменения от бизнеса еще увеличивают сложность критериев. Кроме того, у нас есть разные платформы, через которые клиенты могут создавать заказы: сайт, приложения, колл-центр, В2В-платформа. А также жесткие критерии SLA/MTTI/MTTR (метрики регистрации и решения инцидента). Все это требует от сервиса высокой гибкости и устойчивости.
Архитектурное наследие
Как я уже говорил, на момент образования нашей команды система order processing представляла собой монолит – почти 100 тысяч строк кода, в которых описывалась непосредственно бизнес-логика. Основная часть была написана в 2011 году, с использованием классической многослойной MVC-архитектуры. В основе был РНР (фреймворк ZF1), который постепенно оброс адаптерами и symfony-компонентами для взаимодействия с различными сервисами. За время существования у системы было более 50 контрибьюторов, и хотя нам удалось сохранить единый стиль написания кода, это тоже наложило свои ограничения. Плюс ко всему возникло большое количество смешанных контекстов — по разным причинам в систему были имплементированы некоторые механизмы, не связанные непосредственно с обработкой заказов. Все это привело к тому, что на настоящий момент мы имеем MySQL базу данных размером более 1 терабайта.
Схематично изначальную архитектуру можно представить так:
Заказ, конечно, находился на каждом из слоев — но помимо заказа были и другие контексты. Мы начали с того, что определили bounded context именно заказа и назвали его Customer Order, так как помимо самого заказа, там есть те самые блоки, которые я упомянул в начале: доставка, оплата и прочее. Внутри монолита всем этим было сложно управлять: любые изменения влекли к увеличению зависимостей, код доставлялся на прод очень долго, всё время увеличивалась вероятность ошибок и отказа системы. А мы ведь говорим про создание заказа, основную метрику интернет-магазина — если заказы не создаются, то остальное уже не так важно. Отказ системы вызывает немедленное падение продаж.
Поэтому мы решили вынести контекст Customer Order из системы Order Processing в отдельный микросервис, который назвали Order Management.
Требования и инструментарий
После определения контекста, который решили вынести из монолита в первую очередь, мы сформировали требования к нашему будущему сервису:
Мы хотели, чтобы код был максимально понятным и легко редактируемым, чтобы следующие поколения разработчиков могли быстро внести требующиеся для бизнеса изменения.
В итоге мы пришли к определенной структуре, которую используем во всех новых микросервисах:
Bounded Context. Каждый новый микросервис, начиная с Order Management, мы создаем на основе бизнес-требований. Должны существовать конкретные объяснения, какую часть системы и почему требуется вынести в отдельный микросервис.
Существующая инфраструктура и инструментарий. Мы не первая команда в Lamoda, которая начала внедрять Go, до нас были первопроходцы — непосредственно Go-шная команда, которая подготовила инфраструктуру и инструментарий:
Примерно так выглядит наш новый микросервис Order Management:
На входе у нас есть данные, мы их агрегируем, валидируем, взаимодействуем со сторонними сервисами, принимаем решения и передаем результаты дальше в Order Processing — тот самый монолит, который большой, неустойчивый и требовательный к ресурсам. Это тоже нужно учитывать при построении микросервиса.
Сдвиг парадигмы
Выбрав Go, мы сразу получили несколько преимуществ:
Язык Go ограничивает воображение разработчика. Это стало камнем преткновения для нашей команды, привыкшей к РНР, когда мы перешли к разработке на Go. Мы столкнулись с настоящим парадигменным сдвигом. Нам пришлось пройти через несколько стадий и понять некоторые вещи:
В итоге мы пришли к пониманию, что Go – это процедурный язык программирования.
Data first
Я думал, как визуализировать проблему, с которой мы столкнулись, и наткнулся на эту картинку:
Здесь изображен “объектно-ориентированный” взгляд на мир, где мы строим абстракции и закрываем за ними объекты. Например, тут не просто дверь, а Indoor Session Initialiser. Не зрачок, а Visitor Monitor Interface — и так далее.
Мы отказались от такого подхода, и на первое место поставили сущности, не став их скрывать за абстракциями.
Рассуждая таким образом, мы поставили на первое место данные, и получили такой Pipelining в сервисе:
Изначально мы определяем модель данных, которые поступают в конвейер обработчиков. Данные являются изменяемыми, причем изменения могут происходить как последовательно, так и конкурентно (concurrency). С помощью этого мы выигрываем в скорости.
Назад в будущее
Неожиданно, разрабатывая микросервисы, мы пришли к модели программирования 70-х годов. После 70-х возникли большие enterprise-монолиты, где появилось объектно-ориентированное программирование, функциональное программирование – большие абстракции, которые позволяли удерживать код в этих монолитах. В микросервисах нам все это не нужно, и мы можем использовать отличную модель CSP (communicating sequential processes), идею которой выдвинул как раз в 70-х Чарльз Хор.
Также мы используем Sequence/Selection/Interation — парадигму структурного программирования, согласно которой весь код программы можно составить из соответствующих управляющих конструкций.
Ну и процедурное программирование, которое в 70-х годах было мейнстримом 🙂
Структура проекта
Как я уже говорил, на первое место мы поставили данные. Кроме того, построение проекта “от инфраструктуры” мы заменили на бизнес-ориентированное. Чтобы разработчик, заходя в код проекта, сразу видел, чем занимается сервис — это и есть та самая прозрачность, которую мы определили как одно из основных требований к структуре наших микросервисов.
В результате мы имеем плоскую архитектуру: небольшой слой API плюс модели данных. А вся логика (которая ограничена у нас контекстом бизнес требования от микросервиса), хранится в процессорах (обработчиках).
Мы стараемся не создавать новые отдельные микросервисы без однозначного запроса от бизнеса — так мы контролируем гранулярность всей системы. Если есть логика, которая близко связана с существующим микросервисом, но по сути относится к другому контексту — мы вначале заключаем ее в так называемых сервисах. И только при возникновении постоянной бизнес-потребности мы выносим ее в отдельный микросервис, к которому далее обращаемся при помощи rpc-вызова.
Чтобы контролировать гранулярность и не плодить микросервисы необдуманно, логику, которая не относится непосредственно к этому контексту, но близко связана с данным микросервисом, мы заключаем в слое services. А потом, если есть бизнес-потребность, мы выносим ее в отдельный микросервис — и далее с помощью rpc-вызова обращаемся к нему.
Таким образом, для внутреннего API в процессорах у сервиса взаимодействие никак не меняется.
Устойчивость
Мы решили не брать заранее какие-то сторонние библиотеки, так как данные, с которыми мы работаем, достаточно чувствительные. Поэтому мы немного повелосипедили 🙂 Например, сами реализовали некоторые классические механизмы — для Idempotency, Queue-worker, Fault Tolerance, Compensating transactions. Наш следующий шаг — постараться это переиспользовать. Завернуть в библиотеки, может быть side-car контейнеры в Pod’ах Kubernetes. Но уже сейчас мы можем эти паттерны применять.
Мы реализуем в своих системах паттерн, который называется graceful degradation: сервис должен продолжать работать, независимо от внешних вызовов, в которых мы агрегируем информацию. На примере создания заказа: если запрос попал в сервис, мы в любом случае заказ создадим. Даже если упадет соседний сервис, отвечающий за какую-то часть той информации, которую мы должны сагрегировать или провалидировать. Более того – мы не потеряем заказ, даже если мы не сможем в краткосрочном отказе процессинга заказа, куда мы должны передать. Это тоже один из критериев, по которым мы принимаем решение, выносить ли логику в отдельный сервис. Если сервис не может обеспечить свою работу при недоступности следующих сервисов в сети, то либо нужно его перепроектировать, либо подумать о том, стоит ли его вообще выносить из монолита.
Го в Go!
Когда приходишь писать бизнес-ориентированные продуктовые микросервисы из классической сервис-ориентированной архитектуры, в частности РНР, то сталкиваешься с некоторым парадигменным сдвигом. И его обязательно нужно пройти, иначе можно наступать на грабли бесконечно. Бизнес-ориентированная структура проекта позволяет нам не усложнять лишний раз код и контролировать гранулярность сервиса.
Одной из основных наших задач было повышение устойчивости сервиса. Конечно, Go не дает повышения устойчивости просто “из коробки”. Но, по моему ощущению, в экосистеме Go оказалось проще создать весь необходимый Reliability kit даже своими руками, не прибегая к сторонним библиотекам.
Другой важной задачей было увеличить гибкость системы. И тут я однозначно могу сказать, что скорость внесения требуемых бизнесом изменений сильно выросла. Благодаря архитектуре новых микросервисов разработчик остается один на один с бизнес-фичей, ему не нужно думать о том, чтобы строить клиенты, слать мониторинги, пробрасывать трейсинги, и настраивать логирование. Мы оставляем для разработчика именно прослойку написания бизнес-логики, позволяя ему не задумываться обо всей инфраструктурной обвязке.
Собираемся ли мы полностью переписать все на Go и отказаться от РНР?
Нет, так как мы идем от бизнес-потребностей, и есть некоторые контексты, в которых РНР очень хорошо ложится — там не нужна такая скорость и весь Go-шный набор инструментов. Вся автоматизация операций по доставке заказов и управлению фотостудией сделана на PHP. Но, например, в e-commerce платформе в customer side мы почти все переписываем на Go, так как там это оправданно.