[Java, Микросервисы] О клиенте и сервере в микросервисной архитектуре

Автор Сообщение
news_bot ®

Стаж: 6 лет 3 месяца
Сообщений: 27286

Создавать темы news_bot ® написал(а)
22-Июн-2021 14:31

Согласно устоявшемуся в индустрии мнению, работа старших разработчиков и архитекторов ПО во многом состоит из поиска компромиссов между преимуществами и недостатками тех или иных решений и выделения "достаточно хороших решений" для поставленных задач.Когда мы задались вопросом перехода на микросервисную архитектуру, мы столкнулись с некоторым количеством подобных трейд-оффов. Проведя ряд экспериментов и отвязавшись от специфических для нашего продукта бизнес-требований, мы попытались сформулировать вопросы, которые могут встать перед любой командой разработки, безотносительно к требованиям к продукту. Ну и, конечно, дать на них ответы - никто не любит вопросы без ответов.В качестве прикладного дополнения к рассуждениям мы разработаем несколько Proof of Concept, сопроводим их разработку краткими пояснениями и приложим исходный код PoC.Для нас "родным" стеком является Java 8 и Spring Boot 2, так что приложенные демки будут написаны на основе этих технологий с изрядными вкраплениями Spring Cloud. Мы также постараемся предложить несколько обособленных типовых решений для задач, которые, как нам показалось, могут в перспективе возникнуть перед разработчиками.
О насМы - подразделение группы компаний "Миландр", занимающееся разработкой и поддержкой IoT-платформы "ИНФОСФЕРА". Этот продукт включает в себя комплекс решений для ЖКХ, умного дома, электросетевой энергетики и промышленных предприятий. Мы собираем данные с различных приборов (счетчиков, датчиков, камер, домофонов, умных устройств), позволяем клиентам оперировать этими данными (в том числе, с использованием алгоритмов ML), настраивать пользовательские сценарии автоматизации для обработки данных, а также осуществлять удаленное управление приборами.
Интерфейс "ИНФОСФЕРА Диспетчерская"Для кого эта статья
  • Для новичков, желающих ознакомиться с основными компонентами Spring Cloud и принципами, лежащими в основе микросервисной архитектуры.
  • Для разработчиков и архитекторов, планирующих переход к микросервисной архитектуре.
  • Для разработчиков и архитекторов, желающих пополнить коллекцию типовых решений и почитать про грабли, на которые можно наступить при первом использовании Spring Cloud.
Про микросервисыМикросервисам и Spring Cloud на Хабре уже посвящено множество статей, которыми, отчасти, мы вдохновлялись и которые хотели бы дополнить в тех моментах, где чувствовали недостаточное раскрытие темы.Концепция микросервисной архитектуры достаточно молода, но мы уже успели за 5-6 лет посмотреть, как она прошла все этапы Gartner Hype Cycle.Мы видели, как в 2015-м году люди восторгались новым подходом, видели и отторжение технологии в 2016-м, и теперь, после дозревания технологии и отношения к ней, можем видеть прагматические рассуждения, с трезвыми рассуждениями и подробным описанием плюсов и минусов.Что касается необходимости использования такой архитектуры, для нас при реализации нескольких новых проектов возникла необходимость в адаптации к возрастающим нагрузкам. Возможность выборочного масштабирования отдельных компонентов системы и послужила главным аргументом для рассмотрения возможности перехода на новую архитектуру.Про Spring CloudSpring Cloud - набор инструментов, позволяющих организовать работу Spring-based приложений в соответствии с принципами микросервисной архитектуры.Статьи, описывающие использование Spring Cloud
  • Отличная статья от @sqshq с уклоном в практические аспекты создания микросервисного приложения, с подробными описаниями действий и отличными примерами.
  • Простейшее демо, показывающее, как запустить Eureka Server и подключить к нему клиенты.
Небольшое уточнение для первой работы со Spring CloudОбращаем внимание на не совсем привычный механизм указания зависимостей от Spring Cloud. После создания проекта с зависимостями из Spring Cloud через Spring Initializr в глаза сразу бросается, что starters из Spring Cloud не наследуются из родительского pom-файла, как это происходит со стартерами Spring Boot, а приносятся через dependency management:
<properties>
  <spring-cloud.version>2020.0.3</spring-cloud.version>
</properties>
...
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-dependencies</artifactId>
      <version>${spring-cloud.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>
1. Так что же за вопросы?В нашем случае на первый план вышли вопросы, касающиеся принципов организации клиентского приложения и наладки его взаимодействия с серверной стороной. Сложности добавляет тот факт, что все эти вопросы достаточно плотно переплетены между собой. Будем разбираться.ВопросВарианты решенияВыбор принципа рендеринга веб-страницClient-side rendering, Server-side rendering, MixedХранение и выдача интерфейса на клиентЕдиная точка, микрофронтенды, несколько точек-страницПротоколы взаимодействия клиента и сервера, а также сервисов друг с другомБрокер, HTTP, WS, смесьНеобходимость Service DiscoveryЗависит от использования HTTPМы намеренно оставляем за пределами подробного рассмотрения (ограничившись упоминаниями, по крайней мере, в рамках данной статьи) следующие вопросы:
  • Использование Spring Security для обеспечения безопасности использования приложения,
  • Распределенные транзакции для обеспечения согласованности баз данных разных микросервисов,
  • Балансировка нагрузки для адаптируемости отдельных компонентов к возрастанию нагрузки,
  • Боевое развертывание, пока говорим преимущественно о процессе разработки. Некоторые недостатки предлагаемых подходов могут быть нивелированы за счет грамотного проектирования и администрирования.
1.1 Рендеринг страницПервый вопрос, который хотелось бы рассмотреть, относится к выбору подхода к рендерингу страниц.Под этими словами подразумевается, где будет формироваться HTML-документ для дальнейшего его использования браузером. На текущем этапе развития технологий в основном предлагается использовать клиентский рендеринг (Client-side rendering, CSR) и серверный рендеринг (Server-side rendering, SSR).Рассмотрим коротко суть каждого из подходов.Серверный рендеринг
Данный подход предполагает формирование HTML-страницы (возможно, с каким-то изначальным набором данных, предоставляемых в рамках запроса пользователя к странице) на сервере. Дальнейшее взаимодействие пользователя с интерфейсом может осуществляться посредством исполнения загружаемого JS-кода или полным обновлением всей HTML-страницы при выполнении того или иного действия.Преимущества
  • С точки зрения пользователя это позволяет быстро получить отрисованную страницу (и даже содержательную отрисовку, если сервер перед выдачей страницы сразу подставляет в нее данные).
Недостатки
  • После отрисовки страница может еще некоторое время находиться в неоперабельном состоянии, до подготовки JS-кода к исполнению;
  • Без ухищрений в клиентском коде может потребоваться полное обновление страницы для обновления данных.
Технологии:
  • JSP;
  • Thymeleaf;
  • Mustache;
  • + ваши любимые JS-библиотеки;
Клиентский рендеринг
Данный подход, напротив, предполагает формирование страницы на клиенте посредством исполнения JS-кода. Например, так поступает React при работе через JSX, формируя содержимое страницы из кусков разметки, возвращаемой из компонентов.Преимущества
  • Почти одновременно происходит отрисовка страницы и наступает готовность к взаимодействию.
Недостатки
  • В скорости отрисовки проигрывает серверному рендерингу, поскольку JS необходимо загрузить, а затем исполнить на клиенте. Страница будет отрисована и готова к работе только по окончании работы всего необходимого для отрисовки JS-кода.
СсылкиХорошая обзорная статья (картинки взяты из нее же)1.2 Протоколы взаимодействия клиента и сервера, а также сервисов друг с другомДля микросервисной архитектуры в качестве механизма обмена данными между сервисами зачастую рекомендуют использовать событийно-ориентированную архитектуруДанный подход к разработке информационных систем предписывает обеспечивать общение сервисов друг с другом посредством обмена сообщения через специализированный middleware-софт, предназначенный для работы по принципам PUB/SUB. PUB/SUB обеспечивает слабую связанность приложений-источников сообщений и приложений-потребителей сообщений.Мы и раньше использовали механизм обмена событиями между приложениями, но в весьма ограниченном круге задач: так передавались показания от приборов и отправлялись команды на приборы.Здесь мы вплотную подошли к вопросу: как следует организовать взаимодействие сервисов друг с другом? Как определить, следует ли в конкретной ситуации использовать событийный подход или следует использовать типичные для HTTP запрос-ответ? Порассуждаем.Событийный подход хорошо показывает себя, когда:
  • стороне, публикующей событие, не требуется ответ (реакция на это событие);
  • есть несколько сервисов, заинтересованных в получении события;
  • существует потребность в сохранении события в топике (для случая использования Kafka или другого персистентного брокера сообщений) для возможности потом его прочитать из топика.
С другой стороны, HTTP лучше подходит в ситуациях:
  • когда требуется получить ответ на запрос;
  • когда требуется выполнить синхронную операцию;
  • когда не нужно привносить дополнительную сложность в систему, ограничившись синхронным обращением одного сервиса к другому.
Из этого набора фактов следует, что комбинировать события и HTTP-запросы - уместная практика. При этом нужно четко осознавать, для каких задач какой тип взаимодействия использовать.Случай “внешнего” клиентаТут хотелось бы обсудить вопрос адресации при обращении к сервисам из внешнего клиента. Для выполнения HTTP-запроса к сервису клиенту необходимо знать адрес этого сервиса. В целом, существует несколько типовых решений этой проблемы:
  • Хардкод адресов в клиентском коде (негибко и очень сложно поддерживать в боевом окружении, отметаем);
  • Параметризация клиентского кода (негибко, отметаем);
  • Использование паттерна Service Discovery для клиента (подробнее о Service Discovery можно прочесть ниже) и отправка HTTP запросов напрямую к сервисам;
  • Использование паттерна API gateway для проксирования всех запросов к backend-сервисам через единую точку входа. Подробнее об API gateway можно прочесть ниже;
  • Если же основное взаимодействие между сервисами строится на основе событий и брокера сообщений, можно попытаться подключить клиентское приложение напрямую к брокеру. Хотя современные технологии и позволяют это сделать, такая архитектура привносит серьезные проблемы с безопасностью, и потому далее не рассматривается.
Из пяти вариантов для рассмотрения остаются два:
  • Service Discovery;
  • API Gateway.
Service Discovery для клиентских приложений - жизнеспособный подход, обеспечивающий возможность менять конфигурацию адресов сервисов на лету. Проблема в том, что он предполагает прямые обращения к сервисам, что вынуждает выставлять все сервера, с которыми взаимодействует клиент, наружу из защищенных сетей. Такой подход приводит к возникновению большого количества направлений атаки, поэтому противоречит хорошим практикам безопасности.Еще одна сложность может возникнуть вследствие недопустимости прямого доступа клиента к брокеру сообщений. Если для обмена данными между сервисами будет использоваться исключительно событийная модель, сервисам все равно потребуется HTTP API, чтобы внешний клиент мог взаимодействовать с ними.С другой стороны, API Gateway выступает единой точкой входа клиентов в защищенную сеть, поэтому, будучи единственным сервисом, доступным снаружи, выступает единственным направлением атаки для злоумышленника, не проникшего в защищенную сеть. Следует отметить, что по тем же причинам API Gateway также является и единой точкой отказа, выход которой из строя сделает невозможным взаимодействие клиентов с сервером.Другая сильная сторона API Gateway заключается в возможности выступать шлюзом для внешних запросов даже в том случае, когда внутри сервисной сети используется обмен данными только через брокер. Нам не удалось найти готовых реализаций инструментов, которые могли бы производить "перекладывание" запроса к backend в брокер, но подобная логика может быть без труда реализована и посредством обычных Web-контроллеров.Существует еще один вариант архитектуры - введение дополнительного слоя сервисов 1-го уровня, способных принимать запросы от API Gateway по HTTP и перекладывать их в брокер. Такой подход позволяет и не вводить HTTP API для всех сервисов и сохранить “чистоту” API Gateway, не привнося туда логику работы с брокером.Еще одно преимущество API Gateway над подходом Service Discovery может быть получено при использовании WebSocket для полнодуплексного обмена данными между клиентом и сервером. Например, может возникнуть задача уведомлять клиентское приложение о происходящих в системе событиях. Источниками таких событий могут выступать несколько backend-сервисов. Вместо поддержания нескольких WS-соединений с такими сервисами клиент может поддерживать одно соединение с одной точкой, предоставляющей возможность пересылки таких уведомлений через WS. API Gateway - неплохой кандидат для решения этой задачи.Исходя из приведенных рассуждений, мы сформулировали для себя следующие выводы:
  • Смешение событий и HTTP-запросов - допустимая практика;
  • Запрос от клиента к API Gateway следует направлять по HTTP;
  • Во внутренней сети для поиска адресов сервисов для последующего выполнения HTTP-запросов следует использовать Service Discovery. Он же может служить заделом для масштабирования;
  • Использовать HTTP для межсервисного взаимодействия на ранней стадии переработки системы - допустимая практика, обеспечивающая простоту и быстрое построение работоспособной системы.
Spring Cloud GatewayСсылки Документация по java-конфигурации Spring Cloud Gateway: Spring Cloud Gateway принимает входящие внешние запросы, проверяет свои правила маршрутизации и, в случае нахождения подходящего для пришедшего запроса правила, перенаправляет запрос к одному из сервисов.Данный сервис предназначен для упрощения взаимодействия клиента, находящегося во внешней сети, с сервисами, расположенными во внутренней backend-сети.ИспользованиеПо сути, для начала работы со Spring Cloud Gateway нужно сделать лишь две вещи:
  • Добавить зависимость в ClassPath;
  • А Определить бин типа org.springframework.cloud.gateway.route.RouteLocator и добавить его конфигурацию;
    ИЛИ
    Б Прописать конфигурацию в файле application.properties/yaml
    Пример
На этапе разработки мы решили остановиться на java-конфигурации. Это позволило опробовать более тонкие варианты конфигурации API-gateway. Однако, вместе с тем, в промышленной эксплуатации этот вариант привносит и ограничения, не позволяя изменять конфигурацию "на лету" (например, с использованием Spring Cloud Config), вынуждая менять код приложения, исполняемого в боевом окружении, и, как следствие, требуя пересборки приложения при необходимости внести изменения в конфигурацию шлюза.Мы попытались предложить несколько типовых решений для конфигурации Spring Cloud Gateway, которые могли бы быть полезны сообществу.Пример 1: Меняем request path при помощи регулярного выражения. Запрос /google/hello приведет к обращению по адресу с параметром http://google.com/search?q=hello.
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
  return builder
    .routes()
    .route(r -> r
      .path("/google/**")
      .filters(gatewayFilterSpec -> gatewayFilterSpec.rewritePath("/google/(?<appendix>.*)", "/search?q=${appendix}"))
      .uri("http://google.com"))
  .build();
}
Пример 2: Отрезаем от path 1 блок. Запрос /yandex/hello/123 будет перенаправлен на http://yandex.ru/hello/123
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
  return builder
    .routes()
    .route(r -> r
      .path("/yandex/**")
      .filters(gatewayFilterSpec -> gatewayFilterSpec.stripPrefix(1))
      .uri("[http://yandex.ru](http://yandex.ru)"))
  .build();
}
Пример 3: Формируем URI для перенаправления, вычленяя адрес сервиса из path запроса.
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
  return builder
    .routes()
    .route(r -> r
      .path("/service/**")
      .filters(gatewayFilterSpec -> gatewayFilterSpec.changeRequestUri(serverWebExchange -> {
          ServerHttpRequest originalRequest = serverWebExchange.getRequest();
          URI oldUri = serverWebExchange.getRequest().getURI();
          UriComponentsBuilder newUri = UriComponentsBuilder.fromUri(oldUri)
          .host(originalRequest.getPath().subPath(3, 4).toString() + ".com") // 0,1,2,3 - /service/<serviceName>,
          .port(null)
          .replacePath(originalRequest.getPath().subPath(4).toString());
          return Optional.of(newUri.build().toUri());
        }))
      .uri("http://ignored-URI")) // Этот URI игнорируется
  .build();
}
Пример 4: Прописываем route с минимальным приоритетом. Запросы по адресам, не предусмотренным в других route, будут пересылаться по адресу localhost:8090.
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
  return builder
    .routes()
    .route(r -> r
    .order(Integer.MAX_VALUE)
    .path("/**")
    .uri("[http://localhost:8090](http://localhost:8090)"))
  .build();
}
1.3 Хранение интерфейса и его выдача на клиентВ рамках решения задачи передачи интерфейса для исполнения на клиент также существует несколько вариантов действия.МикрофронтендыПо сути - это продолжение идеи микросервисов и на UI. Некоторые микросервисы, помимо выполнения серверных задач, также имеют в своей зоне ответственности части пользовательского интерфейса, которые могут быть отданы на клиент для использования.
Оригинальное изображение - https://techrocks.ru/2018/09/19/prepare-your-skill-...ends/Объединение компонентов может осуществляться как на клиенте, так и на сервере. Преимущества
  • Гибко - позволяет развивать UI разных компонентов независимо друг от друга.
  • Продолжение идеи независимого развертывания - позволяет исключать микросервисы из продакшена, что будет приводить к исчезновению лишь отдельных компонентов со страницы. Например, упал сервис N, его кусочки интерфейса недоступны, а все остальное - живее живых. Круто.
  • Возможность использовать разные технологии для разных блоков интерфейса. Если не уходить в занудство на тему однообразия технологий и простоты поддержки, следует признать, что это все же выглядит скорее преимуществом.
Недостатки
  • Сложно организовать совместную работу с UI. Подход требует серьезной дисциплины, например, в части выдерживания единых стилей. Как следствие, нужны компетенции в клиентской разработке.
  • Накладные расходы на формирование интерфейса из кусочков. Как трудовые, так и вычислительные.
  • Подход молодой, и его нужно глубоко и аккуратно изучать.
Технологии Единая точка хранения интерфейсаСледующий вариант предписывает хранение всего, что нужно для работы пользовательского интерфейса, в одном сервисе. В качестве такого сервиса может существовать как обособленный сервис, так и сервис, совмещенный по функционалу с другим сервисом.Вариант 1 - Выделенный UI-сервисВ backend-сети выделяется отдельный сервис, единственная задача которого - хранить и отдавать по запросу пользовательский интерфейс.Преимущества
  • Логическая обособленность интерфейсного кода и страниц от остального кода приложения.
Недостатки
  • Необходимость создания и администрирования отдельного сервиса.
Технологии
  • Spring Web + JSP/Thymeleaf как простейший пример для минимальной реализации.
Вариант 2 - UI GatewayВыше мы уже упоминали паттерн API Gateway. Существует практика расширения этого шлюза до UI Gateway - шлюза с функционалом хранения и раздачи страниц и клиентского кода.Преимущества
  • Меньшее количество сервисов, чуть большая простота администрирования, не нужно прописывать дополнительные роутинги на UI.
Недостатки
  • Как мы помним, API Gateway - единая точка отказа системы. Даже при наличии его реплицированных экземпляров в эксплуатации обновление такого шлюза для обновления UI выглядит достаточно рискованной операцией.
ТехнологииSpring Web + JSP/Thymeleaf как простейший пример для минимальной реализации.Вариант 3 - Page UI-сервисКонцепция похожа на предыдущую с той лишь разницей, что единый UI-сервис в данном подходе разбит на несколько сервисов, каждый из которых отвечает за одну или несколько страниц.Преимущества
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_java, #_mikroservisy (Микросервисы), #_arhitektura (Архитектура), #_spring, #_spring_cloud, #_mikroservisy (Микросервисы), #_blog_kompanii_milandr (
Блог компании Миландр
)
, #_java, #_mikroservisy (
Микросервисы
)
Профиль  ЛС 
Показать сообщения:     

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

Текущее время: 17-Май 17:04
Часовой пояс: UTC + 5