что такое микросервис java
Микросервисная архитектура на современном стеке Java-технологий
Тип технологии | Название | Версия |
---|---|---|
Платформа | JDK | 11.0.1 |
Язык программирования | Kotlin | 1.3.10 |
Фреймворк приложения | Spring Framework | 5.0.9 |
Spring Boot | 2.0.5 | |
Система сборки | Gradle | 5.0 |
Gradle Kotlin DSL | 1.0.4 | |
Фреймворк для unit-тестирования | JUnit | 5.1.1 |
Spring Cloud | ||
Единая точка доступа (API gateway) | Spring Cloud Gateway | Входит в Release train Finchley SR2 проекта Spring Cloud |
Централизованное конфигурирование (Centralized configuration) | Spring Cloud Config | |
Трассировка запросов (Distributed tracing) | Spring Cloud Sleuth | |
Декларативный HTTP клиент (Declarative HTTP client) | Spring Cloud OpenFeign | |
Обнаружение сервисов (Service discovery) | Spring Cloud Netflix Eureka | |
Предохранитель (Circuit breaker) | Spring Cloud Netflix Hystrix | |
Клиентская балансировка нагрузки (Client-side load balancing) | Spring Cloud Netflix Ribbon |
Проект состоит из 5-и микросервисов: 3-х инфраструктурных (Config server, Service discovery server, UI gateway) и примеров front-end’а (Items UI) и back-end’а (Items service):
Все они будут последовательно рассмотрены далее. В «боевом» проекте, очевидно, будет значительно больше микросервисов, реализующих какую-либо бизнес-функциональность. Их добавление в подобную архитектуру технически выполняется аналогично Items UI и Items service.
Disclaimer
В статье не рассматриваются инструменты для контейнеризации и оркестрации, т. к. в настоящее время они не используются в проекте.
Config server
Для создания централизованного хранилища конфигураций приложений был использован Spring Cloud Config. Конфиги могут быть прочитаны из различных источников, например, отдельного git-репозитория; в этом проекте для простоты и наглядности они находятся в ресурсах приложения:
При этом конфиг самого Config server ( application.yml ) выглядит так:
Программный код этого микросервиса состоит из всего лишь одного файла, в котором находятся объявление класса приложения и main-метод, являющийся, в отличие от эквивалентного кода на Java, функцией верхнего уровня:
Классы приложения и main-методы в остальных микросервисах имеют аналогичный вид.
Service discovery server
Service discovery — это паттерн микросервисной архитектуры, позволяющий упростить взаимодействие между приложениями в условиях возможного изменения числа их инстансов и сетевого расположения. Ключевым компонентом при таком подходе является Service registry — база данных микросервисов, их инстансов и сетевых расположений (подробнее здесь).
В этом проекте Service discovery реализован на основе Netflix Eureka, представляющего собой Client-side service discovery: Eureka server выполняет функцию Service registry, а Eureka client перед выполнением запроса к какому-либо микросервису обращается к Eureka server за списком инстансов вызываемого приложения и самостоятельно осуществляет балансировку нагрузки (используя Netflix Ribbon). Netflix Eureka, как и некоторые другие компоненты стека Netflix OSS (например, Hystrix и Ribbon) интегрируется с Spring Boot приложениями с помощью Spring Cloud Netflix.
В конфиге Service discovery server, находящемся в его ресурсах ( bootstrap.yml ), указывается только название приложения и параметр, определяющий, что запуск микросервиса будет прерван, если невозможно подключиться к Config server:
Оставшаяся часть конфига приложения располагается в файле eureka-server.yml в ресурсах Config server:
Eureka server использует порт 8761, что позволяет всем Eureka client’ам не указывать его, используя значение по умолчанию. Значение параметра register-with-eureka (указано для наглядности, т. к. оно же используется по умолчанию) говорит о том, что само приложение, как и другие микросервисы, будет зарегистрировано в Eureka server. Параметр fetch-registry определяет, будет ли Eureka client получать данные из Service registry.
Список зарегистрированных приложений и другая информация доступны по http://localhost:8761/ :
Альтернативными вариантами для реализации Service discovery являются Consul, Zookeeper и другие.
Items service
Это приложение представляет собой пример back-end с REST API, реализованным с использованием появившегося в Spring 5 фреймворка WebFlux (документация здесь), а точнее Kotlin DSL для него:
Рассмотрим один из способов отправки дополнительных метаданных на Eureka server:
Убедимся в получении Eureka server этих данных, зайдя на http://localhost:8761/eureka/apps/items-service через Postman:
Items UI
Этот микросервис, помимо того, что демонстрирует взаимодействие с UI gateway (будет показано в следующем разделе), выполняет функцию front-end для Items service, с REST API которого может взаимодействовать несколькими способами:
И используется таким образом:
И используется таким образом:
В том, что все три способа возвращают одинаковый результат, можно убедиться, зайдя на http://localhost:8081/example :
Я предпочитаю вариант с использованием OpenFeign, т. к. он даёт возможность разработать контракт на взаимодействие с вызываемым микросервисом, обязанности по имплементации которого берёт на себя Spring. Объект, реализующий этот контракт, инжектируется и используется, как обычный бин:
Для работы Feign-клиента требуется аннотировать класс приложения @EnableFeignClients :
Для работы Hystrix fallback в Feign-клиенте в конфиг приложения нужно внести:
UI gateway
Паттерн API gateway позволяет создать единую точку входа для API, предоставляемого другими микросервисами (подробнее здесь). Приложение, реализующее этот паттерн, осуществляет маршрутизацию (роутинг) запросов к микросервисам, а также может выполнять дополнительные функции, например, аутентификацию.
В этом проекте для большей наглядности реализован UI gateway, то есть единая точка входа для различных UI; очевидно, что API gateway реализуется аналогично. Микросервис реализован на основе фреймворка Spring Cloud Gateway. Альтернативным вариантом является Netflix Zuul, входящий в Netflix OSS и интегрированный с Spring Boot с помощью Spring Cloud Netflix.
UI gateway работает на 443 порту, используя сгенерированный SSL-сертификат (находится в проекте). SSL и HTTPS сконфигурированы следующим образом:
Логины и пароли пользователей хранятся в Map-based имплементации специфичного для WebFlux интерфейса ReactiveUserDetailsService :
Параметры безопасности настроены таким образом:
Эта страница использует средства фреймворка Bootstrap, подключаемого к проекту с помощью Webjars, который даёт возможность управлять client-side библиотеками как обычными зависимостями. Для формирования HTML-страниц используется Thymeleaf. Доступ к странице логина конфигурируется с помощью WebFlux:
Маршрутизация средствами Spring Cloud Gateway может быть настроена в YAML- или java-конфиге. Роуты к микросервисам либо прописываются вручную, либо создаются автоматически на основе данных, полученных из Service registry. При достаточно большом количестве UI, к которым требуется осуществлять маршрутизацию, удобнее будет воспользоваться интеграцией с Service registry:
Значение параметра include-expression указывает, что роуты будут созданы только для микросервисов, названия которых оканчиваются на -UI, а значение параметра url-expression — что они доступны по HTTP протоколу, в отличие от самого UI gateway, работающего по HTTPS, и при обращении к ним будет использоваться клиентская балансировка нагрузки (реализуемая с помощью Netflix Ribbon).
Рассмотрим пример создания роутов в java-конфиге вручную (без интеграции с Service registry):
Первый роут осуществляет маршрутизацию на ранее показанную домашнюю страницу Eureka server ( http://localhost:8761 ), второй нужен для загрузки ресурсов этой страницы.
В нижележащих микросервисах может возникнуть необходимость получить доступ к логину и/или ролям пользователя, прошедшего аутентификацию в UI gateway. Для этого я создал фильтр, добавляющий в запрос соответствующие заголовки:
Spring Cloud Sleuth — это решение для трассировки запросов в распределённой системе. В заголовки запроса, проходящего через несколько микросервисов, добавляются Trace Id (сквозной идентификатор) и Span Id (идентификатор unit of work) (для более лёгкого восприятия я упростил схему; здесь более детальное объяснение):
Указав соответствующие настройки логирования, в консоли соответствующих микросервисов можно будет увидеть примерно следующее (после названия микросервиса выводятся Trace Id и Span Id):
Для графического представления распределённой трассировки можно воспользоваться, например, Zipkin, который будет выполнять функцию сервера, агрегирующего информацию о HTTP-запросах из других микросервисов (подробнее здесь).
Сборка
Учитывая возможность использования Gradle wrapper, нет необходимости в наличии установленного локально Gradle.
Сборка и последующий запуск успешно проходят на JDK 11.0.1. До этого проект работал на JDK 10, поэтому допускаю, что на этой версии проблем со сборкой и запуском не возникнет. По поводу более ранних версий JDK данных у меня нет. Кроме того, нужно учитывать, что используемый Gradle 5 требует как минимум JDK 8.
Запуск
Рекомендую стартовать приложения в порядке их описания в этой статье. Если вы используете Intellij IDEA с включённым Run Dashboard, то должно получиться примерно следующее:
Заключение
В статье был рассмотрен пример микросервисной архитектуры на актуальном в Java-мире стеке технологий, её основные компоненты и некоторые фичи. Надеюсь, для кого-то материал окажется полезным. Благодарю за внимание!
Просто о микросервисах
Вступление
Чуть ли не каждый второй, кто впервые сталкивается с MSA (Micro Service Architecture), на первых порах восклицает: «Да я эти микросервисы еще …надцать лет назад». Отчасти они правы. И я тоже был из этой самой половины, и не понимал — почему такой шум?
В самом деле! Ведь MSA — это тоже про разработку софта. Какие здесь могут быть революции? Все методики знакомы. В некоторых местах можно даже удивиться: «А разве бывает по-другому»? Фанаты Agile и DevOps тоже скажут, что это всё наше, родное.
Но всё же прошу вас набраться терпения и продолжить читать дальше.
Что такое микросервисная архитектура (MSA)
MSA — принципиальная организация распределенной системы на основе микросервисов и их взаимодействия друг с другом и со средой по сети, а также принципов, направляющих проектирование архитектуры, её создание и эволюцию.
Что такое микросервис (MS)
Понять суть микросервиса проще всего на сравнении, или даже противопоставлении его крупному приложению — монолиту. В отличии от MSA, я не буду давать определение микросервису, а перечислю его наиболее важные характеристики.
А дальше мы рассмотрим каждую из них подробнее.
Я выделил восемь свойств микросервиса:
Небольшой
Что такое «небольшой»? Такая малоинформативная формулировка! На самом деле, по-другому не скажешь. Каждый должен самостоятельно определиться с размером. Лучше всего на практике. В качестве индикативных оценок можно ориентироваться на рекомендации экспертов. Размер микровервиса должен быть таким, чтобы выполнялось одно из условий:
Независимый
Микросервисная архитектура — воплощение паттернов High Cohesion и Low Coupling. Всё, что противоречит этому, отвергается беспощадно. В противном случае команду ждут большие проблемы. Так что микросервис обязан быть независимым компонентом.
Здесь попрошу вас не начинать холивар о том, что же такое «компонент». Давайте в рамках этой статьи сойдемся на том, что
Компонент — это единица ПО, код которой может быть независимо заменен или обновлен.
Конечно любая мало-мальски серьезная программа пишется с разбиением на компоненты, которые, безусловно, основываются на тех же принципах. Но в монолите общая кодовая база открывает возможности для нарушения низкой связанности. И при слабой дисциплине рано или поздно код превращается в спагетти.
Под такую формулировку компонента подходят и сторонние библиотеки. Здесь сложнее с нарушением границ произвольными связями, но не на много.
В то же время методология разбиения на отдельные микросервисы вынуждает придерживаться жесткого их разделения, ведь они должны отвечать более жестким критериям независимости.
Так, каждый микросервис работает в своем процессе и поэтому должен явно обозначить свой API. Учитывая, что другие компоненты могут использовать только этот API, и к тому же он удаленный, минимизация связей становится жизненно важной.
Такое разделение дает явный выигрыш с точки зрения независимого развития разных компонентов. И с учетом этого различные языки вводят конструкции, позволяющие явное создание независимых компонентов (например, модули в Java 9), и это перестает быть прерогативой микросервисного подхода.
Не хочу, чтобы создалось впечатление, будто в микросервисной архитектуре запрещено использование библиотек. Их использование не приветствуется, поскольку так или иначе приводит к зависимостям между микросервисами, но всё же допускается. Как правило, это допущение распространяется на инфраструктурные функции вроде логирования, вызова удаленного API, обработки ошибок и тому подобного.
Независимость микросервисов позволяет организовать независимый жизненный цикл разработки, создавать отдельные сборки, тестировать и развертывать.
Поскольку размер микросервисов невелик, то очевидно, что в крупных системах их будет немало. Управлять ими вручную будет сложно. Поэтому команда обязана иметь приемлемый уровень автоматизации согласно Continuous integration и Continuous Delivery.
Где же микросервис (бизнес-потребность)
Итак, вы решили спроектировать новый микросервис.
Определение его границ — самый важный шаг. От этого будет зависеть вся дальнейшая жизнь микросервиса, и это серьёзно повлияет на жизнь команды, отвечающей за него.
Основной принцип определения зоны ответственности микросервиса — сформировать её вокруг некоторой бизнес-потребности. И чем она компактнее, чем формализованней её взаимоотношения с другими областями, тем проще создать новый микросервис. В общем, довольно стандартный посыл. На нем основывается создание любых других компонентов. Вопрос только в том, чтобы в дальнейшем выдержать эту зону ответственности, что мы и обсуждали в предыдущем параграфе.
Когда границы микросервиса заданы и он выделен в отдельную кодовую базу, защитить эти границы от постороннего влияния не составляет труда. Далее внутри микросервиса создают свой микромир, опираясь на паттерн «ограниченного контекста». В микросервисе для любого объекта, для любого действия может быть своя интерпретация, отличная от других контекстов.
Но что делать, если границы оказались неправильными? В этом случае изменение функциональности в новом микросервисе ведет к изменению функциональности в других микросервисах. В результате «поплывут» интерфейсы всех зависимых микросервисов, а за ними интеграционные тесты. И всё превращается в снежный ком. А если эти микросервисы ещё и принадлежат разным командам, то начинаются межкомандные встречи, согласования и тому подобное. Так что правильные границы микросервиса — это основа здоровой микросервисной архитектуры.
Чтобы минимизировать ошибки при определении границ, нужно вначале их продумать. Поэтому оправданным является подход Monolith First, когда вначале систему развивают в традиционной парадигме, а когда появляются устоявшиеся области, их выделяют в микросервисы. Но всё течет и меняется. И границы тоже могут меняться. Главное, чтобы выигрыш от разбиения превышал сложности пересмотра этих границ. Такой подход к постепенному формированию набора микросервисов похож на итерационное развитие, используемое в Agile, ещё его называют «эволюционным проектированием» (Evolutionary Design).
Есть ещё одно интересное следствие создания микросервисов, соответствующее закону Конвея (Conwey Law).
Если организация использует монолитное приложение, то оно нарушает соответствие структуре и коммуникациям внутри организации. А команды разработчиков строятся вокруг архитектурных слоев монолита: UI, серверная логика, база данных.
Микросервисная архитектура приводит IT и бизнес в гармонию, с точки зрения Конвея. Поскольку микросервисы формируются вокруг бизнес-потребностей конкретных бизнес-подразделений, то архитектура предприятия начинает повторять оргструктуру и каналы социальной и бизнес-коммуникации. А команды становятся кроссфункциональными и формируются вокруг этих бизнес-потребностей / бизнес-подразделений.
Поскольку разные микросервисы получаются независимыми не только логически, но и технологически, а создавать их могут разные команды, ничто не мешает для каждого случая подбирать подходящие языки программирования, фреймворки и даже операционные системы.
Интеграция. Smart endpoints and dumb pipes
Интеграция микросервисов обходится без ESB, как центрального промежуточного звена. Наверное, комьюнити уже натерпелось от неудачных вариантов реализации этого подхода. То, что были и удачные — не принимается в расчет. Впрочем, ESB ещё и противоречит таким критериям как децентрализация и независимость. Таким образом, сложность интеграции распределяется с центрального звена в виде ESB непосредственно на интегрируемые компоненты: «умные конечные точки».
Здесь есть дилемма. Конечно бинарные протоколы гораздо эффективнее. Но, во-первых, появляются технологические ограничения. Во-вторых, на бинарных протоколах сложнее реализовывать шаблон Tolerant Reader, сохраняя эффективность. В-третьих, опять появляется зависимость провайдера и потребителей, поскольку они оперируют одними и теми же объектами и методами, то есть связаны по кодовой базе.
Другая отличительная черта взаимодействия микросервисов — синхронные вызовы не приветствуются. Рекомендуется использовать один синхронный вызов на один запрос пользователя, или вообще отказаться от синхронных вызовов.
И еще пара замечаний.
Design for failure для распределенной системы
Одно из наиболее критичных мест в микросервисной архитектуре — необходимость разрабатывать код для распределенной системы, составные элементы которой взаимодействуют через сеть.
А сеть ненадежна по своей природе. Сеть может просто отказать, может работать плохо, может вдруг перестать пропускать какой-то тип сообщений, потому что изменились настройки файрвола. Десятки причин и видов недоступности.
Поэтому микросервисы могут вдруг перестать отвечать, могут начать отвечать медленнее, чем обычно. И каждый удаленный вызов должен это учитывать. Должен правильно обрабатывать разные варианты отказа, уметь ждать, уметь возвращаться к нормальной работе при восстановлении контрагента.
Дополнительный уровень сложности привносит событийная архитектура. А отладку такой системы — не одного микросервиса, а системы, где много потоков разнонаправленных неупорядоченных событий — даже трудно представить. И даже если каждый из микросервисов будет безупречен с точки зрения бизнес-логики, этого мало. По аналогии со спортом, «звёзды» не гарантируют звездную команду, ведь в команде важнее не «звезды», а слаженность всех её игроков.
И поскольку сложность таких систем очень высока, то проблему решают так.
Децентрализация данных
Каждому микросервису по своей базе данных!
Лозунг популиста на выборах.
На самом деле и в монолите можно побороться за изолированность компонентов, например, на уровне серверного кода. Если время от времени изоляция даёт течь, современные инструменты предлагают продвинутые инструменты рефакторинга. Пользуйтесь. Хотя, как правило, на это находится время, только когда дела уже совсем плохи.
Теперь опустимся ниже, на уровень базы данных. Почему-то здесь на изолированность обращают внимание гораздо реже. В результате через пару тройку лет активного развития в базе данных монолита образуется если не хаос, то энтропия продвинутого уровня. Чтобы её побороть, мало уже одной строчки в бэклоге. Необходимы месяцы кропотливого и долгого труда.
В микросервисной архитектуре это решается гильотиной. Общей базы данных просто нет.
Помимо изолированности есть и побочные плюсы. Например, легче реализовать Polyglot Persistence, когда база подбирается под конкретные цели. Ничто не мешает делать это и без микросервисов, и так часто делают. Но всё же в одном случае это закон, в другом — исключение.
У этой медали есть оборотная сторона. Много баз, много контекстов, как их все согласовать? Старая техника распределенных транзакций сложна и обладает низкой скоростью. Возможно это иногда можно пережить. А вот необходимость синхронного взаимодействия нескольких микросервисов не может устраивать, и это не побороть.
Проблема решается нетрадиционно для монолита: отказом от постоянной согласованности данных. Добро пожаловать в мир Eventual consistency. На первых порах это вызывает волну «справедливого» гнева. Но если разобраться, то нужна ли повсеместно немедленная согласованность данных по окончании транзакции? При детальном рассмотрении значительную часть случаев можно отброс ить. Где возможно, заменяют одну распределённую транзакцию серией локальных с компенсационными механизмами. Где-то мирятся с временной несогласованностью. А возможные ошибки либо обрабатывают за счет более сложной архитектуры, либо благодаря данным мониторинга. Если ничего не получается, то в особо экстремальных случаях всё же используют распределенные транзакции. Но это, с моей точки зрения, нарушение принципов MSA.
Монолит против микросервисов
Микросервисный подход несет довольно много проблем. Их найти не трудно и каждый может поупражняться.
Например, организационные вопросы. Как удержать в согласованном по версиям состоянии сотню микросервисов, которые еще и постоянно и непредсказуемо редеплоятся. А доступ к средам у каждого инженера каждой команды? Какая команда напишет интеграционные тесты? И если кто-то согласится, то попробуй еще их напиши для такой запутанной конфигурации. А если возникает ошибка, то чья она? Только той команды, у которой сломалось? Как не узнать вечером в пятницу, что версия API N-го сервиса, которой вы пользуетесь, вдруг стала deprecated?
Да, это действительно проблемы. Но команды, которые практикуют Agile и DevOps, уже знают решение. Поэтому начинать путь к микросервисной архитектуре стоит с внедрения этих практик.
Кроме организационных есть и чисто архитектурные. Как перейти от монолита, где всё синхронно, согласованно и едино, к распределенной событийной архитектуры, основанной на множестве мелких элементов, в которой надо учитывать возможную неконсистентность данных? Одного этого достаточно, чтобы задуматься: а стоит ли игра свеч? На этом фоне, например, падение скорости обработки одного запроса кажется мелочью. Хотя бы работает!
Тогда зачем? Если у вас нет проблем с вашим «монолитом», то не надо их искать.
Но если проблемы есть, то посмотрите на плюсы MSA, и возможно она спасет вас.
Разбиение на независимые компоненты даёт безусловные и неоспоримые преимущества: легкое понимание контекста, гибкость развития, управления и масштабирования. Независимость и небольшой размер дают и неожиданные плюсы с точки зрения инфраструктуры. Вам теперь не нужна монструозная машина за 100500 долларов. Микросервисы можно устанавливать на обычные дешевые машинки. И окажется, что даже все вместе они будут стоить на порядок меньше, но работать эффективнее той самой супермашины, на которую у вас в организации, наверняка, молятся и сдувают с неё пылинки.
Здесь уместен другой лозунг от популиста. Хотя, как и предыдущий, он вполне серьезен.
Каждому микросервису по своему серверу!
Продолжим агитировать за микросервисы. Посмотрим на лидер ов IT-индустрии: Amazon, Netflix, Google и другие показывают впечатляющие результаты. Их гибкость и скорость вывода новых продуктов поражают. Поэтому игра точно стоит свеч! Здесь уместно вспомнить, что в упомянутых организациях команд «уровня бог» не одна и не две. Им сложности микросервисной архитектуры вполне по зубам. И если предложить создать монолит, то они и его сделают так, что он будет сверкать путеводной звездой.
А, например, Amazon вполне себе работал на монолите, уже будучи гигантом и имея миллиардные обороты. Сайт газеты Guardian до сих пор, а возможно и навсегда, базируется на микросервисах вокруг монолита. Это говорит о том, что значительная часть задач успешно, а зачастую и легче, решается без привлечения микросервисов.
И всё же это не значит, что микросервисы не для вас. Не боги горшки обжигают. Но бросаться с головой в омут тоже не стоит. Для микросервисной архитектуры команда должна быть достаточно зрелой. Один из главных критериев: использует ли она Agile и DevOps? Команда должна быть грамотной. Это сложно формализовать, но всё же попробуйте трезво оценить возможности. Например, насколько команда продвинута в Reactive и Event-Driven Architecture? К тому же команда должна иметь подготовленную инфраструктуру для поддержки микросервисной системы.
Впрочем, достаточно. Просто попробуйте. Надеюсь, получится и понравится.