Микросервисная архитектура от А до Я

Микросервисная архитектура от А до Я


Декабрь 2, 2021


  1. Что из себя представляют микросервисы?
  2. Как работает микросервисная архитектура
    1. Контексты и внедрение зависимостей
    2. Динамические прокси
    3. Веб-сервер
    4. Роль JVM
    5. Фреймворки
    6. Контейнеризация
  3. Плюсы и минусы микросервисов
    1. Преимущества
    2. Недостатки
  4. Как сделать микросервисы быстрыми и эффективными
    1. Повысьте эффективность микросервисной архитектуры с помощью нативного образа
  5. Заключение

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

Мы дадим определение микросервисной архитектуре, обсудим фреймворки, контейнеры, технологию нативного образа, а также особенности написания и поддержки микросервисов на Java™. Вы узнаете, как разбить приложение на микросервисы. Заинтригованы? Тогда приступим!

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

Что из себя представляют микросервисы?

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

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

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

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

Микросервисы ー это вариант SOA. Данный вид сервисов был разработан во избежание риска разбухания ПО. Размер микросервисов меньше, коммуникация между ними осуществляется по сети через произвольные протоколы, такие как API. Разработчики более свободны в выборе инструментального ПО без привязки к ESB (сервисным шинам предприятия), другим сервисам или межсервисным соединениям. Со временем, благодаря контейнеризации и маленьким контейнерам на Alpine Linux, микросервисы стали еще более независимыми друг от друга. Теперь бизнес-компоненты приложения можно запускать одновременно на одном оборудовании и при этом контролировать их по отдельности. Как итог, микросервисы в контейнерах открывают путь к облачно-нативным разработкам и созданию масштабируемых приложений.

Как работает микросервисная архитектура

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

Ниже описаны инструменты, позволяющие создать микросервисную архитектуру на Java™.

Контексты и внедрение зависимостей

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

Аннотации или разные методы внешней конфигурации позволяют задействовать поля простых классов. Контейнеры или фреймворки, контролирующие жизненный цикл класса/бина, могут внедрять необходимые поля после их создания. Исходный код не увидит эту сложность и не заметит замену, выполненную с целью тестирования или изменения способа коммуникации. Таким образом, команда может писать и тестировать небольшие компоненты и в результате создавать более гибкие и надежные микросервисы. Подобная архитектура основана на принципе инверсии управления (IoC).

Динамические прокси

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

В Java™ можно создать фасадный экземпляр класса, перехватывающий вызовы метода интерфейса. Данный паттерн проектирования основан на динамических прокси и реализуется с помощью java.lang.reflect.Proxy и java.lang.reflect.InvocationHandler. Это встроенный API-механизм рефлексии, который широко применяется в CDI-контейнерах, например, в Spring.

Веб-сервер

Фреймворки обеспечивают подключение настраиваемых компонентов на высоком уровне. Но при этом существуют протоколы низкоуровневой коммуникации, например, HTTP, требующие наличия промежуточного звена между сетевыми соединениями и бизнес-логикой. Также необходимо обеспечить управление некоторыми состояниями (контекстами), ресурсами и безопасностью. Этим занимаются веб-сервисы. Каждый микросервис соединяется со своим экземпляром сервера, сконфигурированным в централизованном порядке с помощью фреймворка, например, Embedded Tomcat.

Роль JVM

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

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

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

Фреймворки

Фреймворки обеспечивают реализацию многих рутинных задач в микросервисах. На рынке присутствует большое количество фреймворков: Spring, MicroProfile, Quarkus и многие другие. Некоторые имеют общие API. Также они часто поддерживают несколько API, что позволяет портировать существующий код в неизменном виде или с незначительными изменениями.

Контейнеризация

Итак, мы определили все компоненты микросервисов, выбрали целевую ОС, системные пакеты и оборудование. Теперь нужно запустить систему микросервисов целиком.

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

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

alt_text

Слои микросервиса в контейнере

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

Наконец, существуют системы оркестрации, такие как Kubernetes и Marathon, которые позволяют распределять контейнеры в кластере машин. В данном случае остатки кода на Java™ и JVM исчезают, а строительные блоки превращаются в конфигурированные контейнеры, развернутые в парке серверов. Они могут сосуществовать с компонентами SaaS, доступными в облаке.

Плюсы и минусы микросервисов

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

  1. Масштабируемость. Микросервисы идеально вам подходят, если вы планируете горизонтальное (посредством узлов, десктопов, серверов) или вертикальное (увеличение количества CPU или объема памяти / хранилища) масштабирование вашего приложения. Такая гибкость возможна благодаря независимости каждого микросервиса. Кроме того, приложение можно изменить динамически, включая или выключая компоненты для балансировки вычислительной нагрузки. Вертикальное или горизонтальное масштабирование в случае монолитов ー трудная задача. Их, конечно, можно масштабировать вертикально, скопировав приложение и запустив несколько копий. Но каждый экземпляр будет иметь доступ ко всем данным и ресурсам, что увеличит входящий / выходящий трафик и потребление памяти и снизит эффективность кэширования. Некоторые узлы используют CPU и хранилище данных в разных объемах, что замедляет работу всей системы. В таких условиях монолитные приложения утрачивают присущую им стабильность.
  2. Облачно-нативные приложения. Микросервисы хорошо сочетаются с Docker-контейнерами, где они упаковываются вместе с рантаймом, фреймворками, библиотеками и даже операционной системой. В результате каждая бизнес-функция отделяется и поддерживается и развертывается в облаке как единое целое.
  3. Простота обслуживания. Микросервисы проще тестировать и отлаживать. Предположим, компания поддерживает монолитное приложение на тысячах копий: последствия обновления одного сегмента непредсказуемы. Микросервисная архитектура более прямолинейна: вот интерфейс, вот реализация, а вот определенное количество сервисов. Замените некоторые из них и не трогайте остальные. В сочетании с непрерывным развертыванием эта модель проектирования облегчает создание и поставку надежного продукта.
  4. Высокая устойчивость. Поскольку приложение разбивают на независимые микросервисы, оно более устойчиво к ошибкам. Баг в одной части кода не приведет к падению всей системы. Вместо этого команде нужно устранить неисправности всего в одном сервисе.
  5. Общая экономия. Трехкомпонентное преимущество:
    • Благодаря тому, что приложение поделено на микросервисы, цикл релизов сокращается. Компании не нужно полностью перепроектировать приложение, чтобы обновить один сервис. Она использует механизмы непрерывного развертывания и универсальные команды разработчиков и развертывает сервисы независимо друг от друга.
    • Контейнерные образы с микросервисами используют файловые системы, такие как UnionFS, AuFS, OverlayFS. Они создают слои с описанием того, как нужно воспроизводить действия с базовым образом. В результате увеличивается только количество обновлений слоя, поэтому контейнеры остаются легкими, что экономит ресурсы и сокращает издержки компании.
    • Благодаря независимости функций на каждую из них можно назначить отдельную команду. Разработчикам даже не нужно находиться в одном месте. Использование мощных и дорогих машин тоже необязательно. Ваши инженеры смогут разворачивать микросервисы на самый простых устройствах (т.е. x86 32 bit).
  6. Широкий выбор инструментов. Микросервисная архитектура устраняет привязку к поставщику. Если компоненты не зависят друг от друга, они могут работать на разных фреймворках или использовать несколько библиотек. Разработчики также могут писать код одного приложения на разных языках.
  7. Независимость от базы данных. Микросервисы отделены от системы, а это значит, что можно забыть о связях с базой данных благодаря унифицированному интерфейсу. Можно вносить изменения в данные или полностью заменить базу данных.

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

В случае сомнений воспользуйтесь советом профессионалов. Нажмите на кнопку ниже, оставьте свои контактные данные и обсудите свою ситуацию с нашими экспертами.

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

В случае традиционных монолитов приложения находятся в одном пространстве виртуальной памяти, а запросы посылаются на физический сервер. Микросервисы взаимодействуют при помощи протоколов (HTTP/HTTPS, AMQP, REST, TCP). Это влечет за собой определенные последствия:

  • Постоянное передвижение трафика по сети;
  • Сбои запросов и другие ошибки, связанные с сетью;
  • Возможно, потребуется зашифровать и упаковать данные в другие формы.

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

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

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

Как сделать микросервисы быстрыми и эффективными

Если вы хотите применить теорию на практике и владеете английским, можете написать собственное приложение, воспользовавшись нашим примером микросервисной архитектуры из статьи в международном блоге компании (Building a Java microservices E-commerce app, статья на английском языке). А мы тем временем идем дальше.

Микросервисная система может быстро выйти из-под контроля ввиду своей сложности и превратиться в клубок, распутать который не по силам будет даже старшим разработчикам. Как решить эту проблему? Разделите микросервисы с помощью систем управления потоковыми данными (Kafka, Kinesis, Spark Streaming), упростите коммуникацию между сервисами посредством сервисной сетки (Istio, OSM на Kubernetes) или перепроектируйте систему. В идеале, ваша цель ー избежать этого:

alt_text

Пример спагетти-кода

и прийти к этому:

alt_text

Микросервисы на системе управления потоковыми данными

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

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

alt_text

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

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

alt_text

Микросервисы с сервисной сеткой

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

Но даже когда мы запустим, сконфигурируем и распределим наши микросервисы, нужно будет решить еще одну проблему. Как свести к минимуму потребление памяти и время запуска?

Во время работы над проектом можно попробовать улучшить следующие показатели:

  1. Размер Docker-образа;
  2. Объем потребляемой памяти;
  3. Время запуска.

Контейнеризированному проекту требуется доступ к среде исполнения Java™. Но вот в чем загвоздка: нельзя поместить Oracle JDK в Docker-контейнер без лицензии. Вместо этого вы можете использовать базовые образы BellSoft размером всего 107 MB. Есть еще вариант размером 41,5 MB для CLI-подобных приложений. Это самый маленький контейнер на рынке.

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

alt_text

Результаты эксперимента с проектом в разных конфигурациях

Загрузка классов оказывает значительное влияние на время запуска. Благодаря thin jar и AppCDS скорость запуска выросла почти в 2,5 раза.

В итоге мы получаем легковесный контейнерный образ, потребляющий всего 128 MB памяти и запускающийся за 8 секунд. По сравнению с изначальными результатами время запуска сократилось с 20 с до 8 с, а потребление памяти уменьшилось в 6 раз.

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

Повысьте эффективность микросервисной архитектуры с помощью нативного образа

При сборке в нативном образе приложение занимает 35 MB оперативной памяти и запускается за 0,111 секунды! При этом сам нативный образ весит 89 MB.

Как работает нативный образ? Разработчики меняют двоичный файл сервиса на нативный исполняемый файл с помощью технологии нативного образа GraalVM. Сочетание компилятора Graal в режиме AOT и виртуальной машины SubstrateVM обеспечивает мгновенный запуск, минимальное потребление памяти, экономию оперативной памяти и высокую производительность. Для использования этого инструмента нужно добавить несколько зависимостей на этапе сборки, а затем расширить сборочные скрипты с помощью команды:

gradlew nativeImage

Вы также можете воспользоваться инструментом, разработанным BellSoft, Native Image Kit (NIK). NIK преобразует Java приложение в предварительно скомпилированный исполняемый файл, который потребляет минимальное количество ресурсов и запускается почти мгновенно. NIK идеально подходит для проектирования микросервисов, так как он поддерживает разные языки программирование и широкий диапазон платформ.

Микросервисы на Java™ на основе нативного образа работают в соответствии с предположением о замкнутости мира. Базовый контейнер должен обладать минимальной функциональностью (в отсутствие JDK), то есть он может быть scratch-образом. Он будет работать в том случае, если приложение содержит все свои зависимости. Например, нативный образ можно статически слинковать со стандартной библиотекой C.

Как правило, нативному образу требуются некоторые базовые артефакты: сертификаты, библиотеки SSL, а также динамически загружаемая библиотека C. В этом случае двоичный файл можно слинковать по-другому, а базовый образ будет так называемым distroless-образом, т.е. без дистрибутива. Базовый distroless-образ без libc в gcr.io весит всего 2 MB, что немного меньше, чем размер минимального образа Alpine Linux, но при этом с glibc базовый образ будет весить уже 17 MB.

Запуск микросервисов на Java™ с применением технологии нативного образа позволяет добиться высокой производительности и скорости. Но разработчики не всегда могут гарантировать корректную работу приложения из-за определенных ограничений. Кроме того, Graal VM с оптимизациями и другие полезные инструменты не бесплатны. А развертывание контейнерного образа с тонким слоем thin jar возможно, только если у нас есть jar-файлы, то есть с обычной средой исполнения.

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

Заключение

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

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

Author image

Дмитрий Чуйко

Старший инженер по производительности BellSoft

 Twitter

 LinkedIn

BellSoft LTD [email protected] BellSoft LTD logo Liberica Committed to Freedom 199 Obvodnogo Kanala Emb. 190020 St. Petersburg RU +7 812-336-35-67 BellSoft LTD 199 Obvodnogo Kanala Emb. 190020 St. Petersburg RU +7 812-336-35-67 BellSoft LTD 111 North Market Street, Suite 300 CA 95113 San Jose US +1 702 213-59-59