Автор оригинала: David Landup.
Вступление
В этом руководстве мы будем использовать Netflix Eureka , службу обнаружения микросервисов для объединения микросервиса Spring Boot с микросервисом Flask, объединяя службы, написанные на совершенно разных языках программирования и платформах.
Мы будем создавать две службы – Службу Конечного пользователя , которая представляет собой службу загрузки Spring, ориентированную на конечного пользователя, которая собирает данные и отправляет их в службу Агрегирования данных -службу Python, использующую Pandas для выполнения агрегирования данных и возвращающую ответ JSON в службу Конечного пользователя .
Открытие сервиса Netflix Eureka
При переходе от монолитной кодовой базы к архитектуре, ориентированной на микросервисы, Netflix создала множество инструментов, которые помогли им пересмотреть всю свою архитектуру. Одним из внутренних решений, которое впоследствии было обнародовано, является Эврика .
Netflix Eureka инструмент обнаружения служб (также известный как сервер поиска или реестр служб ), который позволяет нам регистрировать несколько микросервисов и обрабатывает маршрутизацию запросов между ними.
Это центральный узел, где регистрируется каждая служба, и каждая из них взаимодействует с остальными через этот узел. Вместо отправки вызовов REST через имена хостов и порты – мы делегируем это Эврике и просто вызываем имя службы, зарегистрированной в хабе.
Для достижения этой цели типичная архитектура состоит из нескольких элементов:
Вы можете отключить сервер Eureka на любом языке, на котором есть оболочка Eureka, хотя, скорее всего, это делается на Java с помощью Spring Boot, поскольку это оригинальная реализация инструмента с официальной поддержкой.
Каждый сервер Eureka может зарегистрировать N Клиентов Eureka, каждый из которых, как правило, является индивидуальным проектом. Это также может быть сделано на любом языке или в любой среде, поэтому каждый микросервис использует то, что наиболее подходит для его задачи.
У нас будет два клиента:
- Служба конечного пользователя (Клиент Eureka на базе Java)
- Служба агрегирования данных (Клиент Eureka на основе Python)
Поскольку Eureka-это проект на основе Java, изначально предназначенный для решений Spring Boot, у него нет официальной реализации для Python. Тем не менее, мы можем использовать для этого оболочку Python, управляемую сообществом:
Имея это в виду, давайте сначала создадим сервер Эврика .
Создание сервера Эврика
Мы будем использовать Spring Boot для создания и обслуживания нашего сервера Eureka. Давайте начнем с создания каталога для размещения наших трех проектов, а в нем каталога для нашего сервера:
$ mkdir eureka-microservices $ cd eureka-microservices $ mkdir eureka-server $ cd eureka-server
Каталог eureka-server
будет корневым каталогом нашего сервера Eureka. Вы можете запустить проект Spring Boot здесь через интерфейс командной строки:
$ spring init -d=spring-cloud-starter-eureka-server
В качестве альтернативы вы можете использовать Spring Initializr и включить Сервер Эврика зависимость:
Если у вас уже есть проект и вы просто хотите включить новую зависимость, если вы используете Maven, добавьте:
org.springframework.cloud spring-cloud-starter-eureka-server ${version}
Или если вы используете Gradle:
implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-eureka-server', version: ${version}
Независимо от типа инициализации – сервер Eureka требует, чтобы одна аннотация была помечена как сервер.
В вашем Приложении конечного пользователя
классе файлов, который является нашей отправной точкой с аннотацией @SpringBootApplication
, мы просто добавим @EnableEurekaServer
:
@SpringBootApplication @EnableEurekaServer public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } }
Порт по умолчанию для серверов Eureka является 8761
, и это также рекомендовано Весенней командой. Хотя, для хорошей меры, давайте также установим его в файле application.properties
:
server.port=8761
После этого наш сервер готов к работе. Запуск этого проекта приведет к запуску сервера Eureka, доступного по адресу localhost:8761
:
Примечание: Без регистрации каких-либо услуг Eureka может ошибочно утверждать, что НЕИЗВЕСТНЫЙ экземпляр запущен.
Создание службы конечного пользователя клиента Eureka в весенней загрузке
Теперь, когда наш сервер запущен и готов к регистрации сервисов, давайте продолжим и сделаем наш Сервис для конечных пользователей в весенней загрузке. У него будет одна конечная точка, которая принимает данные JSON, касающиеся Студента . Эти данные затем отправляются в формате JSON в нашу службу Агрегации данных , которая вычисляет общую статистику оценок.
На практике эта операция была бы заменена гораздо более трудоемкими операциями, которые имеет смысл выполнять в специализированных библиотеках обработки данных и которые оправдывают использование другой службы, а не выполнение их в одной и той же.
Тем не менее, давайте вернемся и создадим каталог для нашей Службы конечного пользователя :
$ cd.. $ mkdir end-user-service $ cd end-user-service
Здесь давайте начнем новый проект через интерфейс командной строки и включим spring-cloud-starter-netflix-eureka-клиент
зависимость. Мы также добавим зависимость web
, так как это приложение будет фактически обращено к пользователю:
$ spring init -d=web, spring-cloud-starter-netflix-eureka-client
В качестве альтернативы вы можете использовать Spring Initializr и включить Клиент обнаружения Эврики зависимость:
Если у вас уже есть проект и вы просто хотите включить новую зависимость, если вы используете Maven, добавьте:
org.springframework.cloud spring-cloud-starter-netflix-eureka-client ${version}
Или если вы используете 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
, которая является более всеобъемлющей аннотацией. Это может относиться к Эврике, Консулу или Хранителю зоопарка, в зависимости от того, какой инструмент используется.
Мы также определили @Bean
здесь, чтобы мы могли @Autowire
|/RestTemplate позже в нашем контроллере. Эта
Табличка будет использоваться для отправки
СООБЩЕНИЯ запроса в
Службу агрегирования данных . Аннотация @LoadBalanced означает, что наш
Resttemplate должен использовать
Клиент балансировки нагрузки ленты при отправке запросов.
Git Essentials
Ознакомьтесь с этим практическим руководством по изучению Git, содержащим лучшие практики и принятые в отрасли стандарты. Прекратите гуглить команды Git и на самом деле изучите это!
Поскольку это приложение является клиентом 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:
INFO 3220 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8060 (http) with context path '' INFO 3220 --- [ main] .s.c.n.e.s.EurekaAutoServiceRegistration : 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] c.m.e.EndUserServiceApplication : 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
, мы сможем увидеть его зарегистрированным на сервере:
Теперь давайте продолжим и определим Ученика
модель:
public class Student { private String name; private double mathGrade; private double englishGrade; private double historyGrade; private double scienceGrade; // Constructor, getters and setters and toString() }
Для студента мы хотим рассчитать некоторые сводные статистические данные об их успеваемости, такие как среднее, минимальное и максимальное их оценки. Поскольку для этого мы будем использовать Панд, мы воспользуемся очень удобной функцией DataFrame.describe ()
. Давайте также создадим Результат оценки
модель, в которой будут храниться ваши данные после возврата из Службы агрегации данных :
public class GradesResult { private MapmathGrade; private Map englishGrade; private Map historyGrade; private Map scienceGrade; // Constructor, getters, setters and toString() }
После завершения моделей давайте сделаем действительно простой @RestController
, который принимает POST
запрос, десериализует его в Студента
и отправляет его в службу Агрегации данных , которую мы еще не сделали:
@Autowired private RestTemplate restTemplate; @RestController public class HomeController { @PostMapping("/student") public ResponseEntitystudent(@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
. Затем мы отправляем запрос в нашу службу агрегации данных
, которая еще не реализована , так как она будет зарегистрирована на Эврике, и мы упаковываем результаты JSON этого вызова в наш объект 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
Единственным отсутствующим компонентом является Служба агрегации данных , которая принимает Студента в формате JSON и заполняет фрейм данных Pandas
, выполняет определенные операции и возвращает результат обратно.
Давайте создадим каталог для нашего проекта и запустим для него виртуальную среду:
$ cd.. $ mkdir data-aggregation-service $ python3 -m venv flask-microservice
Теперь, чтобы активировать виртуальную среду, запустите файл activate
. На Окнах:
$ 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
А теперь мы можем создать ваше приложение для колбы:
$ touch flask_app.py
Теперь откройте flask_app.py
файл и импорт колб, Панд и клиентских библиотек Py-Eureka:
from flask import Flask, request import pandas as pd import py_eureka_client.eureka_client as eureka_client
Мы будем использовать Flask и запрос
для обработки наших входящих запросов и возврата ответа, а также для запуска сервера. Мы будем использовать 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 не отказал им в подключении.
Это довольно минимальное приложение для колбы с одним @app.route()
. Мы извлекли тело входящего СООБЩЕНИЯ
запроса в данные
словарь через request.json
, после чего мы создали Фрейм данных
с этими данными.
Поскольку в этом словаре вообще нет индекса, мы установили его вручную.
Наконец, мы вернули результаты функции describe()
в формате JSON. Мы не использовали jsonify
здесь, так как он возвращает Ответ
объект, а не строку. Ответ
объект, при отправке обратно будет содержать дополнительные \
символы:
{\"mathGrade\":...} vs {"mathGrade":...}
Их нужно было бы избежать, чтобы они не сбросили десериализатор.
В init()
функции eureka_client
мы задали URL-адрес нашего сервера Eureka , а также имя приложения/службы для обнаружения, а также указали порт, на котором он будет доступен. Это та же информация, которую мы предоставили в приложении Spring Boot.
Теперь давайте запустим это приложение для колбы:
(flask-microservice) $ python flask_app.py
И если мы проверим наш сервер Eureka на localhost:8761
, он зарегистрирован и готов к приему запросов:
Вызов службы Flask из службы Spring Boot с помощью Эврики
Поскольку обе наши службы запущены и работают, зарегистрированы в Eureka и могут взаимодействовать друг с другом, давайте отправим СООБЩЕНИЕ
запрос в нашу Службу конечных пользователей , содержащую некоторые данные о студентах, которые , в свою очередь, отправят СООБЩЕНИЕ
запрос в Службу агрегации данных , получим ответ и перешлем его нам:
$ 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.
Эти службы построены с использованием разных платформ и разных языков программирования, хотя с помощью API REST взаимодействие между ними является простым и легким.
Исходный код для этих двух служб, включая сервер Eureka, доступен на Github .