1. Introducción
Bienvenidos al primer post sobre Spring Cloud de nuestro blog!.
Este será el primero de algunos posts que iremos publicando sobre esta tecnología y que hoy nos centraremos principalmente en el servidor de Eureka.
Spring Cloud es un conjunto de tecnologías desarrolladas por Netflix (Sí!, la plataforma online de series y películas) bajo el nombre Netflix OSS. Básicamente viene a dar respuesta a cómo desplegar y manejar nuestros microservicios de SpringBoot en un entorno cloud. Nos permite dar salida a algunas de las siguientes cuestiones:
- Registro y descubrimiento de servicios
- Balanceo de la carga
- Enrutamiento
- Control de ruptura de servicios
- Configuración distribuída
En este primer post nos centraremos en el registro y descubrimiento de servicios así como en el balanceo de la carga.
Para entender la problemática, os pongo el siguiente supuesto. Imaginemos que tenemos 4 instancias de un mismo microservicio de gestión de usuarios los cuales exponen el método /users
que nos da el listado de usuarios del sistema. Ahora imaginemos que tenemos, en nuestro entorno, otro microservicio que necesita consumir el controlador expuesto, sin saber a priori en qué máquinas ni qué puertos están desplegadas. Es aquí donde se hace necesaria la aparición de una entidad que lleve el recuento de las instancias de nuestros microservicios, así como su descubrimiento en caso de necesitarlo. Es aquí donde cobra sentido, el Eureka Server
!.
Os podéis descargar el código de ejemplo de mi GitHub aquí.
Tecnologías empleadas:
- Java 8
- Gradle 3.1
- SpringBoot 1.5.2.RELEASE
- Spring 4.3.7.RELEASE
- SpringCloud 1.1.5.RELEASE
2. Eureka server
Mostramos el build.gradle
group 'com.jorgehernandezramirez.spring.springboot' version '1.0-SNAPSHOT' buildscript { repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:1.4.1.RELEASE") } } apply plugin: 'java' apply plugin: 'idea' apply plugin: 'maven' apply plugin: 'spring-boot' dependencyManagement { imports { mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Camden.SR2' } } sourceCompatibility = 1.8 springBoot { mainClass = "com.jorgehernandezramirez.spring.springboot.eureka.Application" } repositories { mavenCentral() } dependencies { compile 'org.springframework.cloud:spring-cloud-starter-eureka-server' }
Arrancamos en el puerto 8761
Para habilitar el servidor de Eureka basta con utilizar la anotación @EnableEurekaServer
package com.jorgehernandezramirez.spring.springcloud.eureka; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @EnableEurekaServer @SpringBootApplication public class Application { public Application(){ //For Spring } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Arrancamos el servidor de Eureka y nos vamos a http://localhost:8761
y voala!. Vemos como EUREKA se ha registrado a sí mismo.
3. Registrando un microservicio en Eureka
Nos creamos otro microservicio en el puerto 8080 que se registre en Eureka. Para ello necesitaremos:
- Incluir dependencias del cliente de Eureka
- Indicar en los properties donde está Eureka
- Utilizar la anotación
@EnableDiscoveryClient
- Indicar el nombre con el que nos registraremos en Eureka
Utilización de la dependencia org.springframework.cloud:spring-cloud-starter-eureka
que contiene el cliente que nos registrará en Eureka.
group 'com.jorgehernandezramirez.spring.springboot' version '1.0-SNAPSHOT' buildscript { repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.2.RELEASE") } } apply plugin: 'java' apply plugin: 'idea' apply plugin: 'maven' apply plugin: 'spring-boot' dependencyManagement { imports { mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Camden.SR2' } } sourceCompatibility = 1.8 springBoot { mainClass = "com.jorgehernandezramirez.spring.springcloud.Application" } repositories { mavenCentral() } dependencies { compile 'org.springframework.cloud:spring-cloud-starter-eureka' }
Indicamos que queremos levantar nuestra aplicación en el puerto 8080. Después se indica donde se encuentra eureka y como queremos que se nos descubra (localhost:8080)
server: port: 8080 eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ instance: hostname: localhost nonSecurePort: 8080
En el fichero bootstrap.yml es en donde indicamos el nombre con el que queremos que nuestro microservicio se registre en eureka.
Utilización de la anotación @EnableDiscoveryClient
package com.jorgehernandezramirez.spring.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.feign.EnableFeignClients; @SpringBootApplication @EnableDiscoveryClient public class Application { public Application(){ //For Spring } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Una vez arrancada la aplicación, nos vamos de nuevo a http://localhost:8761
y vemos nuestro microservicio registrado.
4. Ribbon
Hasta ahora te preguntarás, y todo esto ¿para qué?. La respuesta está en lo que viene a continuación. ¿Te acuerdas del supuesto que hablamos en la introducción? Pues bien, lo que pretendemos hacer ahora es que podamos consumir un servicio sin conocer cuantas instancias del mismo existen, ni las máquinas en la que se encuentran, alojadas ni sus puertos. Para solventar este problema tenemos Ribbon, el cual se encargará de dos cosas:
- Descubrimiento. Hacer el descubrimiento de las instancias, preguntandole a Eureka donde se encuentran
- Balanceo. De todas las instancias encontradas seleccionar una
¿Cómo lo vamos a hacer?
Vamos a configurar la clase RestTemplate para que utilice Ribbon por debajo, de tal forma que atacaremos a nuestros servicios de la siguiente manera.
Nos creamos un bean de Spring de la clase RestTemplate utilizando la anotación @LoadBalanced
. De esta mágica forma Ribbon ya actuará :).
package com.jorgehernandezramirez.spring.springcloud.configuration; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class RestTemplateConfiguration { @Bean @LoadBalanced RestTemplate loadBalancedRestTemplate() { return new RestTemplate(); } }
Realmente tal magia no existe, lo que está pasando es que Spring por debajo está configurando un interceptor (RetryLoadBalancerInterceptor) para el objeto RestTemplate que está creando.
Nos creamos un controlador de prueba para testear nuestra aplicación
package com.jorgehernandezramirez.spring.springcloud.controller; import com.jorgehernandezramirez.spring.springcloud.feign.TestFeign; import com.netflix.discovery.converters.Auto; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController @RequestMapping("/ping") public class TestController { @Autowired private RestTemplate restTemplate; public TestController(){ //For Spring } @RequestMapping public String doAlive() { return "Alive!"; } @RequestMapping("/rest") public String doRestAlive() { return new RestTemplate().getForObject("http://localhost:8080/ping", String.class); } @RequestMapping("/rest/ribbon") public String doRestAliveUsingEurekaAndRibbon() { return restTemplate.getForObject("http://SPRINGCLOUDWEBTEST/ping", String.class); } }
Llamamos al controlador ping
Nos devuelve
Alive!
Llamamos al controlador ping/rest que hace una llamada rest a http://localhost:8080/ping
Nos devuelve
Alive!
Llamamos al controlador ping/rest/ribbon que hace una llamada rest a http:/SPRINGCLOUDWEBTEST/ping
Nos devuelve
Alive!
5. Feign
Visto Ribbon, ¿qué es Feign?. Pues es simple y llanamente una forma de consumir servicios rest de una forma fácil, rápida e indolora. Es exactamente lo mismo que utilizar RestTemplate pero de otra forma. Evidentemente utiliza Ribbon por debajo para hacer el descubrimiento de servicios y el balanceo correspondiente. Para configurarlo necesitaremos:
- Incluir dependencia Feign
- Crear interfaz Feign
- Utilizar la anotación
@FeignClients
Incluimos la dependencia
Vemos a continuación como crear un cliente feign. Destacar que necesitamos especificar el nombre del microservicio al que queremos atacar en la anotación @FeignClient
package com.jorgehernandezramirez.spring.springcloud.feign; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @FeignClient("SPRINGCLOUDWEBTEST") public interface TestFeign { @RequestMapping(value="/ping", method = RequestMethod.GET) String doAlive(); }
Añadimos la anotación @EnableFeignClients
. Además debemos indicar el paquete donde se encuentras nuestras interfaces feign.
@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients("com.jorgehernandezramirez.spring.springcloud.feign") public class Application { ... }
Para utilizar nuestro cliente Feign lo inyectamos en nuestro controlador de pruebas y hacemos uso de él.
package com.jorgehernandezramirez.spring.springcloud.controller; ... @RestController @RequestMapping("/ping") public class TestController { ... @Autowired private TestFeign testFeign; ... @RequestMapping("/rest/feign") public String doRestAliveUsingFeign() { return testFeign.doAlive(); } }
Desplegamos de nuevo y llamamos al controlador ping/rest/feign que hace una llamada rest a http:/SPRINGCLOUDWEBTEST/ping utilizando Ribbon para realizar el descubrimiento del servicio y el balanceo correspondiente.
Nos devuelve
Alive!