Обнаружение микросервисов Spring Boot и Flask с Netflix Eureka

Введение В этом руководстве мы будем использовать Netflix Eureka [https://github.com/Netflix/eureka], службу обнаружения микросервисов, которая объединяет микросервис Spring Boot с микросервисом Flask, объединяя службы, написанные на совершенно разных языках программирования и каркасы. Мы будем создавать две службы - службу конечного пользователя, которая представляет собой службу загрузки Spring, ориентированную на конечного пользователя, которая собирает данные и отправляет их в службу агрегации данных - службу Python, использующую Pandas для каждого конкретного пользователя.

Вступление

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

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

Netflix Eureka Serice Discovery

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

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

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

Для этого типичная архитектура состоит из нескольких элементов:

микросервисная архитектураeureka{.ezlazyload}

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

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

У нас будет два клиента:

  • Служба конечного пользователя (клиент Eureka на базе Java)
  • Служба агрегирования данных (клиент Eureka на базе Python)

Поскольку Eureka - это проект на основе Java, изначально предназначенный для решений Spring Boot - у него нет официальной реализации для Python. Однако для этого мы можем использовать управляемую сообществом оболочку Python:

Имея это в виду, давайте сначала создадим Eureka Server.

Создание сервера Eureka

Мы будем использовать Spring Boot для создания и поддержки нашего Eureka Server. Давайте начнем с создания каталога для размещения наших трех проектов и внутри него каталога для нашего сервера:

 $ mkdir eureka-microservices 
 $ cd eureka-microservices 
 $ mkdir eureka-server 
 $ cd eureka-server 

eureka-server будет корневым каталогом нашего Eureka Server. Вы можете запустить проект Spring Boot здесь через интерфейс командной строки:

 $ spring init -d=spring-cloud-starter-eureka-server 

В качестве альтернативы вы можете использовать Spring Initializr и включить зависимость Eureka Server:

Spring Initializr EurekaServer{.ezlazyload}

Если у вас уже есть проект и вы просто хотите включить новую функцию, если вы используете Maven, добавьте:

 <dependency> 
 <groupId>org.springframework.cloud</groupId> 
 <artifactId>spring-cloud-starter-eureka-server</artifactId> 
 <version>${version}</version> 
 </dependency> 

Или, если вы используете Gradle:

 implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-eureka-server', version: ${version} 

Независимо от типа инициализации - Eureka Server требует, чтобы одна аннотация была помечена как сервер.

В вашем EndUserApplication файла EndUserApplication, который является нашей точкой входа с @SpringBootApplication , мы просто добавим @EnableEurekaServer :

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

Порт по умолчанию для серверов Eureka - 8761 , и его также рекомендует команда Spring. Хотя, на всякий случай, установим это и в файле application.properties

 server.port=8761 

После этого наш сервер готов к работе. Запуск этого проекта запустит сервер Eureka Server, доступный по адресу localhost:8761 :

серверэврика{.ezlazyload}

Примечание. Без регистрации каких-либо служб Eureka может ошибочно заявить, что НЕИЗВЕСТНЫЙ экземпляр работает.

Создание клиента Eureka - служба конечного пользователя в Spring Boot

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

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

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

 $ cd.. 
 $ mkdir end-user-service 
 $ cd end-user-service 

Здесь давайте начнем новый проект через интерфейс командной строки и включим зависимость spring-cloud-starter-netflix-eureka-client Мы также добавим web зависимость, поскольку это приложение фактически будет обращено к пользователю:

 $ spring init -d=web, spring-cloud-starter-netflix-eureka-client 

В качестве альтернативы вы можете использовать Spring Initializr и включить зависимость Eureka Discovery Client:

Spring Initializr клиентEureka{.ezlazyload}

Если у вас уже есть проект и вы просто хотите включить новую функцию, если вы используете Maven, добавьте:

 <dependency> 
 <groupId>org.springframework.cloud</groupId> 
 <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> 
 <version>${version}</version> 
 </dependency> 

Или, если вы используете Gradle:

 implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-netflix-eureka-client', version: ${version} 

Независимо от типа инициализации - чтобы отметить это приложение как клиент Eureka, мы просто добавляем @EnableEurekaClient к основному классу:

 @SpringBootApplication 
 @EnableEurekaClient 
 public class EndUserServiceApplication { 
 public static void main(String[] args) { 
 SpringApplication.run(EndUserServiceApplication.class, args); 
 } 
 
 @LoadBalanced 
 @Bean 
 RestTemplate restTemplate() { 
 return new RestTemplate(); 
 } 
 } 

Примечание. В качестве альтернативы вы можете использовать @EnableDiscoveryClient , которая представляет собой более обширную аннотацию. Он может относиться к Eureka, Consul или Zookeper, в зависимости от того, какой инструмент используется.

Мы также определили здесь @Bean , чтобы @Autowire RestTemplate в нашем контроллере. Этот RestTemplate будет использоваться для отправки запроса POST в службу агрегации данных . @LoadBalanced означает, что наша RestTeamplate должна использовать RibbonLoadBalancerClient при отправке запросов.

Поскольку это приложение является клиентом Eureka, мы хотим дать ему имя для реестра. Другие службы будут ссылаться на это имя, полагаясь на него. Имя определяется в файле application.properties или application.yml

 server.port = 8060 
 spring.application.name = end-user-service 
 eureka.client.serviceUrl.defaultZone = http://localhost:8761/eureka 

 server: 
 port: 8060 
 spring: 
 application: 
 name: end-user-service 
 eureka: 
 client: 
 serviceUrl: 
 defaultZone: http://localhost:8761/eureka/ 

Здесь мы установили порт для нашего приложения, который должен знать Eureka для маршрутизации запросов к нему. Мы также указали имя сервиса, на которое будут ссылаться другие сервисы.

Запуск этого приложения зарегистрирует службу на сервере Eureka Server:

 INFO 3220 --- [ main] osbwembedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8060 (http) with context path '' 
 INFO 3220 --- [ main] .scnesEurekaAutoServiceRegistration : Updating port to 8060 
 INFO 3220 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_END-USER-SERVICE/DESKTOP-8HAKM3G:end-user-service:8060 - registration status: 204 
 INFO 3220 --- [ main] cmeEndUserServiceApplication : Started EndUserServiceApplication in 1.978 seconds (JVM running for 2.276) 
 INFO 3220 --- [tbeatExecutor-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_END-USER-SERVICE/DESKTOP-8HAKM3G:end-user-service:8060 - Re-registering apps/END-USER-SERVICE 
 INFO 3220 --- [tbeatExecutor-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_END-USER-SERVICE/DESKTOP-8HAKM3G:end-user-service:8060: registering service... 
 INFO 3220 --- [tbeatExecutor-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_END-USER-SERVICE/DESKTOP-8HAKM3G:end-user-service:8060 - registration status: 204 

Теперь, если мы зайдем на localhost:8761 , мы сможем увидеть, что он зарегистрирован на сервере:

зарегистрированный сервис eurekaserver{.ezlazyload}

Теперь давайте продолжим и определим модель Student

 public class Student { 
 private String name; 
 private double mathGrade; 
 private double englishGrade; 
 private double historyGrade; 
 private double scienceGrade; 
 
 // Constructor, getters and setters and toString() 
 } 

Для учащегося мы хотим вычислить некоторые сводные статистические данные об их успеваемости, такие как средняя, минимальная и максимальная их оценки. Поскольку мы будем использовать для этого Pandas, мы будем использовать очень удобную функцию DataFrame.describe() Давайте также GradesResult модель GradesResult, в которой будут храниться наши данные, когда они будут возвращены из службы агрегирования данных :

 public class GradesResult { 
 private Map<String, Double> mathGrade; 
 private Map<String, Double> englishGrade; 
 private Map<String, Double> historyGrade; 
 private Map<String, Double> scienceGrade; 
 
 // Constructor, getters, setters and toString() 
 } 

Завершив модели, давайте сделаем действительно простой @RestController который принимает POST , десериализует его в Student и отправляет его в службу агрегации данных , чего мы еще не сделали:

 @Autowired 
 private RestTemplate restTemplate; 
 
 @RestController 
 public class HomeController { 
 @PostMapping("/student") 
 public ResponseEntity<String> student(@RequestBody Student student) { 
 GradesResult grades = restTemplate.getForObject("http://data-aggregation-service/calculateGrades", GradesResult.class); 
 
 return ResponseEntity 
 .status(HttpStatus.OK) 
 .body(String.format("Sent the Student to the Data Aggregation Service: %s \nAnd got back:\n %s", student.toString(), gradesResult.toString())); 
 } 
 } 

Этот @RestController принимает POST и десериализует его тело в объект Student Затем мы отправляем запрос в нашу data-aggregation-service , которая еще не реализована, поскольку она будет зарегистрирована на Eureka, и упаковываем результаты JSON этого вызова в наш объект GradesResult

Примечание. Если у сериализатора есть проблемы с построением GradesResult из данного результата, вам нужно вручную преобразовать его с помощью ObjectMapper Джексона:

 String result = restTemplate.postForObject("http://data-aggregation-service/calculateGrades", student, String.class); 
 ObjectMapper objectMapper = new ObjectMapper(); 
 GradesResult gradesResult = objectMapper.readValue(result, GradesResult.class); 

Наконец, мы выводим student экземпляр мы посылали, а также grades экземпляр мы строили из результата.

Теперь давайте продолжим и создадим службу агрегирования данных .

Создание клиента Eureka - служба агрегирования данных во Flask

Единственный отсутствующий компонент - это служба агрегирования данных , которая принимает объект Student в формате JSON и заполняет DataFrame Pandas, выполняет определенные операции и возвращает результат.

Создадим каталог для нашего проекта и запустим для него виртуальную среду:

 $ cd.. 
 $ mkdir data-aggregation-service 
 $ python3 -m venv flask-microservice 

Теперь, чтобы активировать виртуальную среду, запустите файл activate В Windows:

 $ flask-microservice/Scripts/activate.bat 

В Linux / Mac:

 $ source flask-microservice/bin/activate 

Для этого мы создадим простое приложение Flask, поэтому давайте установим зависимости для Flask и Eureka через pip в нашей активированной среде:

 (flask-microservice) $ pip install flask pandas py-eureka-client 

И теперь мы можем создать наше приложение Flask:

 $ touch flask_app.py 

Теперь откройте flask_app.py и импортируйте библиотеки Flask, Pandas и Py-Eureka Client:

 from flask import Flask, request 
 import pandas as pd 
 import py_eureka_client.eureka_client as eureka_client 

Мы будем использовать Flask и request для обработки наших входящих запросов и возврата ответа, а также для раскрутки сервера. Мы будем использовать Pandas для агрегирования данных, и мы будем использовать py_eureka_client для регистрации нашего приложения Flask на сервере Eureka на localhost:8761 .

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

 rest_port = 8050 
 eureka_client.init(eureka_server="http://localhost:8761/eureka", 
 app_name="data-aggregation-service", 
 instance_port=rest_port) 
 
 app = Flask(__name__) 
 
 @app.route("/calculateGrades", methods=['POST']) 
 def hello(): 
 data = request.json 
 df = pd.DataFrame(data, index=[0]) 
 response = df.describe().to_json() 
 return response 
 
 if __name__ == "__main__": 
 app.run(host='0.0.0.0', port = rest_port) 

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

Это довольно минималистичное приложение Flask с единственным @app.route() . Мы извлекли тело POST в словарь data request.json , после чего создали DataFrame с этими данными.

Поскольку у этого словаря вообще нет индекса, мы его установили вручную.

Наконец, мы вернули результаты функции describe() в формате JSON. Мы не использовали jsonify поскольку он возвращает Response , а не String. Объект Response при отправке будет содержать лишние символы \

 {\"mathGrade\":...} 
 vs 
 {"mathGrade":...} 

Их нужно избежать, чтобы они не сбросили десериализатор.

В функции init() eureka_client мы установили URL-адрес нашего Eureka Server, а также установили имя приложения / службы для обнаружения, а также предоставили порт, на котором он будет доступен. Это та же информация, которую мы предоставили в приложении Spring Boot.

Теперь давайте запустим это приложение Flask:

 (flask-microservice) $ python flask_app.py 

И если мы проверим наш Eureka Server на localhost:8761 , он будет зарегистрирован и готов принимать запросы:

зарегистрированный сервис eurekaserver{.ezlazyload}

Вызов службы Flask из службы загрузки Spring с использованием Eureka

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

 $ curl -X POST -H "Content-type: application/json" -d "{\"name\" : \"David\", \"mathGrade\" : \"8\", \"englishGrade\" : \"10\", \"historyGrade\" : \"7\", \"scienceGrade\" : \"10\"}" "http://localhost:8060/student" 

Это приводит к ответу сервера конечному пользователю:

 Sent the Student to the Data Aggregation Service: Student{name='David', mathGrade=8.0, englishGrade=10.0, historyGrade=7.0, scienceGrade=10.0} 
 And got back: 
 GradesResult{mathGrade={count=1.0, mean=8.0, std=null, min=8.0, 25%=8.0, 50%=8.0, 75%=8.0, max=8.0}, englishGrade={count=1.0, mean=10.0, std=null, min=10.0, 25%=10.0, 50%=10.0, 75%=10.0, max=10.0}, historyGrade={count=1.0, mean=7.0, std=null, min=7.0, 25%=7.0, 50%=7.0, 75%=7.0, max=7.0}, scienceGrade={count=1.0, mean=10.0, std=null, min=10.0, 25%=10.0, 50%=10.0, 75%=10.0, max=10.0}} 

Заключение

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

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

Исходный код этих двух сервисов, включая Eureka Server, доступен на Github .

comments powered by Disqus