Implementar tolerancia a fallos con Resilience4j

Escrito por el .
java planeta-codigo
Enlace permanente Comentarios

Resilience4j

Java

Hystrix ha sido una de las primeras librerías en Java disponibles para implementar varios patrones de tolerancia a fallos en los microservicios. Desde hace un tiempo ha pasado a modo mantenimiento en el que ya no se incluyen nuevas características, una de las librerías recomendadas como sustituta es Resilience4j. Resilience4j proporciona las características similares con algunas ventajas adicionales.

Los patrones útiles para aumentar la tolerancia a fallos debido a problemas de red o fallo de alguno de los múltiples servicios son:

  • Circuit breaker: para dejar de hacer peticiones cuando un servicio invocado está fallando.
  • Retry: realiza reintentos cuando un servicio ha fallado de forma temporal.
  • Bulkhead: limita el número de peticiones concurrentes salientes a un servicio para no sobrecargarlo.
  • Rate limit: limita el número de llamadas que recibe un servicio en un periodo de tiempo.
  • Cache: intenta obtener un valor de la cache y si no está presente de la función de la que lo recupera.
  • Time limiter: limita el tiempo de ejecución de una función para no esperar indifinidamente a una respuesta.
  • Además de la funcionalidad de métricas.

La ventaja de Resilience4j es que todos estos patrones para los microservicios se ejecutan en el mismo hilo que el principal y no en un aparte como en Hystrix. Su uso además no requiere de crear clases específicas para hacer uso de los patrones y pueden emplearse las funciones lambda incorporadas en Java 8.

Este artículo actualiza el ejemplo que usaba Spring Boot y Spring Cloud que implementé en su momento para la serie de artículos sobre Spring Cloud añadiendo una implementación del cliente de un microservicio con Resilience4j.

La configuración de Resilience4j se puede proporcionar mediante código Java, con anotaciones y con la integración para Spring Boot con parámetros en el archivo de configuración de la aplicación. La aplicación además de varias cosas de Spring Cloud para otros artículos de la serie consiste para el de este artículo en un servicio cliente y un servicio servidor que para ser tolerantes a fallos hacen uso de los patrones circuit breaker y time limiter para demostrar su uso.

Resilience4j para implementar los patrones lo que hace es decorar la función objetivo que hace la invocación del servicio. Si se quieren aplicar varios patrones hay que encadenar las decoraciones, por ejemplo, si se quiere limitar el número de llamadas salientes con bulkhead y el patrón circuit breaker hay que aplicar una decoración sobre la otra. En este ejemplo se aplica un time limiter y un circuit breaker usando código Java. La variable get es la que realmente contiene la llamada al servicio.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package io.github.picodotdev.blogbitix.springcloud.client;

...

@Component
public class Resilience4jProxyService {

    @Autowired
    private LoadBalancerClient loadBalancer;

    @Autowired
    private Tracing tracing;

    @Autowired
    private Tracer tracer;

    private CircuitBreakerConfig circuitBreakerConfiguration;
    private TimeLimiterConfig timeLimiterConfiguration;

    public Resilience4jProxyService() {
        circuitBreakerConfiguration = CircuitBreakerConfig.custom()
                .failureRateThreshold(50)
                .recordExceptions(IOException.class, TimeoutException.class)
                .build();

        timeLimiterConfiguration = TimeLimiterConfig.custom()
                .timeoutDuration(Duration.ofMillis(2500))
                .cancelRunningFuture(true)
                .build();
    }

    public String get() {
        ServiceInstance instance = loadBalancer.choose("proxy");
        URI uri = instance.getUri();
        String resource = String.format("%s%s", uri.toString(), "/service");
        Invocation.Builder builder = ClientBuilder.newClient().target(resource).request();

        ...

        CircuitBreaker circuitBreaker = CircuitBreaker.of("resilience4jCircuitBreakerProxyService", circuitBreakerConfiguration);
        TimeLimiter timeLimiter = TimeLimiter.of(timeLimiterConfiguration);

        Supplier<CompletableFuture<String>> get = () -> {
            return CompletableFuture.supplyAsync(() -> {
                return builder.get().readEntity(String.class);
            });
        };
        Callable<String> getLimiter = TimeLimiter.decorateFutureSupplier(timeLimiter, get);
        Callable<String> getCircuitBreaker = CircuitBreaker.decorateCallable(circuitBreaker, getLimiter);

        return Try.of(getCircuitBreaker::call).recover((throwable) -> getFallback()).get();
    }

    private String getFallback() {
        return "Fallback";
    }
}
Resilience4jProxyService.java

Las dependencias que hay que añadir en el proyecto son:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
plugins {
    id 'application'
    id 'org.springframework.boot' version '2.1.6.RELEASE'
}

mainClassName = 'io.github.picodotdev.blogbitix.springcloud.client.Main'

dependencies {
    implementation platform("org.springframework.boot:spring-boot-dependencies:2.1.6.RELEASE")
    implementation platform("org.springframework.cloud:spring-cloud-dependencies:Greenwich.SR1")

    // Spring
    def excludeSpringBootStarterLogging = { exclude(group: 'org.springframework.boot', module: 'spring-boot-starter-logging') }
    compile('org.springframework.boot:spring-boot-starter', excludeSpringBootStarterLogging)
    compile('org.springframework.boot:spring-boot-starter-web', excludeSpringBootStarterLogging)
    ...
    compile('org.springframework.cloud:spring-cloud-starter-config', excludeSpringBootStarterLogging)
    ...

    compile('io.github.resilience4j:resilience4j-spring-boot2:0.17.0', excludeSpringBootStarterLogging)
    compile('io.micrometer:micrometer-registry-prometheus:1.1.5')

    ...
}
build.gradle

Resilience4j proporciona añadidos de integración con Spring Boot y exportación de métricas para Prometheus.

Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./gradle-run.sh


Este artículo forma parte de la serie spring-cloud:

  1. Datos de sesión externalizados con Spring Session
  2. Aplicación Java autocontenida con Spring Boot
  3. Configuración de una aplicación en diferentes entornos con Spring Cloud Config
  4. Información y métricas de la aplicación con Spring Boot Actuator
  5. Registro y descubrimiento de servicios con Spring Cloud y Consul
  6. Aplicaciones basadas en microservicios
  7. Registro y descubrimiento de servicios con Spring Cloud Netflix
  8. Recargar sin reiniciar la configuración de una aplicación Spring Boot con Spring Cloud Config
  9. Almacenar cifrados los valores de configuración sensibles en Spring Cloud Config
  10. Tolerancia a fallos en un cliente de microservicio con Spring Cloud Netflix y Hystrix
  11. Balanceo de carga y resiliencia en un microservicio con Spring Cloud Netflix y Ribbon
  12. Proxy para microservicios con Spring Cloud Netflix y Zuul
  13. Monitorizar una aplicación Java de Spring Boot con Micrometer, Prometheus y Grafana
  14. Exponer las métricas de Hystrix en Grafana con Prometheus de una aplicación Spring Boot
  15. Servidor OAuth, gateway y servicio REST utilizando tokens JWT con Spring
  16. Trazabilidad en microservicios con Spring Cloud Sleuth
  17. Implementar tolerancia a fallos con Resilience4j
  18. Iniciar una aplicación de Spring Boot en un puerto aleatorio
  19. Utilizar credenciales de conexión a la base de datos generadas por Vault en una aplicación de Spring
  20. Microservicios con Spring Cloud, Consul, Nomad y Traefik
  21. Trazabilidad en servicios distribuidos con Sleuth y Zipkin
  22. Configuración de una aplicación con Spring Boot y configuración centralizada con Spring Cloud Config
Comparte el artículo: