Spring: Cloud

Spring: Cloud


Основные выгоды от использования микросервисов:

  • повышают изолированность;
  • повышают общую стабильность;
  • простота внедрения изменений и тестирования системы в целом;
  • улучшенное объединение для работы над 1 общим делом;
  • повышенная независимость различных частиц системы от других;
  • удобство в разработке, рефакторинге и доставке измнений;
  • четкая специализация - каждый микросервис отвечает за небольшой участок системы;
  • каждый микросервис гибкий, небольшой, динамичный и ограниченный в ответственности;
  • условная безопасность от отказа в работе всей системы;
  • необязательность использования единого стэка разработки, библиотек или языка прогрммирования;
  • простая адаптация для новых членов команды;
  • простота в увеличении и снижении потребляемых системой ресурсов.

У них так же присутствуют и минусы:

  • Опасность при нарушении в работе какого-нибудь “центрального” микросервиса в системе. Система в целом не будет считаться недоступной, но бизнес-процессы будут ограничены или заблокированы;
  • Требуется контракт между микросервисами о формате обмена данными и жесткое следование ему;
  • Возможно сложное координирование обновления всех микросервисов, если выполняется какая-либо массовая критическая миграция;
  • Сложность в обработке распределенных транзакций (distributed transactions);
  • В случае если присутствует несколько (версий) протоколов обмена данными - все их придется поддерживать, тестировать и мониторить.

Микросервисы и источники данных

Существует несколько стратегий использования источников данных (в частности, БД):

  • частные-объекты. Каждый микросервис имеет (эксклюзивный) доступ к ограниченному набору таблиц (и прочих объектов). Реализовать можно, например, посредством авторизации в источнике данных;
  • 1-схема-1-сервис. К 1 схеме в рамках 1 источника (если такое разделение, конечно, преусмотренно в источнике данных) доступ присутствует только у 1 сервиса;
  • 1-источник-1-сервис. К 1 источнику данных доступ ограничен только 1му сервису.

Spring Cloud

“Из коробки” Spring Cloud предлагает сразу несколько компонентов, которые упростят работу и оркестрирование микросервисами:

  • Spring Could Config - управление конфигурациями;
  • Eureka - компонент межсервисного взаимодействия, который обеспечивает балансировку нагрузки, отказоустойчивость, поиск других компонентов системы и т.д.;
  • Netflix’s Hysterix - компонент, который ведет учет неактивных сервисов и перераспределения трафика;
  • Zuul - распределяет трафик;
  • micro-proxy - межсетевые прокси сервисы со стороны клиентов;
  • control bus - компонент, который выполняет мониторинг и управление компонентами, исползуя технические сообщения;
  • Spring Vault - система управления безопасностью на основе токенов;
  • global locks - глобальные замки, накладываемые на ресурсы;
  • leadership election - компонент, служащий процессом-дирижёром для нескольких подчиненных как-то связанных процессов, выполняемых параллельно (или последовательно) на нескольких компонентах
  • Spring Cloud Bus - брокер сообщений;
  • load balancing на стороне клиента - распреление нагрузки по нескольким узлам.

Eureka

Данные компонент крайне важен, поскольку он позволяет объединять несколько компонентов в одну общую систему путем поиска их в рамках сети.

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

  • org.springframework.cloud:spring-cloud-starter-netflix-eureka-server
  • org.springframework.cloud:spring-cloud-starter-netflix-eureka-client

Для включения компонента поиска сервисов необходимо в класс, инициализирующий Spring добавить соответствующую аннотацию:

1
2
3
4
5
6
7
@SpringBootApplication
@EnableEurekaServer
public class MyEurekaDiscoveryServer {
public static void main(String[] args) throws Exception {
SpringApplication.run(..., args)
}
}

В конфигурации Spring так же следует добавить конфигурацию относительно расположения Eureka сервера:

1
2
3
eureka.instance.hostname=localhost
eureka.client.registerWithEureka=false
eureka.client.fetchRegistry=false

Данные конфигурации выполняются для настройки, по сути, пустого Spring приложения, который выступает исключительно в качестве сервера Eureka. После запуска приложения, Spring самостоятельно откроет порт 3000, в котором будет представлена информация по Eureka серверу по пути /eureka.

Добавление сервисов в Eureka

Для идентификации себя, сервис должен уведомить Eureka о своем существовании как компонента - тогда он станет доступеным для других компонентов. Для сбора информации от сервисов (и для распределения нагрузки между ними), по ум. используется библиотека Ribbon.

Для регистрации микросервиса как компонента в рамках Eureka, потребуется опредлить его как клиента:

1
2
3
4
5
6
7
@SpringBootApplication
@EnableEurekaClient
public class MyServiceApp {
public static void main(String[] args) throws Exception {
SpringApplication.run(..., args)
}
}

При этом, в Spring конфигурациях необходимо определить настройки клиента, а так же передать имя для приложения (де-факто, микросервиса):

1
2
3
4
5
6
spring.application.name=my-service-app
eureka.client.registerWithEureka=true
eureka.client.fetchRegistry=false
eureka.client.serviceUrl.defaultZone=http://localhost:3000/eureka/
eureka.instance.leaseRenewalIntervalInSeconds=5
eureka.instance.preferIpAddress=false

В данном случае приложение подключается уже как клиент для Eureka. Этому свидетельствует аннотация @EnableEurekaClient. Конфигурация defaultZone - определяет адрес до севера Eureka. В отличае от приложения с сервером Eureka, в клиенте указывается параметр registerWithEureka со значением true, что говорит о том что данный компонент стоит зарегистрировать. Параметр fetchRegistry со значением false говорит о том, что для данного компонента нет необходимости синхронизировать наличие других компонентов (значение true применяется если компонент будет взаимодействовать с другими компонентами как их клиент).

Параметр leaseRenewalIntervalInSeconds определяет частоту отправки уведомлений о своем состоянии на сервер Eureka. Де-факто отправляется просто уведомление что компонент активен. По умолчанию равен 30 секундам.

После запуска приложения, приложение-клиент запросит всю доступную информацию по имеющимся компонентам и сохранит их в локально в RAM.

На Eureka сервере же, можно обратить внимание как компонент попадет в список зарегистрированных. Их полный список доступен /eureka/apps. После регистрации в Eureka каждому компоненту выдается уникальный идентификатор. Идентификатор имеет следующий шаблон:

1
${ip_address}:${spring.application.name:${server.port}}

Шаблон идентификатора можно поменять:

1
eureka.instance.metadataMap.instanceId=${spring.application.name}:${spring.application.name}:${my.company.props.env}:${server.port}

Каждый идентификатор уникален и в случае если он совпадет с уже существующим, Eureka будет рассматривать данное событие как перезапуск компонента.

Коммуникации между компонентами

Для коммуникации между сервисами в стандартном пакете предусмотрен следующие шаги реализации:

  1. Регистрация бина с типом RestTemplate и аннотацией @LoadBalanced.;
  2. Реализовать @Controller, который будет принимать запрос клиента и ретранслировать его (целиком, или видоизмененно) на один из компонентов Eureka;
  3. В момент запроса данных у одного из компонентов Eureka, должен быть использован RestTemplate, которому в качестве URI следует передавать имя микросервиса и название метода, к которому следует обратиться;
  4. Реализовать LoadBalancerClient и переопределить ему метод reconstructURI таком образом чтобы он принимая URI в составе которого будет компонент Eureka, перенаправлял запрос на компонент, а не выполнял поиск узла где-то вне Eureka;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class MyRibbonBalancer implements LoadBalancerClient {
@Autowired
SpringCliehntFactory factory;

@Override
public URI reconstructURI(ServiceInstance instance, URI uri) {
RibbonLoadBalancerContext ribbonCtx = factory.getLoadBalancerContenxt(serviceId);

Pair<Server, URI> netPair = (instance instanceof RibbonServer) ? buildRibbonNetPair(uri, (RibbonServer) instance) : buildClientBasedNetPair(uri, instance);

return ribbonCtx.reconstructURIWithServer(netPair.getKey(), netPair.getValue());
}

private Pair<Server, URI> buildRibbonNetPair(URI uri, RibbonServer ribbonServer) {
return Pair.of(ribbonServer.getServer(), updateToSecureConnectionIfNeeded(uri, ribbonServer));
}

private Pair<Server, URI> buildClientBasedNetPair(URI uri, ServiceInstance instance) {
ServiceInstance server = new Server(instance.getScheme(), instance.getHost(), instance.getPort());
IClientConfig cliCfg = factory.getClientConfig(instance.getServiceId());
ServerIntrospector introspector = ...;
URI target = updateToSecureConnectionIfNeeded(uri, cliCfg, introspector, server);
return Pair.of(server, target);
}

@Override
public ServiceInstance choose(String serviceId) {
Server server = getServer(getLoadBalancer(serviceId), null);
if(server == null) return null;
ServerIntrospector introspector = ...;
return new RibbonServer(serviceId, server, isSecure(server, serviceId), introspector.getMetadata(server));
}
}
  1. При обращении клиента к сервису, необходимо ретранслировать его к компоненту Eureka используя RestTemplate:
1
MyObject[] objects = restTemplate.getForObject("http://my-eureka-service/some-service", MyObject[].class);

или

1
User[] users = restTemplate.getForObject("http://my-users-service/find-all", User[].class);

Выполнение запроса/ответа к компонентам Eureka следует воспринимать как “обычные” обращения к веб-сервисам. Вместо RestTemplate можно использовать любой другой клиент - ему необходимо будет так же изменить стандартное поведение в случае обращения к Eureka компоненту.

 Comments
Comment plugin failed to load
Loading comment plugin