DEV Community

eidher
eidher

Posted on

Resilience4J Circuit Breakers

If we have a chain of services calling other services and one of the services is down or slow, it would impact the entire chain. In that case, we would need to return a fallback response. We could implement a circuit breaker pattern to reduce the load, retry the requests, and implement a rate limiting.

Resilience4j is a lightweight fault tolerance library designed for functional programming.
https://resilience4j.readme.io/docs

Dependencies

<dependency>
  <artifactId>resilience4j-spring-boot2</artifactId>
  <groupId>io.github.resilience4j</groupId>
</dependency>
<dependency>
  <artifactId>spring-boot-starter-aop</artifactId>
  <groupId>org.springframework.boot</groupId>
</dependency>
<dependency>
  <artifactId>spring-boot-starter-actuator</artifactId>
  <groupId>org.springframework.boot</groupId>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Retry

When an API call fails, you can force a retry just by adding the @Retry annotation to the API endpoint. By default, it would retry three times, but you can set the max retry attempts by giving the retry a name and adding a max attempts property:

@GetMapping("/sample-api")
@Retry(name = "sample-api")
public String sampleApi() {
  ...
}
Enter fullscreen mode Exit fullscreen mode
resilience4j.retry.instances.sample-api.max-attempts=5
Enter fullscreen mode Exit fullscreen mode

Additionally, you can define other properties as wait duration (between attempts), and enable exponential backoff to wait in exponential ranges of time:

resilience4j.retry.instances.sample-api.waitDuration=1s
resilience4j.retry.instances.sample-api.enableExponentialBackoff=true
Enter fullscreen mode Exit fullscreen mode

Fallback

You can define a fallback method in case after retries it fails again:

@GetMapping("/sample-api")
@Retry(name = "sample-api", fallbackMethod = "hardcodedResponse")
public String sampleApi() {
  ...
}

public String hardcodedResponse(Exception ex) {
  return "fallback-response";
}
Enter fullscreen mode Exit fullscreen mode

Circuit breaker

@GetMapping("/sample-api")
@CircuitBreaker(name = "default", fallbackMethod = "hardcodedResponse")
public String sampleApi() {
  ...
}

public String hardcodedResponse(Exception ex) {
  return "fallback-response";
}
Enter fullscreen mode Exit fullscreen mode

With this configuration, if sampleApi method fails, the fallback method is executed, for the next calls it will break the circuit and it will directly return the fallback response.

The CircuitBreaker is implemented via a finite state machine with three normal states: CLOSED, OPEN, and HALF_OPEN.
https://resilience4j.readme.io/docs/circuitbreaker

Closed is when I am calling the microservice continuously. In the open state, the circuit breaker will not call the dependent microservice. It will return the fallback response. Finally, in the HALF_OPEN state, the circuit breaker will be sending a percentage of requests to the dependent microservice, and for the rest of the requests, it will return the fallback response. When starting the application the circuit breaker starts in the closed state. If a significant percentage of the calls are failing, the circuit breaker will switch to the open state. There is a wait duration that you can configure, then it will switch to HALF_OPEN state. You can configure how much percentage of requests it will try to send successfully to the dependant service to move to the closed state again. If it fails, it will move back to the open state again.

Example of a configuration for failure rate threshold in percentage, the default value is 50%:

resilience4j.circuitbreaker.instances.default.failureRateThreshold=90
Enter fullscreen mode Exit fullscreen mode

For more information see: https://www.baeldung.com/spring-boot-resilience4j

Rate Limiting

The @RateLimiter annotation allows us to define a period for a specific number of calls. If you exceed that quantity of calls, the service will return an exception.

@GetMapping("/sample-api")
@RateLimiter(name="default")
public String sampleApi() {
  ...
}
Enter fullscreen mode Exit fullscreen mode
resilience4j.ratelimiter.instances.default.limitForPeriod=2
resilience4j.ratelimiter.instances.default.limitRefreshPeriod=10s
Enter fullscreen mode Exit fullscreen mode

BulkHead

The @Bulkhead annotation allows us to define a max concurrent calls value.

@GetMapping("/sample-api")
@Bulkhead(name = "sample-api")
public String sampleApi() {
  ...
}
Enter fullscreen mode Exit fullscreen mode
resilience4j.bulkhead.instances.sample-api.maxConcurrentCalls=10
Enter fullscreen mode Exit fullscreen mode

Top comments (0)