Вступление
В этом руководстве мы будем использовать Netflix Eureka , службу обнаружения микросервисов, чтобы объединить микросервис Spring Boot с микросервисом Flask, объединяя службы, написанные на совершенно разных языках программирования и фреймворках.
Мы будем создавать две службы - службу конечного пользователя , которая является службой Spring Boot, ориентированной на конечного пользователя, которая собирает данные и отправляет их в службу агрегации данных - службу Python, использующую Pandas для агрегации данных. и вернуть ответ JSON в службу конечного пользователя .
Netflix Eureka Serice Discovery
При переходе от монолитной кодовой базы к архитектуре, ориентированной на микросервисы, Netflix создал множество инструментов, которые помогли им полностью пересмотреть свою архитектуру. Одним из собственных решений, которое впоследствии было выпущено для широкой публики, является Eureka .
Netflix Eureka - инструмент для обнаружения сервисов (также известный как сервер поиска или реестр сервисов ), который позволяет нам регистрировать несколько микросервисов и обрабатывать маршрутизацию запросов между ними.
Это центральный хаб, где регистрируется каждая служба, и каждая из них обменивается данными с остальными через хаб. Вместо того, чтобы отправлять вызовы REST через имена хостов и порты, мы делегируем это 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:
{.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:
{.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
, мы сможем увидеть, что он
зарегистрирован на сервере:
{.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
, он будет
зарегистрирован и готов принимать запросы:
{.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 .