1. Introducción

Bienvenidos al siguiente post de Spring Cloud. Veremos a continuación Hystrix!, que es básicamente la implementación del patrón Cirtuit Breaker que ha adoptado la gente de Netflix OSS.

Cada vez es más común consumir servicios externos a nuestras aplicaciones ya sea haciendo uso de servicios web, rest o los antiguos rmi o llamada a procedimientos remotos. La mayoría de las ocasiones dichos servicios estarán operativos por lo que la interconexión entre nuestro sistema y el externo será satisfactoria. En este momento decimos que el circuito está cerrado. Sin embargo hay ocasiones en que esto no es así y la conexión falla, por lo que decimos que el circuito está abierto.

Lo que nos interesa sobre este patrón son dos cosas.

  • Dar una respuesta fácil, rápida y transparente cuando el circuito está abierto de tal forma que el sistema pueda recuperarse
  • Tener la capacidad de monitorizar todos los circuitos de nuestra aplicación

Para encontrar más información visitar el post del gran Martin Flower sobre este patrón.

Os podéis descargar el código de ejemplo de mi GitHub aquí.

  • Java 8
  • Gradle 3.1
  • SpringBoot 1.5.2.RELEASE
  • Spring 4.3.7.RELEASE
  • SpringCloud 1.1.5.RELEASE

2. Eureka

Necesitamos Eureka para realizar el registro y descubrimiento de nuestros microservicios. Para más información visitar el post.

...
dependencies {
    compile 'org.springframework.cloud:spring-cloud-starter-eureka-server'
}
server:
   port: 8761
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);
    }
}

3. Microservicio de ejemplo Backend

Creamos un microservicio de ejemplo sobre el que implementar el patrón Cirtuit Breaker. Debemos:

  • Incluir dependencia spring-cloud-starter-hystrix y spring-boot-starter-actuator
  • Utilizar la anotación @EnableCircuitBreaker en el fichero Application.java
  • Utilizar la anotación @HystrixCommand en aquellos métodos que llamen a servicios externos y sean susceptibles de fallar.

Incluirmos las dependencias spring-cloud-starter-hystrix y spring-boot-starter-actuator. Además incluimos spring-cloud-starter-eureka para registrarnos 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.backend.Application"
}

repositories {
    mavenCentral()
}

dependencies {
    compile('org.springframework.cloud:spring-cloud-starter-eureka')
    compile('org.springframework.cloud:spring-cloud-starter-hystrix')
    compile('org.springframework.boot:spring-boot-starter-actuator')
}

Hacemos uso de la anotación @EnableCircuitBreaker

package com.jorgehernandezramirez.spring.springcloud.backend;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableCircuitBreaker
@EnableDiscoveryClient
@SpringBootApplication
public class Application {

    public Application(){
        //For Spring
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Creamos un controlador de ejemplo en donde simulamos que el circuito siempre está abierto. Mediante el uso de la anotación @HystrixCommand indicamos cual es el método fallback que se debe ejecutar en caso de que dicho circuito esté abierto. PD: Un circuito está abierto cuando ocurre una excepción no controlada

package com.jorgehernandezramirez.spring.springcloud.backend.controller;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.sun.jersey.api.client.ClientHandlerException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/token")
public class TokenController {

    public TokenController(){
        //For Spring
    }

    @HystrixCommand(fallbackMethod = "getTokenHystrixFallbackMethod")
    @RequestMapping(method = RequestMethod.GET)
    public String getToken() {
        //Here we throw a ClientHandlerException simulating error of communication
        throw new ClientHandlerException();
    }

    private String getTokenHystrixFallbackMethod() {
        return "El servicio se encuentra no disponible. Inténtelo más tarde.";
    }
}

Circuito abierto

Si ejecutamos

curl http://localhost:8080/token

Obtenemos la respuesta del fallback

El servicio se encuentra no disponible. Inténtelo más tarde.

Monitorización

Para monitorizar el estado de los circuitos de nuestro microservicio

curl http://localhost:8080/hystrix.stream

Obtendremos algo como

data: {“type”:”HystrixCommand”,”name”:”getToken”,”group”:”TokenController”,”currentTime”:1490003090680
,”isCircuitBreakerOpen”:true…

4. Turbine

Como ya sabemos, en un entorno cloud, podemos tener múltiples instancias de un microservicio sin saber a pripori en qué máquinas ni puertos se encuentran alojadas. Por este motivo se hace necesario un mecanismo que nos indique el estado de los circuitos de todas las instancia de un microservicio y no solamente de uno en concreto como vimos en el punto anterior. Es aquí cuando aparece Turbine!

Básicamente lo que hace es preguntarle a Eureka en donde se encuentra las instancias de nuestro microservicio, para ir llamando al controlador /hystrix.stream de cada uno de ellos, con el fin mostrarnos la información unificada.

Para ver el estado de todas las instancias de nuestro microservicio basta con llamar a (arrancamos turbine en el puerto 8081)

http://localhost:8081/turbine.stream?cluster=BACKEND

Para utilizar turbine necesitaremos

  • Incluir dependencia spring-cloud-starter-turbine
  • Utilizar anotación @EnableTurbine
  • Configuración de los microservicios que vamos a monitorizar en application.yml
group 'com.jorgehernandezramirez.spring.springcloud'
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.SR6'
    }
}

sourceCompatibility = 1.8

springBoot {
    mainClass = "com.jorgehernandezramirez.spring.springcloud.turbine.Application"
}

repositories {
    mavenCentral()
}

dependencies {
    compile('org.springframework.cloud:spring-cloud-starter-turbine')
}
package com.jorgehernandezramirez.spring.springcloud.turbine;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.turbine.EnableTurbine;

@EnableTurbine
@EnableDiscoveryClient
@SpringBootApplication
public class Application {

    public Application(){
        //For Spring
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

En el fichero application.yml indicamos el cluster y el nombre del microservicio registrado en Eureka. Evidentemente necesitamos configurar la conexión a Eureka.

server:
   port: 8081

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
  instance:
    hostname: localhost
    nonSecurePort: 8081

turbine:
  aggregator:
    clusterConfig: BACKEND
  appConfig: backend

Monitorización

Para monitorizar el estado de los circuitos de nuestro microservicio

http://localhost:8081/turbine.stream?cluster=BACKEND

Obtendremos algo como

data: {“rollingCountFallbackSuccess”:1,”rollingCountFallbackFailure”:0,
“propertyValue_circuitBreakerRequestVolumeThreshold”:20,”propertyValue_circuitBreakerForceOpen”:false,
“propertyValue_metricsRollingStatisticalWindowInMilliseconds”:10000,”latencyTotal_mean”:0,
“rollingMaxConcurrentExecutionCount”:1,”type”:”HystrixCommand”,”rollingCountResponsesFromCache”:0,
“rollingCountBadRequests”:0,”rollingCountTimeout”:0,”propertyValue_executionIsolationStrategy”:”THREAD”,
“rollingCountFailure”:1,”rollingCountExceptionsThrown”:0,”rollingCountFallbackMissing”:0,”threadPool”:
“TokenController”,”latencyExecute_mean”:0,”isCircuitBreakerOpen”:false,”errorCount”:1,
“rollingCountSemaphoreRejected”:0,”group”:”TokenController”,”latencyTotal”: …

5. Hystrix Dashboard

Ya hemos visto como monitorizar los circuitos de nuestros microservicios. Sin embargo la interpretación de esta información puede no ser clara ya que debemos leer el resultado json de los controladores /hystrix.stream o /turbine.stream. Por este motivo aparece Hystrix Dashboard!. Básicamente lo que hace es leer la información de estos controladores y pintar en pantalla los resultados. Necesitaremos

  • Incluir dependencia spring-cloud-starter-hystrix-dashboard
  • Utilizar anotación @EnableHystrixDashboard
...
dependencies {
    compile('org.springframework.cloud:spring-cloud-starter-hystrix-dashboard')
}
package com.jorgehernandezramirez.spring.springcloud.hystrixdashboard;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;

@EnableHystrixDashboard
@SpringBootApplication
public class Application {

    public Application(){
        //For Spring
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Arrancamos en el puerto 8082

server:
   port: 8082

Arrancamos

Vamos a http://localhost:8082/hystrix e indicamos la url localhost:8081/turbine.stream?cluster=BACKEND

El resultado se muestra a continuación. Podemos ver el estado del circuito getToken del microservicio backend

6. Conclusiones

En este post hemos visto como Hystrix es una solución para implementar el patrón Circuit Breaker en donde podemos monitorizar el estado de todos nuestros circuitos en tiempo real.

El mayor inconveniente de Hystrix es que no guarda un histórico de lo ocurrido en nuestros circuitos. De esta forma únicamente podemos ver la foto actual del sistema.

7. Referencias