DEV Community

Somenath Das
Somenath Das

Posted on

Why We Use circuit breaker and How Do we Implements this.

The Circuit Breaker pattern is a crucial design pattern for ensuring system stability and fault tolerance in a microservices architecture. Below is a comprehensive overview of the Circuit Breaker pattern and its implementation in Spring Boot using Resilience4j.

What is a Circuit Breaker?

In a microservices environment, if a specific service goes down or becomes slow, the Circuit Breaker temporarily halts calls to that service. This prevents the entire system from crashing or suffering from a cascading failure.

The 3 main states of a Circuit Breaker:

  1. Closed: This is the normal operating state. All requests pass successfully from one service to another.
  2. Open: If a service fails repeatedly or exceeds a specific threshold, the circuit trips to the 'Open' state. Requests are no longer sent to the main service; instead, an alternative or fallback response is returned immediately.
  3. Half-Open: After a set period, the circuit automatically transitions to the 'Half-Open' state. In this state, a limited number of requests are sent to verify whether the remote service has recovered. If they succeed, the circuit returns to the 'Closed' state; if they fail, it reverts to the 'Open' state.

Circuit Breaker Implementation in Spring Boot

Step 1: Adding Dependencies

Spring Boot project's pom.xml

<!-- Resilience4j Spring Boot Starter --> io.github.resilience4j
resilience4j-spring-boot3
<!-- For Spring Boot 3 -->
2.1.0


Application.property file configuration.

Resilience4j Circuit Breaker configuration for userServiceBreaker

resilience4j.circuitbreaker.instances.userServiceBreaker.registerHealthIndicator=true

resilience4j.circuitbreaker.instances.userServiceBreaker.slidingWindowType=COUNT_BASED

resilience4j.circuitbreaker.instances.userServiceBreaker.slidingWindowSize=10

resilience4j.circuitbreaker.instances.userServiceBreaker.failureRateThreshold=50

resilience4j.circuitbreaker.instances.userServiceBreaker.minimumNumberOfCalls=5

resilience4j.circuitbreaker.instances.userServiceBreaker.automaticTransitionFromOpenToHalfOpenEnabled=true

resilience4j.circuitbreaker.instances.userServiceBreaker.waitDurationInOpenState=10s

resilience4j.circuitbreaker.instances.userServiceBreaker.permittedNumberOfCallsInHalfOpenState=3


Code Implementation (Service & Controller):

In Service Class:--------------


We will use the @CircuitBreaker annotation and create a fallback method.

package com.example.service;

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate; @Service
public class OrderService {

private final RestTemplate restTemplate = new RestTemplate();
private static final String USER_SERVICE_URL = "http://localhost:8081/users/";

// The name here must match the instance name defined in application.yml
@CircuitBreaker(name = "userServiceBreaker", fallbackMethod = "getUserFallback")
public String getUserDetails(Long userId) {
// Assume this calls another microservice that might be down
return restTemplate.getForObject(USER_SERVICE_URL + userId, String.class);
}

// The fallback method signature (parameters and return type) must match the original method,
// with the addition of a Throwable or Exception parameter.
public String getUserFallback(Long userId, Throwable throwable) {
return "Sorry, the user service is currently down. Showing fallback data. Reason: " + throwable.getMessage();
}
}


In the Controller class:

package com.example.controller;

import com.example.service.OrderService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class OrderController {

private final OrderService orderService;

public OrderController(OrderService orderService) {
this.orderService = orderService;
}

@GetMapping("/order/user/{id}")
public String getOrderUser(@PathVariable Long id) {
return orderService.getUserDetails(id);
}
}



Testing:----


  1. Normal State (Closed): When the user service (port: 8081) is active, the request will directly fetch the data.
  2. Service Down (Open): If the user service is down, errors will occur during the initial attempts. As soon as the failure rate exceeds 50% within 10 requests, the circuit breaker will trip and switch to the 'Open' state. Subsequently, it will execute the getUserFallback method directly, without making the service call.
  3. Recovery (Half-Open): 10 seconds

Now , we Consolidated all these:---------

Guidelines for combining Retry or Rate Limiter patterns with the Circuit Breaker:---------

Resilience4j allows you to chain or combine multiple patterns (such as Circuit Breaker, Retry, and Rate Limiter) on the same method.


Retry Pattern:------
This is used to automatically retry an operation a few times if a request fails due to temporary network issues or other reasons, rather than immediately returning an error.
We include this in the application.properties file:----------

Resilience4j Retry configuration for userServiceRetry

resilience4j.retry.instances.userServiceRetry.maxAttempts=3
resilience4j.retry.instances.userServiceRetry.waitDuration=2s

Rate Limiter Pattern:
This is used to control the maximum number of API calls a user or client can make.

The application.properties file and settings for Rate Limiter:

Resilience4j Rate Limiter configuration for userServiceRateLimiter

resilience4j.ratelimiter.instances.userServiceRateLimiter.limitForPeriod=5
resilience4j.ratelimiter.instances.userServiceRateLimiter.limitRefreshPeriod=10s
resilience4j.ratelimiter.instances.userServiceRateLimiter.timeoutDuration=0


An important rule (Order) regarding the simultaneous use of multiple annotations in the code:------

Execution sequence of annotations:

Retry ➡️ CircuitBreaker ➡️ RateLimiter

The combined code for all these features looks like this:-----------

package com.example.service;

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.ratelimiter.annotation.RateLimiter;
import io.github.resilience4j.retry.annotation.Retry; import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class OrderService {

private final RestTemplate restTemplate = new RestTemplate();

// Using three annotations together; all share the same fallback method
@Retry(name = "userServiceRetry", fallbackMethod = "commonFallback")
@CircuitBreaker(name = "userServiceBreaker", fallbackMethod = "commonFallback")
@RateLimiter(name = "userServiceRateLimiter", fallbackMethod = "commonFallback")
public String getUserDetails(Long userId) {
return restTemplate.getForObject("http://localhost:8081/users/" + userId, String.class);
}

// A common fallback method to handle errors for all patterns
public String commonFallback(Long userId, Throwable throwable) {
if (throwable instanceof io.github.resilience4j.ratelimiter.RequestNotPermitted) {
return "You are sending requests too fast! Please wait a moment.";
}
return "The User Service is currently unavailable. Alternative data has been loaded.";
}

}

Hope it will make you understandable.

Top comments (0)