DEV Community

Cover image for Enhancing System Resilience with Circuit Breakers
Matheus Bernardes Spilari
Matheus Bernardes Spilari

Posted on

Enhancing System Resilience with Circuit Breakers

Distributed systems are powerful yet complex. A failure in one part of the system can quickly cascade, causing performance degradation or downtime. This is where Circuit Breakers come into play—a design pattern that enables your system to fail gracefully while providing time for recovery. In this post, we’ll explore Circuit Breakers, why they’re essential, and how to integrate them into your project.


What is a Circuit Breaker?

A Circuit Breaker is a software design pattern that monitors requests to external services and halts further requests when failures exceed a predefined threshold. The pattern takes inspiration from electrical circuit breakers: if the load becomes too heavy or a fault is detected, it trips, preventing further damage.

  • Open State: Stops all requests to the failing service.
  • Half-Open State: Allows a limited number of requests to test if the service has recovered.
  • Closed State: All requests pass through as normal.

Why Use Circuit Breakers?

  1. Prevent Overload: Protect your system from overloading dependencies that are slow or failing.
  2. Faster Failures: Instead of waiting for timeouts, circuit breakers fail fast, improving user experience.
  3. Graceful Degradation: Provide fallback responses to keep the system functional for users.

When to Use Circuit Breakers?

  • When your application depends on external services, APIs, or databases that may fail or slow down.
  • When transient errors (e.g., network issues) are common and retries might worsen the problem.
  • To prevent cascading failures in microservices-based architectures.

Implementing Circuit Breakers in Spring Boot

Let’s integrate Circuit Breakers into a Spring Boot project using Resilience4j, a lightweight library for fault tolerance.

1. Setting Up Resilience4j

Add these dependencies to your pom.xml:

<dependency>
   <groupId>io.github.resilience4j</groupId>
   <artifactId>resilience4j-spring-boot2</artifactId>
   <version>2.2.0</version>
</dependency>

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Explanation

1.1 Resilience4j Spring Boot 2 Starter

Purpose:

  • This is the core dependency for integrating Resilience4j with Spring Boot 2.x projects. It provides an out-of-the-box implementation of resilience patterns like Circuit Breaker, Retry, Rate Limiter, Time Limiter, and Bulkhead.

Functions:

  • Enables the use of annotations like @CircuitBreaker, @Retry, @RateLimiter, etc., in your Spring controllers and services.
  • Auto-configures Resilience4j beans for different resilience patterns based on your configuration in application.properties or application.yml.
  • Helps manage the circuit breaker lifecycle, metrics, and configuration in Spring's ecosystem.

1.2 Spring Boot Starter AOP

Purpose:

  • Provides support for Aspect-Oriented Programming (AOP) in Spring Boot. Resilience4j relies on AOP to intercept method calls and apply resilience patterns dynamically.

Functions:

  • Enables the use of aspects (@Aspect) and proxies to wrap your method calls with resilience logic.
  • Allows Resilience4j annotations (like @CircuitBreaker) to intercept methods without requiring manual changes to your service logic.
  • Provides the underlying mechanism for Resilience4j to "wrap" method calls with circuit breaker, retry, or rate limiter behaviors.

2. Adding Circuit Breaker to the Controller

Update your HelloWorld controller to include a Circuit Breaker with a fallback method.

@RestController
@RequestMapping("/hello-world")
public class HelloWorld {

    @GetMapping
    @CircuitBreaker(name = "helloWorldCircuitBreaker", fallbackMethod = "fallbackHelloWorld")
    public ResponseEntity<Map<String, String>> helloWorld() {
        // Simulating a potential failure
        if (Math.random() < 0.5) {
            throw new RuntimeException("Service Unavailable");
        }
        return ResponseEntity.ok().body(Map.of("message", "Hello world from: " + System.getenv("SERVER_NAME")));
    }

    public ResponseEntity<Map<String, String>> fallbackHelloWorld(Throwable t) {
        return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
            .body(Map.of("message",
                        "Fallback response: " + System.getenv("SERVER_NAME") + 
" service temporarily unavailable."));
    }
}
Enter fullscreen mode Exit fullscreen mode

Here’s what’s happening:

  • The @CircuitBreaker annotation wraps the endpoint, monitoring failures.
  • If the service fails or exceeds the threshold, the fallbackHelloWorld method returns a predefined response.

3. Configuring Circuit Breaker

In application.properties, configure Resilience4j:


# Circuit Breaker Configuration
resilience4j.circuitbreaker.instances.helloWorldCircuitBreaker.slidingWindowSize=5
resilience4j.circuitbreaker.instances.helloWorldCircuitBreaker.failureRateThreshold=30
resilience4j.circuitbreaker.instances.helloWorldCircuitBreaker.waitDurationInOpenState=5s
resilience4j.circuitbreaker.instances.helloWorldCircuitBreaker.permittedNumberOfCallsInHalfOpenState=2
Enter fullscreen mode Exit fullscreen mode
  • slidingWindowSize: The number of calls monitored for success or failure.
  • failureRateThreshold: Percentage of failed calls before tripping the breaker.
  • waitDurationInOpenState: Time before retrying the service.
  • permittedNumberOfCallsInHalfOpenState: Requests allowed in the half-open state.

4. Testing the Circuit Breaker

Simulate high traffic or failures to test the Circuit Breaker:

  1. Send 50 requests using curl:
   repeat 50 curl -I http://localhost:8080/hello-world
Enter fullscreen mode Exit fullscreen mode
  1. The possible status codes you might encounter are:
    • 200: OK
    • 429: Too Many Requests (due to Rate Limiter)
    • 503: Service Unavailable (due to Circuit Breaker)

Conclusion

Circuit Breakers are an essential tool for building robust and resilient distributed systems. By integrating Resilience4j in Spring Boot and combining it with NGINX, you ensure fault tolerance at both the application and infrastructure levels. As you scale your system, concepts like Circuit Breakers, retries, and rate limiting will become indispensable.

Start building resilient systems today by implementing Circuit Breakers in your projects!


📍 Reference

💻 Project Repository

👋 Talk to me

Top comments (0)