Implementar tolerancia a fallos con Resilience4j

Escrito por el .
java planeta-codigo
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";
    }
}

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')

    ...
}

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 el comando ./gradle-run.sh.