Сервер, клиент и шлюз Eureka
🙌 Репозиторий Github для приложения: https://github.com/OmarElGabry/microservices-spring-boot
Вспоминая нашу архитектуру приложения, у нас есть реестр сервисов, сервис изображений, сервис галерей и шлюз.
Служба галереи использует службу изображений внизу и извлекает список всех изображений для отображения.
Мы собираемся создать версию приложений для весенней загрузки: 2.0.0.RELEASE.
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.0.RELEASE</version> <relativePath/> </parent>
Сервер Eureka
Это сервер имен или реестр служб. Обязанность дать имена каждому микросервису. Почему?
- Нет необходимости жестко кодировать IP-адреса микросервисов.
- Что делать, если службы используют динамические IP-адреса; при автомасштабировании.
Итак, каждая служба регистрируется в Eureka и отправляет эхо-запрос на сервер Eureka, чтобы сообщить, что он активен.
Если сервер Eureka не получил никаких уведомлений от службы. Эта служба автоматически удаляется с сервера Eureka.
Шаги довольно простые. 1, 2, 3,… и готово !.
Ok. Итак, как обычно, создайте проект maven или воспользуйтесь spring initializr. Не забудьте включить в файл pom.xml
следующие зависимости: Web, Eureka Server и DevTools (необязательно).
.... | |
<dependencies> | |
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-web</artifactId> | |
</dependency> | |
<dependency> | |
<groupId>org.springframework.cloud</groupId> | |
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> | |
</dependency> | |
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-devtools</artifactId> | |
<optional>true</optional> | |
</dependency> | |
</dependencies> | |
.... |
Затем в файле application.properties
нам нужно установить некоторые конфигурации.
# Give a name to the eureka server | |
spring.application.name=eureka-server | |
# default port for eureka server | |
server.port=8761 | |
# eureka by default will register itself as a client. So, we need to set it to false. | |
# What's a client server? See other microservices (image, gallery, auth, etc). | |
eureka.client.register-with-eureka=false | |
eureka.client.fetch-registry=false |
Наконец, в классе основного приложения весенней загрузки включите сервер Eureka с помощью аннотации @EnableEurekaServer
.
package com.eureka.server; | |
import org.springframework.boot.SpringApplication; | |
import org.springframework.boot.autoconfigure.SpringBootApplication; | |
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; | |
@SpringBootApplication | |
@EnableEurekaServer // Enable eureka server | |
public class SpringEurekaServerApplication { | |
public static void main(String[] args) { | |
SpringApplication.run(SpringEurekaServerApplication.class, args); | |
} | |
} |
Все идет нормально?. Далее мы создаем наши сервисы; изображение и галерея.
Служба изображений
Клиентский сервис Eureka - это независимый сервис в микросервисной архитектуре. Это может быть оплата, учетная запись, уведомление, авторизация, конфигурация и т. Д.
Служба изображений действует как источник данных для изображений, каждое изображение имеет идентификатор, заголовок и URL-адрес. Достаточно просто ?.
Ok. Итак, для файла pom.xml
вместо Eureka Server используйте Eureka Client.
.... | |
<dependencies> | |
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-web</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-data-rest</artifactId> | |
</dependency> | |
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-devtools</artifactId> | |
<optional>true</optional> | |
</dependency> | |
</dependencies> | |
.... |
В файле application.properties
определяем конфигурации (как и раньше)
# serivce name | |
spring.application.name=image-service | |
# port | |
server.port=8200 | |
# eureka server url | |
eureka.client.service-url.default-zone=http://localhost:8761/eureka |
Затем включите клиент eureka, используя аннотацию @EnableEurekaClient
.
package com.eureka.image; | |
import org.springframework.boot.SpringApplication; | |
import org.springframework.boot.autoconfigure.SpringBootApplication; | |
import org.springframework.cloud.netflix.eureka.EnableEurekaClient; | |
@SpringBootApplication | |
@EnableEurekaClient // Enable eureka client. It inherits from @EnableDiscoveryClient. | |
public class SpringEurekaImageApp { | |
public static void main(String[] args) { | |
SpringApplication.run(SpringEurekaImageApp.class, args); | |
} | |
} |
Теперь наш сервис изображений собирается предоставлять некоторые данные через конечные точки, не так ли? Итак, нам нужно создать контроллер и определить методы действий.
package com.eureka.image.controllers; | |
import java.util.Arrays; | |
import java.util.List; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.core.env.Environment; | |
import org.springframework.web.bind.annotation.RequestMapping; | |
import org.springframework.web.bind.annotation.RestController; | |
import com.eureka.image.entities.Image; | |
@RestController | |
@RequestMapping("/") | |
public class HomeController { | |
@Autowired | |
private Environment env; | |
@RequestMapping("/images") | |
public List<Image> getImages() { | |
List<Image> images = Arrays.asList( | |
new Image(1, "Treehouse of Horror V", "https://www.imdb.com/title/tt0096697/mediaviewer/rm3842005760"), | |
new Image(2, "The Town", "https://www.imdb.com/title/tt0096697/mediaviewer/rm3698134272"), | |
new Image(3, "The Last Traction Hero", "https://www.imdb.com/title/tt0096697/mediaviewer/rm1445594112")); | |
return images; | |
} | |
} |
Не забудьте создать класс сущности Image с тремя полями; идентификатор, заголовок и URL.
Услуги галереи
Клиентская служба Eureka также может быть клиентом REST, который вызывает (потребляет) другие службы (службы REST API) в нашем приложении микросервиса.
Так, например, служба галереи вызывает службу изображений, чтобы получить список всех изображений или, возможно, только изображений, созданных в течение определенного года.
Вызовы от этого REST-клиента к другим службам можно выполнять с помощью:
- RestTemplate. Объект, который может отправлять запросы к службам REST API.
- FeignClient (действует как прокси) и предоставляет другой подход к RestTemplate.
И то, и другое - запросы балансировки нагрузки между сервисами.
- Что такое балансировка нагрузки?
Что делать, если несколько экземпляров службы работают на разных портах. Итак, нам нужно сбалансировать запросы между всеми экземплярами службы.
При использовании подхода «ленточный» (по умолчанию) запросы будут распределяться между ними поровну.
Итак, как обычно, начинаем с pom.xml
. Он такой же, как и для сервиса изображений. Далее идет файл application.properties
.
spring.application.name=gallery-service | |
server.port=8100 | |
eureka.client.service-url.default-zone=http://localhost:8761/eureka |
В классе основного приложения весенней загрузки, помимо включения клиента eureka, нам нужно создать bean-компонент для RestTemplate
для вызова службы изображений.
package com.eureka.gallery; | |
import org.springframework.boot.SpringApplication; | |
import org.springframework.boot.autoconfigure.SpringBootApplication; | |
import org.springframework.cloud.client.loadbalancer.LoadBalanced; | |
import org.springframework.cloud.netflix.eureka.EnableEurekaClient; | |
import org.springframework.context.annotation.Bean; | |
import org.springframework.context.annotation.Configuration; | |
import org.springframework.web.client.RestTemplate; | |
@SpringBootApplication | |
@EnableEurekaClient // Enable eureka client. | |
public class SpringEurekaGalleryApp { | |
public static void main(String[] args) { | |
SpringApplication.run(SpringEurekaGalleryApp.class, args); | |
} | |
} | |
@Configuration | |
class RestTemplateConfig { | |
// Create a bean for restTemplate to call services | |
@Bean | |
@LoadBalanced // Load balance between service instances running at different ports. | |
public RestTemplate restTemplate() { | |
return new RestTemplate(); | |
} | |
} |
В контроллере вызовите службу изображений с помощью RestTemplate
и верните результат.
package com.eureka.gallery.controllers; | |
import java.util.List; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.core.env.Environment; | |
import org.springframework.web.bind.annotation.PathVariable; | |
import org.springframework.web.bind.annotation.RequestMapping; | |
import org.springframework.web.bind.annotation.RestController; | |
import org.springframework.web.client.RestTemplate; | |
import com.eureka.gallery.entities.Gallery; | |
@RestController | |
@RequestMapping("/") | |
public class HomeController { | |
@Autowired | |
private RestTemplate restTemplate; | |
@Autowired | |
private Environment env; | |
@RequestMapping("/") | |
public String home() { | |
// This is useful for debugging | |
// When having multiple instance of gallery service running at different ports. | |
// We load balance among them, and display which instance received the request. | |
return "Hello from Gallery Service running at port: " + env.getProperty("local.server.port"); | |
} | |
@RequestMapping("/{id}") | |
public Gallery getGallery(@PathVariable final int id) { | |
// create gallery object | |
Gallery gallery = new Gallery(); | |
gallery.setId(id); | |
// get list of available images | |
List<Object> images = restTemplate.getForObject("http://image-service/images/", List.class); | |
gallery.setImages(images); | |
return gallery; | |
} | |
// -------- Admin Area -------- | |
// This method should only be accessed by users with role of 'admin' | |
// We'll add the logic of role based auth later | |
@RequestMapping("/admin") | |
public String homeAdmin() { | |
return "This is the admin area of Gallery service running at port: " + env.getProperty("local.server.port"); | |
} | |
} |
Хорошо, вот что нужно отметить. Поскольку мы используем restTemplate
, который, в свою очередь, использует Eureka Server для именования служб и Ribbon для балансировки нагрузки. Итак, мы можем использовать имя службы (например, image-service
) вместо localhost:port
.
Ворота - Зуул
При вызове любой службы из браузера мы не можем называть ее по имени, как это делали в службе галереи - это используется внутри служб между службами.
И поскольку мы создаем больше экземпляров сервисов, каждый с разными номерами портов, теперь возникает вопрос: Как мы можем вызывать сервисы из браузера и распределять запросы между их экземплярами, работающими на разных портах?
Что ж, обычное решение - использовать шлюз.
Шлюз - это единственная точка входа в систему, которая используется для обработки запросов путем их маршрутизации в соответствующую службу. Его также можно использовать для аутентификации, мониторинга и многого другого.
Что такое Зуул?
Это прокси, шлюз, промежуточный уровень между пользователями и вашими услугами.
Сервер Eureka решил проблему присвоения имен службам вместо жесткого кодирования их IP-адресов.
Но, тем не менее, у нас может быть несколько служб (экземпляров), работающих на разных портах. Итак, Зуул ...
- Сопоставляет префиксный путь say
/gallery/**
и службуgallery-service
. Он использует сервер Eureka для маршрутизации запрошенной службы. - Он балансирует нагрузку (с помощью ленты) между экземплярами службы, работающей на разных портах.
- Что еще? Мы можем фильтровать запросы, добавлять аутентификацию и т. д.
В pom.xml
добавьте зависимости: Web, Eureka Client и Zuul.
.... | |
<dependencies> | |
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-web</artifactId> | |
</dependency> | |
<dependency> | |
<groupId>org.springframework.cloud</groupId> | |
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> | |
</dependency> | |
<dependency> | |
<groupId>org.springframework.cloud</groupId> | |
<artifactId>spring-cloud-starter-netflix-zuul</artifactId> | |
</dependency> | |
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-devtools</artifactId> | |
<optional>true</optional> | |
</dependency> | |
</dependencies> | |
.... |
Стоит отметить, что Зуул выступает в роли клиента Eureka. Итак, мы даем ему имя, порт и ссылку на сервер Eureka (так же, как мы делали с сервисом изображений).
server.port=8762 | |
spring.application.name=zuul-server | |
eureka.client.service-url.default-zone=http://localhost:8761/eureka/ | |
# A prefix that can added to beginning of all requests. | |
#zuul.prefix=/api | |
# Disable accessing services using service name (i.e. gallery-service). | |
# They should be only accessed through the path defined below. | |
zuul.ignored-services=* | |
# Map paths to services | |
zuul.routes.gallery-service.path=/gallery/** | |
zuul.routes.gallery-service.service-id=gallery-service | |
Наконец, включите Zuul и Eureka Client.
package com.eureka.zuul; | |
import org.springframework.boot.SpringApplication; | |
import org.springframework.boot.autoconfigure.SpringBootApplication; | |
import org.springframework.cloud.netflix.eureka.EnableEurekaClient; | |
import org.springframework.cloud.netflix.zuul.EnableZuulProxy; | |
@SpringBootApplication | |
@EnableEurekaClient // It acts as a eureka client | |
@EnableZuulProxy // Enable Zuul | |
public class SpringZuulApplication { | |
public static void main(String[] args) { | |
SpringApplication.run(SpringZuulApplication.class, args); | |
} | |
} |
Тестирование наших микросервисов
Ok. Итак, у нас есть обнаружение службы; Сервер Эврика. Две услуги; изображение и галерея. И шлюз; Зуул.
Чтобы протестировать наше приложение, запустите сервер eureka, zuul, а затем две службы. Затем перейдите на сервер Eureka Server, работающий на localhost:8761
, вы должны увидеть запущенные службы.
Для запуска нескольких экземпляров. В eclipse перейдите в Run → Configurations / Arguments → VM options и добавьте
-Dserver.port=8300
Чтобы пропинговать службу галереи, отправьте запрос на шлюз, добавив путь для службы галереи localhost:8762/gallery
.
Вы должны увидеть сообщение ниже, и если вы снова нажмете URL-адрес, запрос будет перенаправлен на второй экземпляр службы галереи; благодаря балансировщику нагрузки.
Привет от службы галереи, работающей в порту: 8100
Привет от службы галереи, работающей в порту: 8300
Чтобы получить все изображения, нажмите localhost:8762/gallery/1
в браузере.
{ "id": 1, "images": [ { "id": 1, "title": "Treehouse of Horror V", "url": "https://.../rm3842005760" }, { "id": 2, "title": "The Town", "url": "https://.../rm3698134272" }, { "id": 3, "title": "The Last Traction Hero", "url": "https://.../rm1445594112" } ] }
Но мы еще не закончили! Нам все еще нужно аутентифицировать пользователей. Затем мы будем использовать JSON Web Tokens (JWT) для аутентификации.
Спасибо за чтение! Если вам понравилось, пожалуйста, похлопайте 👏 в ладоши.