[17.0.0.3 and later]

Mejora de la resiliencia del microservicio en Liberty

Puede utilizar la característica de tolerancia al error de MicroProfile para hacer que la invocación de servicio sea más resistente. Esta característica es una implementación de la especificación de tolerancia al error de Microprofile de Eclipse 1.0. La característica utiliza la biblioteca de código abierto Failsafe para proporcionar un modelo de programación para dar soporte a microservicios resistentes a través de patrones que incluyen reintentos, interruptores, barreras aislantes, tiempos de espera y recuperaciones.

Antes de empezar

Si desea más información sobre la especificación MicroProfile de código abierto que se implementa mediante la característica de tolerancia al error de MicroProfile, consulte Especificación de tolerancia al error de Microprofile de Eclipse 1.0.

Procedimiento

  1. Añada la característica mpFaultTolerance-1.0 a un elemento featureManager en el archivo server.xml.
    <featureManager>
       <feature>mpFaultTolerance-1.0</feature>
    </featureManager>
  2. Utilice un fragmento de código para mejorar la resiliencia del microservicio.
    Un interruptor de tolerancia al error proporciona una forma de notificar anomalías rápidamente para los sistemas. Inhabilita temporalmente la ejecución de un servicio para evitar que el servicio sobrecargue un sistema.
    Una política RetryPolicy de tolerancia al error proporciona una forma para configurar cuándo reintentar servicios. Si el servicio principal falla, una reserva de tolerancia al error puede especificar un servicio para utilizar.
    Una barrera aislante de tolerancia al error limita el número de llamadas simultáneas a un servicio. La barrera aislante limita la cantidad de recursos del sistema que pueden utilizar invocaciones de servicio. El fragmento de código requiere que la característica Liberty concurrent-1.0 se especifique en el archivo server.xml además de en la característica mpFaultTolerance-1.0.

    Fragmento de código de interruptor 1: Crear un CircuitBreakerBean con CircuitBreaker y Timeout configurados.

    @RequestScoped
    public class CircuitBreakerBean {
    
        private int executionCounterA = 0;
    
        // El efecto combinado de requestVolumeThreshold y failureRatio especificados es que 3  
        // anomalías desencadenarán que se abra el circuito.
        // Después de un retardo de 1 segundo, el circuito permitirá nuevos intentos para invocar el servicio.
        @CircuitBreaker(delay = 1, delayUnit = ChronoUnit.SECONDS, requestVolumeThreshold = 3, failureRatio = 1.0)
        // Se considera que un servicio ha agotado el tiempo de espera después de 3 segundos
        @Timeout(value = 3, unit = ChronoUnit.SECONDS)
        public String serviceA() {
            executionCounterA++;
    
            if (executionCounterA <= 3) {
                //Inactividad durante 10 segundos para forzar un tiempo de espera
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    System.out.println("serviceA interrupted");
                }
            }

    Fragmento de código de interruptor 2: Utilización de CircuitBreakerBean.

    @Inject
    CircuitBreakerBean bean;
    
    // El bean FaultTolerance con interruptor debe fallar 3 veces
    for (int i = 0; i < 3; i++) {
        try {
            bean.serviceA();
            throw new AssertionError("TimeoutException not caught");
        } catch (TimeoutException e) {
                //se espera
        }
    }
    
    // El interruptor debe estar abierto, así que la llamada a serviceA debe generar un 
    // CircuitBreakerOpenException.
    try {
        bean.serviceA();
        throw new AssertionError("CircuitBreakerOpenException not caught");
    } catch (CircuitBreakerOpenException e) {
        //se espera
    }
    
    //permitir tiempo para que se vuelva a cerrar el circuito
    Thread.sleep(3000);
    
    // El interruptor se debe cerrar y ahora el serviceA debería realizarse correctamente.
    String res = bean.serviceA();
    if (!"serviceA: 4".equals(res)) {
        throw new AssertionError("Bad Result: " + res);
    }

    Fragmento de código de reserva y reintento 1: FTServiceBean con FallbackHandler y la política de reintento configurados

    @RequestScoped
    public class FTServiceBean {
    
        // Anote el serviceA con un FallbackHandler especificado y una política de reintento que especifica el
        // número de reintentos.
        @Retry(maxRetries = 2)
        @Fallback(StringFallbackHandler.class)
        public String serviceA() {
            throw new RuntimeException("Connection failed");
            return null;
        }   
    }

    Fragmento de código de reserva y reintento 2: Un FallbackHandler, el código que es controlado si falla el servicio principal.

    @Dependent
    public class StringFallbackHandler implements FallbackHandler<String> {
    
        @Override
        public String handle(ExecutionContext context) {
            return "fallback for " + context.getMethod().getName();
        }
    }

    Fragmento de código de reserva y reintento 3: Utilización de FTServiceBean.

    private @Inject FTServiceBean ftServiceBean;
        
    try {
        // Llamar a serviceA, que se reintentará dos veces en el caso de anomalía, después de lo cual
        // se dirigirá FallbackHandler.
        String result = ftServiceBean.serviceA();
        if(!result.contains("serviceA"))
           throw new AssertionError("The message should be \"fallback for serviceA\"");
     }
    catch(RuntimeException ex) {
        throw new AssertionError("serviceA should not throw a RuntimeException");
    }

    Fragmento de código de barrera aislante 1: Crear un BulkheadBean con barrera aislante configurada.

    @RequestScoped
    @Asynchronous
    public class BulkheadBean {
    
        private final AtomicInteger connectATokens = new AtomicInteger(0);
    
        // Configurar una barrera aislante que admite como máximo 2 hebras simultáneas.
        @Bulkhead(maxThreads = 2)
        public Future<Boolean> connectA(String data) throws InterruptedException {
            System.out.println("connectA starting " + data);
            int token = connectATokens.incrementAndGet();
            try {
                if (token > 2) {
                    throw new RuntimeException("Too many threads in connectA[" + data + "]: " + token);
                }
                Thread.sleep(5000);
                return CompletableFuture.completedFuture(Boolean.TRUE);
            } finally {
                connectATokens.decrementAndGet();
                System.out.println("connectA complete " + data);
            }
        }
    }

    Fragmento de código de barrera aislante 2: Utilizar BulkheadBean.

    @Inject
    BulkheadBean bean;
    
    // connectA tiene un tamaño de agrupación de 2
    // Las dos primeras llamadas a connectA se deben ejecutar inmediatamente, en paralelo, cada
    // 5 segundos
    Future<Boolean> future1 = bean.connectA("One");
    Thread.sleep(100);
    Future<Boolean> future2 = bean.connectA("Two");
    Thread.sleep(100);
    
    // Las siguientes dos llamadas a connectA deben esperar hasta que hayan finalizado las primeras 2
    Future<Boolean> future3 = bean.connectA("Three");
    Thread.sleep(100);
    Future<Boolean> future4 = bean.connectA("Four");
    Thread.sleep(100);
    
    //el tiempo total debe ser poco más de 10s
    Thread.sleep(11000);
    
    if (!future1.get(1000, TimeUnit.MILLISECONDS)) {
        throw new AssertionError("Future1 did not complete properly");
    }
    if (!future2.get(1000, TimeUnit.MILLISECONDS)) {
        throw new AssertionError("Future2 did not complete properly");
    }
    if (!future3.get(1000, TimeUnit.MILLISECONDS)) {
        throw new AssertionError("Future3 did not complete properly");
    }
    if (!future4.get(1000, TimeUnit.MILLISECONDS)) {
        throw new AssertionError("Future4 did not complete properly");
    }

Icono que indica el tipo de tema Tema de tarea

Nombre de archivo: twlp_microprofile_fault_tolerance.html