Spring Cloud Circuit Breaker
Introduction
In a distributed microservices architecture, services often depend on each other for successful operation. However, these dependencies can become points of failure when services are unavailable or experience high latency. This is where the Circuit Breaker pattern comes in - a design pattern that prevents cascading failures and improves system resilience.
Spring Cloud Circuit Breaker provides a unified API for various circuit breaker implementations, allowing developers to add fault tolerance to their applications without tying their code to a specific library.
What is a Circuit Breaker?
The Circuit Breaker pattern is inspired by electrical circuit breakers. In electrical systems, circuit breakers protect the system by breaking the circuit when a fault is detected. Similarly, in software:
- Closed State: Circuit is closed, and requests flow normally to the dependent service
- Open State: Circuit is open, and requests fail fast without attempting to call the dependent service
- Half-Open State: After a timeout period, the circuit goes into half-open state where a limited number of requests can pass through to test if the dependent service has recovered
Spring Cloud Circuit Breaker Abstractions
Spring Cloud Circuit Breaker provides a vendor-neutral abstraction for implementing circuit breakers in your applications. It supports multiple implementations:
- Resilience4J (recommended default)
- Netflix Hystrix (deprecated)
- Sentinel
- Spring Retry
In this guide, we'll focus on Resilience4J as the implementation.
Getting Started with Spring Cloud Circuit Breaker
Adding Dependencies
First, add the required dependencies to your pom.xml
file:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
For Spring Boot projects, make sure you have the proper Spring Cloud version:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Creating a Basic Circuit Breaker
Here's a simple example of how to use the Spring Cloud Circuit Breaker abstraction with Resilience4J:
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ServiceController {
private final RestTemplate restTemplate;
private final CircuitBreakerFactory circuitBreakerFactory;
public ServiceController(RestTemplate restTemplate,
CircuitBreakerFactory circuitBreakerFactory) {
this.restTemplate = restTemplate;
this.circuitBreakerFactory = circuitBreakerFactory;
}
@GetMapping("/product/{id}")
public Product getProduct(@PathVariable("id") Long id) {
return circuitBreakerFactory.create("productService")
.run(() -> restTemplate.getForObject("/api/products/" + id, Product.class),
throwable -> getProductFallback(id, throwable));
}
private Product getProductFallback(Long id, Throwable throwable) {
System.out.println("Fallback executed for product ID: " + id +
", error: " + throwable.getMessage());
return new Product(id, "Fallback Product", 0.0);
}
}
In this example:
- We inject the
CircuitBreakerFactory
into our controller - We create a circuit breaker named "productService"
- The
.run()
method executes our code (calling a product service) within a circuit breaker context - We provide a fallback method that will be called if the circuit is open or an error occurs
Configuring Resilience4J Circuit Breakers
Spring Cloud Circuit Breaker allows you to customize the behavior of your circuit breakers. Here's how to configure Resilience4J circuit breakers globally and for specific instances:
Global Configuration
Create a configuration class to customize the default behavior of all circuit breakers:
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.timelimiter.TimeLimiterConfig;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JCircuitBreakerFactory;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JConfigBuilder;
import org.springframework.cloud.client.circuitbreaker.Customizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.Duration;
@Configuration
public class CircuitBreakerConfiguration {
@Bean
public Customizer<Resilience4JCircuitBreakerFactory> defaultCustomizer() {
return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
.timeLimiterConfig(TimeLimiterConfig.custom()
.timeoutDuration(Duration.ofSeconds(4))
.build())
.circuitBreakerConfig(CircuitBreakerConfig.custom()
.slidingWindowSize(10)
.failureRateThreshold(50)
.waitDurationInOpenState(Duration.ofMillis(1000))
.permittedNumberOfCallsInHalfOpenState(2)
.slowCallRateThreshold(50)
.slowCallDurationThreshold(Duration.ofSeconds(2))
.build())
.build());
}
}
This configuration:
- Sets a timeout duration of 4 seconds for requests
- Configures the circuit breaker to use a sliding window of 10 requests
- Opens the circuit when the failure rate is 50% or higher
- Keeps the circuit open for 1 second before transitioning to half-open
- Allows 2 calls when in the half-open state
- Considers calls taking over 2 seconds as "slow calls" and opens the circuit if 50% of calls are slow
Specific Instance Configuration
You can also customize configuration for specific circuit breakers:
@Bean
public Customizer<Resilience4JCircuitBreakerFactory> productServiceCustomizer() {
return factory -> factory.configure(builder -> builder
.circuitBreakerConfig(CircuitBreakerConfig.custom()
.slidingWindowSize(5)
.failureRateThreshold(20)
.build())
.timeLimiterConfig(TimeLimiterConfig.custom()
.timeoutDuration(Duration.ofSeconds(2))
.build()), "productService");
}
This creates a specific configuration for the "productService" circuit breaker with stricter parameters.
Practical Example: Building a Resilient Product Service
Let's create a more complete example of a product service that calls an inventory service with circuit breaker protection.
1. Define the Service Classes
// Product model
public class Product {
private Long id;
private String name;
private double price;
private boolean inStock;
// constructors, getters and setters
}
// Inventory service client
@Service
public class InventoryServiceClient {
private final RestTemplate restTemplate;
private final CircuitBreakerFactory circuitBreakerFactory;
public InventoryServiceClient(RestTemplate restTemplate,
CircuitBreakerFactory circuitBreakerFactory) {
this.restTemplate = restTemplate;
this.circuitBreakerFactory = circuitBreakerFactory;
}
public boolean isInStock(Long productId) {
return circuitBreakerFactory.create("inventoryService")
.run(() -> {
Boolean result = restTemplate.getForObject(
"http://inventory-service/api/stock/{id}",
Boolean.class,
productId
);
return result != null && result;
}, throwable -> {
System.err.println("Error checking inventory: " + throwable.getMessage());
return false; // Fallback: assume not in stock when service is unavailable
});
}
}
2. Implement the Product Controller
@RestController
@RequestMapping("/api/products")
public class ProductController {
private final ProductRepository productRepository;
private final InventoryServiceClient inventoryClient;
public ProductController(ProductRepository productRepository,
InventoryServiceClient inventoryClient) {
this.productRepository = productRepository;
this.inventoryClient = inventoryClient;
}
@GetMapping("/{id}")
public ResponseEntity<Product> getProductWithAvailability(@PathVariable Long id) {
return productRepository.findById(id)
.map(product -> {
// Enrich product with inventory information using circuit breaker
product.setInStock(inventoryClient.isInStock(id));
return ResponseEntity.ok(product);
})
.orElse(ResponseEntity.notFound().build());
}
}
3. Customize Circuit Breaker Configuration
@Configuration
public class CircuitBreakerConfig {
@Bean
public Customizer<Resilience4JCircuitBreakerFactory> inventoryServiceCustomizer() {
return factory -> factory.configure(builder -> builder
.circuitBreakerConfig(CircuitBreakerConfig.ofDefaults())
.timeLimiterConfig(TimeLimiterConfig.custom()
.timeoutDuration(Duration.ofMillis(500)) // Fast timeout for inventory checks
.build()), "inventoryService");
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
Advanced Features
Circuit Breaker Events
You can listen to circuit breaker events to monitor and log state changes:
@Service
public class CircuitBreakerEventsService {
public CircuitBreakerEventsService(Resilience4JCircuitBreakerFactory circuitBreakerFactory) {
circuitBreakerFactory.configureCircuitBreakerRegistry(circuitBreakerRegistry -> {
CircuitBreaker inventoryCircuitBreaker = circuitBreakerRegistry.circuitBreaker("inventoryService");
inventoryCircuitBreaker.getEventPublisher()
.onStateTransition(event -> {
System.out.println("Circuit state changed: " + event.getStateTransition());
})
.onSuccess(event -> {
System.out.println("Call succeeded");
})
.onError(event -> {
System.err.println("Call failed: " + event.getThrowable().getMessage());
});
});
}
}
Bulkhead Pattern with Circuit Breakers
You can combine circuit breakers with bulkheads to limit concurrent calls:
@Bean
public Customizer<Resilience4JCircuitBreakerFactory> customizer() {
return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
.circuitBreakerConfig(CircuitBreakerConfig.ofDefaults())
.bulkheadConfig(BulkheadConfig.custom()
.maxConcurrentCalls(4)
.maxWaitDuration(Duration.ofMillis(200))
.build())
.build());
}
Rate Limiter Integration
You can also integrate rate limiting with circuit breakers:
@Bean
public Customizer<Resilience4JCircuitBreakerFactory> rateLimiterCustomizer() {
return factory -> factory.configure(builder -> builder
.circuitBreakerConfig(CircuitBreakerConfig.ofDefaults())
.rateLimiterConfig(RateLimiterConfig.custom()
.limitRefreshPeriod(Duration.ofSeconds(1))
.limitForPeriod(10) // Allow 10 calls per second
.timeoutDuration(Duration.ofMillis(100))
.build()), "rateLimitedService");
}
Circuit Breaker in Action: How It Works
Let's understand how the circuit breaker pattern works in practice with a real-world scenario:
-
Normal Operation (Closed State):
- Your application makes calls to a dependent service
- All calls are successful and return within expected time
- The circuit breaker remains closed, allowing all calls through
-
Service Degradation:
- The dependent service starts experiencing problems
- Calls begin to fail or take too long to complete
- The circuit breaker tracks these failures
-
Circuit Opens:
- Once failures reach the configured threshold (e.g., 50% failure rate)
- The circuit breaker transitions to the open state
- All subsequent calls fail fast, returning the fallback response
- This prevents overwhelming the failing service and allows it time to recover
-
Recovery Period:
- After a configured wait time (e.g., 5 seconds), the circuit enters half-open state
- A limited number of calls are allowed through to test if the service has recovered
- If these test calls succeed, the circuit closes and normal operation resumes
- If the test calls fail, the circuit opens again and the recovery period restarts
Summary
Spring Cloud Circuit Breaker provides a powerful abstraction for implementing fault tolerance in your microservices architecture. By using circuit breakers, your applications can gracefully handle failures in dependent services, prevent cascading failures, and improve overall system resilience.
Key takeaways:
- Circuit breakers help isolate failing services in a microservices architecture
- Spring Cloud Circuit Breaker provides a unified API across different circuit breaker implementations
- Resilience4J is the recommended implementation for new Spring Cloud applications
- Circuit breakers can be customized with parameters like failure thresholds, timeouts, and window sizes
- Fallbacks provide alternative responses when a dependent service is unavailable
- The pattern follows three states: closed, open, and half-open
Additional Resources
- Spring Cloud Circuit Breaker Documentation
- Resilience4J Documentation
- Martin Fowler's Circuit Breaker Pattern
Exercises
- Create a simple microservice that uses a circuit breaker to call an external API, with a meaningful fallback mechanism.
- Implement custom circuit breaker configurations for different downstream services based on their reliability requirements.
- Add circuit breaker events logging to track state changes and create a simple dashboard.
- Combine circuit breakers with retry mechanisms for temporary failures in downstream services.
- Implement a bulkhead pattern along with circuit breakers to limit the impact of slow services.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)