Перейти к содержанию

Введение в Spring Cloud Netflix — Eureka

В этой статье рассказывается об обнаружении сервисов на стороне клиента с помощью Spring Cloud Netflix Eureka.

Обнаружение сервисов на стороне клиента позволяет сервисам находить друг друга и связываться между собой без жесткой привязки к конкретному хосту и порту. Единственной «фиксированной точкой» в такой архитектуре является реестр сервисов, в котором должен регистрироваться каждый сервис.

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

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

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

Для достижения цели данной статьи реализуем три микросервиса:

  • сервис регистрации (Eureka Server);
  • REST-сервис, который регистрируется в сервисе регистрации (Eureka Client);
  • веб-приложение, которое использует REST-сервис в качестве поддержки регистрации клиента (Spring Cloud Netflix Feign Client).

Eureka Server

Внедрить Eureka Server для сервиса регистрации просто:

Давайте сделаем это шаг за шагом. Сначала создадим новый проект Maven и поместим в него зависимости. Обратите внимание, что мы импортируем spring-cloud-starter-parent во все проекты, описанные в этой статье:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-parent</artifactId>
            <version>Greenwich.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Можно проверить последние выпуски Spring Cloud в документации Spring Projects. Затем создадим основной класс приложения:

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

Наконец, настроим свойства в формате YAML, поэтому файлом конфигурации будет application.yml:

server:
  port: 8761
eureka:
  client:
    registerWithEureka: false
    fetchRegistry: false

Здесь настраиваем порт приложения; значение по умолчанию для Eureka Server – 8761. Мы говорим встроенному Eureka Client не регистрироваться у себя, потому что наше приложение должно действовать как сервер.

Теперь укажем в браузере http://localhost:8761, чтобы просмотреть панель инструментов Eureka, где позже проверим зарегистрированные экземпляры. На данный момент можно видеть основные индикаторы – status и health:

Eureka Client

Чтобы приложение @SpringBootApplication могло поддерживать обнаружение, необходимо включить Spring Discovery Client (например, spring-cloud-starter-netflix-eureka-client) в наш путь к классам.

Затем нужно аннотировать @Configuration с помощью @EnableDiscoveryClient или @EnableEurekaClient. Обратите внимание, что эта аннотация является необязательной, если есть зависимость spring-cloud-starter-netflix-eureka-client от пути к классам.

Последний указывает Spring Boot явно использовать Spring Netflix Eureka для обнаружения сервисов. Чтобы наполнить клиентское приложение некоторыми живыми примерами, включим пакет spring-boot-starter-web в pom.xml и реализуем REST-контроллер. Но сначала добавим зависимости. Можно предоставить зависимости spring-cloud-starter-parent определить версии артефакта:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-starter</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Здесь реализуем основной класс приложения:

@SpringBootApplication
@RestController
public class EurekaClientApplication implements GreetingController {

    @Autowired
    @Lazy
    private EurekaClient eurekaClient;

    @Value("${spring.application.name}")
    private String appName;

    public static void main(String[] args) {
        SpringApplication.run(EurekaClientApplication.class, args);
    }

    @Override
    public String greeting() {
        return String.format(
          "Hello from '%s'!", eurekaClient.getApplication(appName).getName());
    }
}

Добавляем интерфейс GreetingController:

public interface GreetingController {
    @RequestMapping("/greeting")
    String greeting();
}

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

Затем необходимо настроить application.yml с указанием имени приложения Spring, чтобы однозначно идентифицировать клиента в списке зарегистрированных приложений.

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

spring:
  application:
    name: spring-cloud-eureka-client
server:
  port: 0
eureka:
  client:
    serviceUrl:
      defaultZone: ${EUREKA_URI:http://localhost:8761/eureka}
  instance:
    preferIpAddress: true

Мы решили настроить Eureka Client таким образом, потому что этот вид сервиса должен быть легко масштабируем в дальнейшем. Теперь запустим клиент и снова укажем в браузере http://localhost:8761, чтобы увидеть его статус регистрации на панели Eureka Dashboard. Используя Dashboard, можно выполнить дальнейшую настройку, например, связать домашнюю страницу зарегистрированного клиента с Dashboard для административных целей. Однако параметры конфигурации выходят за рамки этой статьи:

Feign Client

Чтобы завершить проект с тремя зависимыми микросервисами, реализуем веб-приложение, потребляющее REST, с помощью Spring Netflix Feign Client.

Feign – это своего рода шаблон Spring RestTemplate с возможностью обнаружения, использующий интерфейсы для связи с конечными точками. Эти интерфейсы будут автоматически реализованы во время выполнения, и вместо URL-адресов сервисов будут использоваться их названия.

Без Feign пришлось бы автоматически подключать экземпляр EurekaClient к контроллеру, с помощью которого могли бы получать служебную информацию по имени сервиса в качестве объекта приложения. Будем использовать это приложение, чтобы получить список всех экземпляров этого сервиса, выбрать подходящий, а затем использовать InstanceInfo, чтобы получить имя хоста и порт. При этом мы могли бы сделать стандартный запрос с любым http-клиентом:

@Autowired
private EurekaClient eurekaClient;

@RequestMapping("/get-greeting-no-feign")
public String greeting(Model model) {

    InstanceInfo service = eurekaClient
      .getApplication(spring-cloud-eureka-client)
      .getInstances()
      .get(0);

    String hostName = service.getHostName();
    int port = service.getPort();

    // ...
}

RestTemplate также можно использовать для доступа к клиентским службам Eureka по имени, но эта тема выходит за рамки этой статьи. Чтобы настроить проект Feign Client, добавим в его pom.xml следующие четыре зависимости:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

Feign Client находится в пакете spring-cloud-starter-feign. Чтобы включить его, необходимо аннотировать @Configuration с помощью @EnableFeignClients. Чтобы использовать его, просто аннотируем интерфейс с помощью @FeignClient(“service-name”) и автоматически подключаем его к контроллеру.

Хорошим вариантом создания Feign Client является использование интерфейсов с аннотированными методами @RequestMapping и помещение их в отдельный модуль. Таким образом, они могут быть разделены между сервером и клиентом. На стороне сервера можно реализовать их как @Controller, а на стороне клиента их можно расширить и аннотировать как @FeignClient. Кроме того, в проект необходимо включить пакет spring-cloud-starter-eureka, аннотировав основной класс приложения с помощью @EnableEurekaClient.

Зависимости spring-boot-starter-web и spring-boot-starter-thymeleaf используются для представления, содержащего данные, извлеченные из REST-контроллера.

Создадим интерфейс Feign Client:

@FeignClient("spring-cloud-eureka-client")
public interface GreetingClient {
    @RequestMapping("/greeting")
    String greeting();
}

Далее реализуем основной класс приложения, который одновременно выступает в роли контроллера:

@SpringBootApplication
@EnableFeignClients
@Controller
public class FeignClientApplication {
    @Autowired
    private GreetingClient greetingClient;

    public static void main(String[] args) {
        SpringApplication.run(FeignClientApplication.class, args);
    }

    @RequestMapping("/get-greeting")
    public String greeting(Model model) {
        model.addAttribute("greeting", greetingClient.greeting());
        return "greeting-view";
    }
}

Создадим шаблон HTML для нашего представления:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>Greeting Page</title>
    </head>
    <body>
        <h2 th:text="${greeting}"/>
    </body>
</html>

Конфигурационный файл application.yml почти такой же, как и на предыдущем этапе:

spring:
  application:
    name: spring-cloud-eureka-feign-client
server:
  port: 8080
eureka:
  client:
    serviceUrl:
      defaultZone: ${EUREKA_URI:http://localhost:8761/eureka}

Теперь можно построить и запустить этот сервис. Наконец, укажем в браузере http://localhost:8080/get-greeting, и он должен отобразить что-то вроде следующего:

Hello from SPRING-CLOUD-EUREKA-CLIENT!

“TransportException: невозможно выполнить запрос на любом известном сервере”

При запуске Eureka Server часто сталкиваемся с таким исключением:

com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server

В основном это происходит из-за неправильной настройки в application.properties или application.yml. Eureka предоставляет два свойства для клиента, которые можно настроить:

  • registerWithEureka: если установим для этого свойства значение true, то при запуске сервера встроенный клиент попытается зарегистрироваться на сервере Eureka;
  • fetchRegistry: если настроим это свойство как true, встроенный клиент попытается получить реестр Eureka.

Теперь, когда запускаем Eureka Server, мы не будем регистрировать встроенный клиент для настройки себя на сервере.

Если отметим вышеуказанные свойства как true (или просто не настроим их, так как они истинны по умолчанию), то при запуске сервера встроенный клиент попытается зарегистрироваться на Eureka Server, а также попытается обратиться к сервису регистрации, который пока недоступен. В результате получаем TransportException.

Поэтому никогда не указывайте эти свойства как true в настройках Eureka Server. Правильные настройки, которые следует поместить в application.yml:

eureka:
  client:
    registerWithEureka: false
    fetchRegistry: false

Заключение

Из данной статьи мы узнали, как реализовать сервис регистрации с помощью Spring Netflix Eureka Server и зарегистрировать в нем несколько клиентов Eureka.

Поскольку Eureka Client на 3 этапе прослушивает случайно выбранный порт, он не знает его местоположения без информации из сервиса регистрации. С помощью Feign Client и сервиса регистрации можно найти и использовать REST-сервис, даже если местоположение меняется.

Наконец, мы увидели общую картину использования сервиса обнаружения в микросервисной архитектуре. Можно найти исходники на GitHub, где также есть набор связанных Docker-файлов, которые можно использовать с docker-compose для создания контейнеров из данного проекта.

Оригинал