Implementar tolerancia a fallos con Resilience4j

Escrito por picodotdev el .
java planeta-codigo
Enlace permanente Comentarios

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.

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. Servicio de configuración para microservicios con Spring Cloud Config
  9. Recargar sin reiniciar la configuración de una aplicación Spring Boot con Spring Cloud Config
  10. Almacenar cifrados los valores de configuración sensibles en Spring Cloud Config
  11. Tolerancia a fallos en un cliente de microservicio con Spring Cloud Netflix y Hystrix
  12. Balanceo de carga y resilencia en un microservicio con Spring Cloud Netflix y Ribbon
  13. Proxy para microservicios con Spring Cloud Netflix y Zuul
  14. Monitorizar una aplicación Java de Spring Boot con Micrometer, Prometheus y Grafana
  15. Exponer las métricas de Hystrix en Grafana con Prometheus de una aplicación Spring Boot
  16. Servidor OAuth, gateway y servicio REST utilizando tokens JWT con Spring
  17. Trazabilidad en microservicios con Spring Cloud Sleuth
  18. Implementar tolerancia a fallos con Resilience4j
  19. Iniciar una aplicación de Spring Boot en un puerto aleatorio
  20. Utilizar credenciales de conexión a la base de datos generadas por Vault en una aplicación de Spring
  21. Microservicios con Spring Cloud, Consul, Nomad y Traefik
Comparte el artículo: