Dung (Donny) Nguyen

Senior Software Engineer

Circuit Breaker Pattern

The Circuit Breaker Pattern is a design pattern used in distributed systems to handle potential failures in a resilient way. It prevents cascading failures by temporarily stopping the flow of requests to a service when it is likely to fail, and allows the system to recover gracefully. This pattern is commonly used in microservice architectures to maintain system stability and ensure fault tolerance.

How It Works:

The circuit breaker acts like an electrical circuit breaker. When a service call fails repeatedly, the circuit “opens” blocking further attempts to contact the failing service. Over time, the circuit “half-opens” allowing a few requests to pass through to test if the service has recovered. If the service responds successfully, the circuit “closes” and normal traffic resumes.

States in Circuit Breaker Pattern:

  1. Closed:
    • The service is functioning normally.
    • All requests are allowed to pass through to the service.
    • If a certain threshold of failures is reached, the circuit breaker transitions to the “Open” state.
  2. Open:
    • The service has encountered repeated failures.
    • Requests are blocked from reaching the service, and an error or fallback is immediately returned.
    • The circuit breaker stays open for a predefined period (cool-down period).
  3. Half-Open:
    • After the cool-down period, the circuit breaker allows a limited number of test requests to pass through to check if the service has recovered.
    • If the service responses are successful, the circuit breaker transitions back to “Closed.”
    • If failures continue, the circuit goes back to “Open.”

Benefits:

Common Scenarios:

Example:

Java Core Application

Here’s a basic example of implementing a circuit breaker pattern in Java without any external libraries or frameworks.

import java.time.Duration;
import java.time.Instant;

public class CircuitBreaker {
    private enum State {
        CLOSED, OPEN, HALF_OPEN
    }
    
    private State state = State.CLOSED;
    private int failureCount = 0;
    private final int failureThreshold;
    private final Duration cooldownDuration;
    private Instant lastFailureTime;
    private final Duration halfOpenTestInterval;
    
    public CircuitBreaker(int failureThreshold, Duration cooldownDuration, Duration halfOpenTestInterval) {
        this.failureThreshold = failureThreshold;
        this.cooldownDuration = cooldownDuration;
        this.halfOpenTestInterval = halfOpenTestInterval;
    }
    
    public synchronized boolean allowRequest() {
        switch (state) {
            case OPEN:
                if (Duration.between(lastFailureTime, Instant.now()).compareTo(cooldownDuration) >= 0) {
                    state = State.HALF_OPEN;
                    return true;  // Allow a test request in HALF_OPEN state
                }
                return false;
            case HALF_OPEN:
                return Duration.between(lastFailureTime, Instant.now()).compareTo(halfOpenTestInterval) >= 0;
            default:
                return true;
        }
    }
    
    public synchronized void onSuccess() {
        if (state == State.HALF_OPEN || state == State.OPEN) {
            reset();
        }
    }
    
    public synchronized void onFailure() {
        failureCount++;
        lastFailureTime = Instant.now();
        if (failureCount >= failureThreshold) {
            state = State.OPEN;
        }
    }
    
    private void reset() {
        state = State.CLOSED;
        failureCount = 0;
    }
}

Here’s a simple service call example using this circuit breaker:

public class ServiceCaller {
    private final CircuitBreaker circuitBreaker;

    public ServiceCaller(CircuitBreaker circuitBreaker) {
        this.circuitBreaker = circuitBreaker;
    }

    public void callService() {
        if (!circuitBreaker.allowRequest()) {
            System.out.println("Service call blocked by Circuit Breaker");
            return;
        }

        try {
            // Simulate a service call
            boolean serviceResult = simulateServiceCall();
            if (serviceResult) {
                System.out.println("Service call succeeded");
                circuitBreaker.onSuccess();
            } else {
                System.out.println("Service call failed");
                circuitBreaker.onFailure();
            }
        } catch (Exception e) {
            System.out.println("Service call failed with exception: " + e.getMessage());
            circuitBreaker.onFailure();
        }
    }

    private boolean simulateServiceCall() {
        // Simulate a random success or failure
        return Math.random() > 0.5;
    }

    public static void main(String[] args) {
        CircuitBreaker circuitBreaker = new CircuitBreaker(3, Duration.ofSeconds(10), Duration.ofSeconds(5));
        ServiceCaller caller = new ServiceCaller(circuitBreaker);

        for (int i = 0; i < 20; i++) {
            caller.callService();
            try {
                Thread.sleep(1000);  // Wait 1 second between calls
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

Explanation

  1. Circuit Breaker Logic:
    • The allowRequest method decides whether to allow a request based on the current state.
    • If the circuit is Open and the cooldown period has passed, it transitions to Half-Open and allows a test request.
    • If Half-Open and the test interval has passed, it allows a test request.
  2. Service Call Simulation:
    • In the ServiceCaller class, simulateServiceCall randomly returns success or failure, simulating a real service call.
    • On success, circuitBreaker.onSuccess() is called to reset the circuit.
    • On failure, circuitBreaker.onFailure() increments the failure count and, if thresholds are exceeded, transitions the breaker to Open.

This code provides a simple circuit breaker pattern that can be extended for real-world applications by adding retries, error logging, and adjusting thresholds based on our service’s needs.

Using Spring Application and Resilience4j

To implement the Circuit Breaker pattern in Spring Cloud using Resilience4j, we’ll need to configure dependencies and annotations to handle fallback mechanisms, thresholds, and states. Here’s a step-by-step guide on how to set it up:

1. Add Dependencies

To use Resilience4j in a Spring Boot project, add the following dependencies to our pom.xml file:

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot2</artifactId>
    <version>2.0.2</version> <!-- Use the latest stable version -->
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2. Configure Resilience4j in application.yml

Define circuit breaker configurations, such as failure rate threshold, wait duration, and permitted calls during the half-open state:

resilience4j:
  circuitbreaker:
    instances:
      myService:
        registerHealthIndicator: true
        slidingWindowSize: 10
        failureRateThreshold: 50
        waitDurationInOpenState: 10000ms # 10 seconds
        permittedNumberOfCallsInHalfOpenState: 5
        slidingWindowType: COUNT_BASED
        minimumNumberOfCalls: 5

3. Implementing the Circuit Breaker in Code

Use the @CircuitBreaker annotation to apply the circuit breaker logic to a specific method, such as a service call. Specify the circuit breaker name and a fallback method to be used if the circuit is open.

Example Service Class
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import org.springframework.stereotype.Service;

@Service
public class MyService {

    @CircuitBreaker(name = "myService", fallbackMethod = "fallbackMethod")
    public String callExternalService() {
        // Code to call an external service that might fail
        // For example, using RestTemplate or WebClient to make HTTP requests
        return restTemplate.getForObject("https://unstable-service.com/api/resource", String.class);
    }

    public String fallbackMethod(Throwable throwable) {
        // Fallback response logic
        return "Service is currently unavailable, please try again later.";
    }
}

In this example:

4. Testing the Circuit Breaker

To see the circuit breaker in action:

5. Additional Configurations (Optional)

Resilience4j also allows configuring other resilience patterns, such as retry, rate limiter, and bulkhead. Here’s an example of how we might add a retry mechanism in conjunction with the circuit breaker:

resilience4j:
  retry:
    instances:
      myService:
        maxAttempts: 3
        waitDuration: 500ms

Then, use the @Retry annotation alongside @CircuitBreaker:

import io.github.resilience4j.retry.annotation.Retry;

@Service
public class MyService {

    @CircuitBreaker(name = "myService", fallbackMethod = "fallbackMethod")
    @Retry(name = "myService")
    public String callExternalService() {
        // Call the external service
    }

    public String fallbackMethod(Throwable throwable) {
        return "Service is unavailable. Please try again later.";
    }
}

6. Monitoring the Circuit Breaker

We can monitor the circuit breaker status via health indicators or integration with tools like Spring Boot Actuator and Micrometer. Add the Actuator dependency to pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

This provides endpoints like /actuator/health to check the circuit breaker’s status and other metrics.

Summary

Using Resilience4j with Spring Cloud allows us to easily configure and manage the Circuit Breaker pattern to improve fault tolerance in microservices. With this setup, our service is better equipped to handle failures gracefully, preventing cascading failures in our microservices architecture.