Автор оригинала: 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 Map mathGrade;
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 ResponseEntity 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 . Затем мы отправляем запрос в нашу службу агрегации данных , которая еще не реализована , так как она будет зарегистрирована на Эврике, и мы упаковываем результаты 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 .