Failsafe Circuit Breaker Policy

September 14, 2020 No comments Failsafe Java library Circuit-Breaker Policy

1. Introduction

In this article, we are going to present the Circuit Breaker policy from the Failsafe library. Circuit breakers allow us to create an application that fails by temporarily disabling execution as a way of preventing system overload.

2. How it works

Circuit breakers have three states:

  • closed,
  • open,
  • half-open.

In close state executions are allowed. If a configurable number of failures occur, the circuit breaker changed to the open state. In the open state, a circuit breaker will fail executions with CircuitBreakerOpenException. After a delay (configurable), the circuit breaker will transition to a half-open state. In this state, a configurable number of trial executions will be allowed. After that, the circuit breaker will transition to a closed or open state.

There are two types of circuit breaker:

  • count based,
  • time based.

A count-based circuit breaker will transition between states when recent execution results exceed a threshold. A time-based circuit breaker will transition between states when recent execution results exceed a threshold within a time. A minimum number of executions must be performed for a state transition to occur.

Time-based circuit breakers use a sliding window to aggregate execution results. The window is divided into 10-time slices, each representing 1/10th of the failureThresholdingPeriod. As time progresses, statistics for old time slices are gradually discarded, which smoothes the calculation of success and failure rates.

3. Configuration

Circuit breakers can be easily configured to express when the breaker should be opened, half-opened, and closed.

3.1. Opening

A count-based circuit breaker can be configured to open when a successive number of executions have failed:

breaker.withFailureThreshold(5);

Or when, for example, 3 out of the last 5 executions have failed:

breaker.withFailureThreshold(3, 5);

A time-based circuit breaker can be configured to open when several failures occur within a period:

breaker.withFailureThreshold(3, Duration.ofMinutes(1));

Or when some failures occur out of a minimum number of executions within a period:

breaker.withFailureThreshold(3, 5, Duration.ofMinutes(1));

It can also be configured to open when the percentage rate of failures out of a minimum number of executions exceeds a threshold:

breaker.withFailureRateThreshold(20, 5, Duration.ofMinutes(1));

3.2. Half-opening

After opening, a breaker will delay for 1 minute by default before transitioning to half-open. We can configure a different delay:

breaker.withDelay(Duration.ofSeconds(30));

3.3. Closing

The breaker can be configured to close again if many trial executions succeed, else it will re-open:

breaker.withSuccessThreshold(5);

The breaker can also be configured to close again if, for example, 3 out of the last 5 executions succeed, else it will re-open:

breaker.withSuccessThreshold(3, 5);

If a success threshold is not configured, then the failure threshold is used to determine if a breaker should transition from half-open to either closed or open.

4. Event Listeners

The CircuitBreaker can notify you when the state of the breaker changes:

circuitBreaker.onOpen(() -> log.info("The circuit breaker was opened"))
              .onClose(() -> log.info("The circuit breaker was closed")) 
              .onHalfOpen(() -> log.info("The circuit breaker was half-opened"));

5. JUnit test

This is a simple JUnit class that tests CircuitBreaker and Retry policies.

package com.frontbackend.libraries.failsafe;

import static org.mockito.Mockito.when;

import java.net.ConnectException;
import java.time.Duration;
import java.util.Date;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import net.jodah.failsafe.CircuitBreaker;
import net.jodah.failsafe.CircuitBreakerOpenException;
import net.jodah.failsafe.Failsafe;
import net.jodah.failsafe.RetryPolicy;

@RunWith(MockitoJUnitRunner.class)
public class CircuitBreakerTest {

    @Mock
    DatabaseConnection connection = new DatabaseConnection();

    @Test(expected = CircuitBreakerOpenException.class)
    public void testCircuitBreakerPolicy() throws ConnectException {
        // given
        RetryPolicy<Object> retryPolicy = new RetryPolicy<>().handle(ConnectException.class)
                                                             .withDelay(Duration.ofSeconds(1))
                                                             .withMaxRetries(5)
                                                             .onFailedAttempt(
                                                                     e -> log(String.format("Connection attempt failed %s", e.getLastFailure())))
                                                             .onRetry(e -> log(String.format("Failure #%d. Retrying.", e.getAttemptCount())))
                                                             .onRetriesExceeded(e -> log("Failed to connect. Max retries exceeded."))
                                                             .onAbort(e -> log(String.format("Connection aborted due to %s.", e.getFailure())));

        CircuitBreaker<Object> breaker = new CircuitBreaker<>().handle(ConnectException.class)
                                                               .withFailureThreshold(2)
                                                               .withSuccessThreshold(1)
                                                               .withDelay(Duration.ofMinutes(1))
                                                               .onOpen(() -> log("The circuit breaker was opened"))
                                                               .onClose(() -> log("The circuit breaker was closed"))
                                                               .onHalfOpen(() -> log("The circuit breaker was half-opened"));
        // when
        when(connection.connect()).thenThrow(ConnectException.class)
                                  .thenThrow(ConnectException.class)
                                  .thenReturn(connection);

        Failsafe.with(retryPolicy, breaker)
                .run(connection::connect);

        // then
    }

    private void log(String log) {
        System.out.printf("%s : %s%n", new Date(), log);
    }
}

The output:

Tue Sep 15 01:23:34 CEST 2020 : Connection attempt failed java.net.ConnectException
Tue Sep 15 01:23:35 CEST 2020 : Failure #1. Retrying.
Tue Sep 15 01:23:35 CEST 2020 : The circuit breaker was opened
Tue Sep 15 01:23:35 CEST 2020 : Connection attempt failed java.net.ConnectException
Tue Sep 15 01:23:36 CEST 2020 : Failure #2. Retrying.

5. Conclusion

In this article, we presented how to use the Circuit Breaker policy in the Failsafe library. Circuit breakers allow us to prevent attacks on our server by disabling execution.

{{ message }}

{{ 'Comments are closed.' | trans }}