Статьи

Микросервисные архитектуры с Spring Cloud и Docker

Эта статья представляет собой отправную точку для понимания общих шаблонов архитектуры Microservice на примере проверенного приложения, созданного с использованием Spring Boot, Spring Cloud и Docker.

Код доступен на Github , а изображения доступны на Docker Hub. Вы можете запустить всю систему с помощью одной команды.

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

Функциональные услуги

Применение монолита было разложено на три основных микросервиса. Все они являются независимо развертываемыми приложениями, организованными вокруг определенных бизнес-возможностей.

Обслуживание учетной записи

Содержит общую логику пользовательского ввода и проверки: статьи доходов / расходов, сбережения и настройки учетной записи.

метод Дорожка Описание Пользователь аутентифицирован Доступно из пользовательского интерфейса
ПОЛУЧИТЬ / счета / {счета} Получить указанные данные аккаунта
ПОЛУЧИТЬ / Счета / ток Получить данные текущего аккаунта × ×
ПОЛУЧИТЬ / Счета / демо Получить данные демо-счета (предварительно заполненные статьи доходов / расходов и т. Д.) ×
ПОЛОЖИТЬ / Счета / ток Сохранить данные текущего аккаунта × ×
ПОСЛЕ /Счета/ Зарегистрировать новый аккаунт ×

Служба статистики

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

метод Дорожка Описание Пользователь аутентифицирован Доступно из пользовательского интерфейса
ПОЛУЧИТЬ / Статистика / {счет} Получить указанную статистику аккаунта
ПОЛУЧИТЬ / Статистика / ток Получить текущую статистику аккаунта × ×
ПОЛУЧИТЬ / Статистика / демо Получить статистику демо-счета ×
ПОЛОЖИТЬ / Статистика / {счет} Создание или обновление точки данных временного ряда для указанной учетной записи

Служба уведомлений

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

метод Дорожка Описание Пользователь аутентифицирован Доступно из пользовательского интерфейса
ПОЛУЧИТЬ / Уведомление / Настройки / ток Получить текущие настройки уведомлений учетной записи × ×
ПОЛОЖИТЬ / Уведомление / Настройки / ток Сохранить настройки уведомлений текущей учетной записи × ×

Заметки

  • У каждого микросервиса есть своя собственная база данных, поэтому невозможно обойти API и получить прямой доступ к данным персистентности.
  • Для этого проекта я использовал MongoDB в качестве основной базы данных для каждого сервиса. Также может иметь смысл иметь постоянную архитектуру полиглота (чтобы выбрать тип базы данных, который лучше всего подходит для требований сервиса).
  • Связь между сервисами довольно упрощена: микросервисы общаются, используя только синхронный REST API. Обычной практикой в ​​реальных системах является использование комбинации стилей взаимодействия. Например, выполнить синхронный запрос GET для извлечения данных и использовать асинхронный подход через посредник сообщений для операций создания / обновления, чтобы разделить службы и буферизировать сообщения. Тем не менее, это приводит нас в  возможной последовательности  мира.

Инфраструктурные услуги

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

Конфиг Сервис

Spring Cloud Config  — это горизонтально масштабируемая служба централизованной конфигурации для распределенных систем. Он использует сменный слой хранилища, который в настоящее время поддерживает локальное хранилище, Git и Subversion. 

В этом проекте я использую  native profile, который просто загружает файлы конфигурации из локального пути к классам. Вы можете увидеть  sharedкаталог в  ресурсах сервиса Config . Теперь, когда служба уведомлений запрашивает свою конфигурацию, служба Config отвечает с помощью  shared/notification-service.yml и  shared/application.yml (которая используется всеми клиентскими приложениями).

Использование на стороне клиента

Просто создайте приложение Spring Boot с  spring-cloud-starter-config зависимостями, остальное сделает автоконфигурация.

Теперь вам не нужны встроенные свойства в вашем приложении. Просто укажите  bootstrap.yml имя приложения и URL-адрес службы конфигурации:

spring:
  application:
    name: notification-service
  cloud:
    config:
      uri: http://config:8888
      fail-fast: true

С помощью Spring Cloud Config вы можете динамически изменять конфигурацию приложения

Например,  компонент EmailService  был аннотирован  @RefreshScope. Это означает, что вы можете изменить текст сообщения электронной почты и строки темы, не перестраивая и не перезапуская приложение службы уведомлений.

Сначала измените необходимые свойства на сервере конфигурации. Затем выполните запрос на обновление службы уведомлений: curl -H "Authorization: Bearer #token#" -XPOST http://127.0.0.1:8000/notifications/refresh

Вы также можете использовать  webhooks для автоматизации этого процесса .

Заметки

  • Есть некоторые ограничения для динамического обновления. @RefreshScope не работает с  @Configuration классами и не может влиять на  @Scheduled методы.
  • fail-fast свойство означает, что приложение Spring Boot сразу не сможет запуститься, если не сможет подключиться к службе конфигурации. Это очень полезно, когда вы запускаете  все приложения вместе .
  • Ниже приведены важные замечания по безопасности.

Аут Сервис

Ответственность за авторизацию полностью распределяется на отдельный сервер, который предоставляет  токены OAuth2  для служб ресурсов бэкэнда. Auth Server используется для авторизации пользователя, а также для безопасной межмашинной связи внутри периметра.

В этом проекте я использую  Password credentials в качестве типа предоставления для авторизации пользователя (поскольку он используется только в пользовательском интерфейсе приложения) и  Client Credentials в качестве типа предоставления для авторизации на микросервисах.

Spring Cloud Security предоставляет удобные аннотации и автоконфигурации, чтобы сделать это действительно простым для реализации как на стороне сервера, так и на стороне клиента. Вы можете узнать больше об этом в  документации  и проверить детали конфигурации в  коде Auth Server .

Со стороны клиента все работает точно так же, как и при традиционной авторизации на основе сеансов. Вы можете извлекать  Principal объекты из запроса, проверять роли пользователей и другие вещи с помощью контроля доступа на основе выражений и  @PreAuthorize аннотаций.

Каждый клиент в PiggyMetrics (служба учетных записей, служба статистики, служба уведомлений и браузер) имеет область действия:  serverдля внутренних служб и  ui — для браузера. Таким образом, мы также можем защитить контроллеры от внешнего доступа, например:

@PreAuthorize("#oauth2.hasScope('server')")
@RequestMapping(value = "accounts/{name}", method = RequestMethod.GET)
public List<DataPoint> getStatisticsByAccountName(@PathVariable String name) {
    return statisticsService.findByAccountName(name);
}

API-шлюз

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

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

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

Netflix с открытым исходным кодом предоставляет  такой пограничный сервис , и теперь с помощью Spring Cloud мы можем включить его с одной  @EnableZuulProxyаннотацией. В этом проекте я использую Zuul для хранения статического контента (приложение пользовательского интерфейса) и для маршрутизации запросов к соответствующим микросервисам. Вот простая конфигурация маршрутизации на основе префиксов для службы уведомлений:

zuul:
  routes:
    notification-service:
        path: /notifications/**
        serviceId: notification-service
        stripPrefix: false

Это означает, что все запросы, начиная с,  /notifications будут направлены в службу уведомлений. Как вы видите, нет жестко закодированного адреса. Zuul использует   механизм обнаружения службы для обнаружения экземпляров службы уведомлений, а также  прерыватель цепи и балансировщик нагрузки , описанные ниже.

Сервис Discovery

Другим широко известным архитектурным шаблоном является обнаружение службы. Это позволяет автоматически определять сетевые расположения для экземпляров службы, которые могут иметь динамически назначенные адреса из-за автоматического масштабирования, сбоев и обновлений.

Ключевой частью службы обнаружения является реестр. Я использовал Netflix Eureka для этого проекта. Eureka является хорошим примером шаблона обнаружения на стороне клиента, когда клиент отвечает за определение местоположения доступных экземпляров службы (с использованием сервера реестра) и запросов балансировки нагрузки между ними.

С помощью Spring Boot вы можете легко создать Eureka Registry с  spring-cloud-starter-eureka-server зависимостями,  @EnableEurekaServer аннотациями и простыми свойствами конфигурации.

Поддержка клиентов включена с  @EnableDiscoveryClient аннотацией и  bootstrap.yml именем приложения:

spring:
  application:
    name: notification-service

Теперь при запуске приложения оно регистрируется на сервере Eureka и предоставляет метаданные, такие как хост и порт, URL-адрес индикатора работоспособности, домашняя страница и т. Д. Eureka получает сообщения пульса от каждого экземпляра, принадлежащего службе. Если сердцебиение не выполняется в течение настраиваемого расписания, экземпляр будет удален из реестра.

Кроме того, Eureka предоставляет простой интерфейс, где вы можете отслеживать запущенные сервисы и количество доступных экземпляров: http://localhost:8761

Балансировщик нагрузки, автоматический выключатель и клиент Http

Netflix OSS предоставляет еще один отличный набор инструментов. 

лента

Лента — это балансировщик нагрузки на стороне клиента, который дает вам большой контроль над поведением клиентов HTTP и TCP. По сравнению с традиционным балансировщиком нагрузки нет необходимости в дополнительном прыжке для каждого беспроводного вызова — вы можете напрямую связаться с нужной службой.

Изначально он интегрируется с Spring Cloud и Service Discovery. Eureka Client  предоставляет динамический список доступных серверов, чтобы лента могла балансировать между ними.

Hystrix

Hystrix — это реализация  шаблона прерывателя цепи , который дает контроль над задержкой и отказом от зависимостей, доступных по сети. Основная идея заключается в том, чтобы остановить каскадные сбои в распределенной среде с большим количеством микросервисов. Это помогает быстро выходить из строя и восстанавливаться как можно быстрее — важные аспекты отказоустойчивых систем, которые самовосстанавливаются.

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

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

Feign

Feign — декларативный HTTP-клиент, который легко интегрируется с Ribbon и Hystrix. Фактически, с одной  spring-cloud-starter-feign зависимостью и  @EnableFeignClients аннотацией у вас есть полный набор балансировщика нагрузки, автоматического выключателя и HTTP-клиента с разумной готовой конфигурацией по умолчанию.

Вот пример из службы учетных записей:

@FeignClient(name = "statistics-service")
public interface StatisticsServiceClient {

    @RequestMapping(method = RequestMethod.PUT, value = "/statistics/{accountName}", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
    void updateStatistics(@PathVariable("accountName") String accountName, Account account);

}
  • Все, что вам нужно, это просто интерфейс
  • Вы можете разделить  @RequestMapping часть между Spring MVC контроллером и Feign методами
  • В приведенном выше примере указан только нужный идентификатор службы —  statistics-serviceблагодаря автообнаружению через Eureka (но, очевидно, вы можете получить доступ к любому ресурсу с определенным URL)

Панель мониторинга монитора

В этой конфигурации проекта каждый микросервис с Hystrix на борту передает метрики в Turbine через Spring Cloud Bus (с брокером AMQP). Проект Monitoring — это всего лишь небольшое загрузочное приложение Spring с  Turbine  и  Hystrix Dashboard .

Давайте посмотрим, как работает наша система под нагрузкой: служба учетных записей вызывает службу статистики, и она реагирует с различной задержкой имитации. Порог ожидания ответа установлен на 1 секунду.

0 ms delay 500 ms delay 800 ms delay 1100 ms delay
Хорошо ведет себя система. Пропускная способность составляет около 22 запросов в секунду. Небольшое количество активных тем в службе статистики. Среднее время обслуживания составляет около 50 мс.

Количество активных тем растет. Мы видим фиолетовое число отклонений пула потоков и, следовательно, около 30-40% ошибок, но цепь все еще замкнута.

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

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

Анализ журнала

Централизованное ведение журнала может быть очень полезным при попытке выявления проблем в распределенной среде. Стек Elasticsearch, Logstash и Kibana позволяет с легкостью искать и анализировать ваши журналы, данные об использовании и сетевой активности. Готовая конфигурация Docker описана  в моем другом проекте .

Безопасность

Расширенная конфигурация безопасности выходит за рамки этого проверенного проекта. Для более реалистичного моделирования реальной системы рассмотрите возможность использования хранилища ключей https и JCE для шифрования паролей микросервисов и содержимого свойств сервера конфигурации (  подробности см. В  документации ).

Автоматизация инфраструктуры

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

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

Вот простой рабочий процесс Continuous Delivery, реализованный в этом проекте:

В этой  конфигурации Travis CI создает помеченные изображения для каждого успешного нажатия Git. Таким latest образом, в Docker Hub всегда есть  образ для каждого микросервиса,   а старые изображения помечаются хэшем Git commit. Легко развернуть любой из них и быстро откатиться, если это необходимо.

Как запустить все вещи?

Это действительно легко, и я предлагаю вам попробовать. Помните, что вы собираетесь запустить 8 приложений Spring Boot, 4 экземпляра MongoDB и RabbitMq. Убедитесь, что 4 Gb на вашем компьютере есть  оперативная память. Вы всегда можете запустить только жизненно важные службы, хотя Gateway, Registry, Config, Auth Service и Account Service.

Прежде чем ты начнешь

  • Установите Docker и Docker Compose.
  • Переменные среды Экспорт:  CONFIG_SERVICE_PASSWORDNOTIFICATION_SERVICE_PASSWORDSTATISTICS_SERVICE_PASSWORDACCOUNT_SERVICE_PASSWORDMONGODB_PASSWORD

Режим производства

В этом режиме все последние изображения будут извлечены из Docker Hub. Просто скопируйте  docker-compose.yml и нажмите  docker-compose up -d.

Режим разработки

Если вы хотите создавать изображения самостоятельно (например, с некоторыми изменениями в коде), вам необходимо клонировать все хранилище и создавать артефакты с помощью Maven. Затем беги docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d

docker-compose.dev.yml наследует  docker-compose.yml с дополнительной возможностью создавать образы локально и выставлять все порты контейнеров для удобной разработки.

Важные конечные точки

  • localhost: 80 — шлюз
  • localhost: 8761 — Eureka Dashboard
  • localhost: 9000 — Панель управления Hystrix
  • localhost: 8989 — Поток турбины (источник для Hystrix Dashboard)
  • localhost: 15672 — управление RabbitMq

Заметки

 Для запуска всех приложений Spring Boot требуется уже запущенный  Config Server . Но мы можем запустить все контейнеры одновременно из-за  fail-fast свойства Spring Boot и  restart: always опции docker-compose. Это означает, что все зависимые контейнеры будут пытаться перезапускаться до тех пор, пока Config Server не будет запущен и запущен.

Кроме того, для механизма обнаружения служб требуется некоторое время после запуска всех приложений. Любая служба недоступна для обнаружения клиентами до тех пор, пока экземпляр, сервер Eureka и клиент не будут иметь одинаковые метаданные в своем локальном кэше, поэтому это может занять 3 такта. Период прослушивания по умолчанию составляет 30 секунд.