Search code examples
spring-bootspring-cloudcircuit-breakerresilience4j

Resilience4j Circuit Breaker is not working


I am facing a issue with the circuit breaker implementation using Spring Cloud Resilience4j.

Following some tutorial, I have tried to add the necessary dependencies in the project. Also, tried to add the configurations but, still the circuit is not opening and fallback method is not getting called.

For the use case, I am calling an external API from my service and if that external API is down then after few calls I need to enable the circuit breaker.

Please find the code pieces from the different files.

I am a newbie to circuit breaker pattern. Any help will be highly appreciated.

pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
<parent>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-parent</artifactId>
   <version>2.5.5</version>
   <relativePath/> <!-- lookup parent from repository -->
</parent>
    <properties>
    <java.version>11</java.version>
    <spring-cloud.version>2020.0.4</spring-cloud.version>
    </properties>
    <dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
<dependencies>
</project>

Application properties

resilience4j.circuitbreaker.instances.test-api.register-health-indicator=true
resilience4j.circuitbreaker.instances.test-api.minimum-number-of-calls=4
resilience4j.circuitbreaker.instances.test-api.failure-rate-threshold=50
resilience4j.circuitbreaker.instances.test-api.permitted-number-of-calls-in-half-open-state=3
resilience4j.circuitbreaker.instances.test-api.wait-duration-in-open-state=30s
resilience4j.circuitbreaker.instances.test-api.automatic-transition-from-open-to-half-open-enabled=true
resilience4j.circuitbreaker.instances.test-api.record-exceptions=com.testapi.exception.ServiceUnavailableError

Service Class Code Piece

@CircuitBreaker(name = "test-api", fallbackMethod = "storeResponseFallback")
    public TestResponse storeResponse(String apiURL, HttpEntity<String> entityrequest) {

        TestResponse testResponse = new TestResponse();
        Optional<ResponseEntity<TestResponse>> response = Optional.empty();
        Future<ResponseEntity<TestResponse>> responseFuture;

        ExecutorService executor = Executors.newFixedThreadPool(10);

        log.debug("Calling Extrenal API, Request Body: {}", entityrequest.toString());

        try {
            //Service call returns a future
            responseFuture = executor.submit(() -> restTemplate.postForEntity(apiURL, entityrequest, TestResponse.class));
            response = Optional.ofNullable(responseFuture.get());
            log.info("Got response from external API");


            if ((response.isPresent()) && (response.get().hasBody())) {
                testResponse = response.get().getBody();
            }

        } catch (Exception exception) {
            log.error("External api call got failed with an error");
            Thread.currentThread().interrupt();            
            throw new ServiceUnavailableError();
        }


        return testResponse;

    }
    
    
    public TestResponse storeResponseFallback(ServiceUnavailableError ex) {
        log.error("Executing Fallback Method For General exceptions");
        throw new ServiceUnavailableError();
    }

ServiceUnavailableError Java file

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ServiceUnavailableError extends RuntimeException{
    private static final long serialVersionUID = 2382122402994502766L;

    private String message;

}

Solution

  • The signature of your fallback method is wrong. It should contain all the parameters of the actual method ( in your case storeResponseFallback is the fallback method and storeResponse is the actual method), along with the exception. Please make sure to remove the try catch block. You do not want to handle the exception yourself, rather you should let circuit breaker to handle it for you. Please take a look at the following code which is from given link https://resilience4j.readme.io/docs/getting-started-3

    @CircuitBreaker(name = BACKEND, fallbackMethod = "fallback")
    public Mono<String> method(String param1) {
        return Mono.error(new NumberFormatException());
    }
    
    private Mono<String> fallback(String param1, IllegalArgumentException e) {
        return Mono.just("test");
    }
    

    Try using the following yaml file I used the following configuration with your existing code,I used yaml instead of properties file. this seems to stay in open state and call only the fallback method.

    resilience4j.circuitbreaker:
      configs:
        default:
          slidingWindowSize: 4
          permittedNumberOfCallsInHalfOpenState: 10
          waitDurationInOpenState: 10000
          failureRateThreshold: 60
          eventConsumerBufferSize: 10
          registerHealthIndicator: true
        someShared:
          slidingWindowSize: 3
          permittedNumberOfCallsInHalfOpenState: 10
      instances:
        test-api:
          baseConfig: default
          waitDurationInOpenState: 500000
        backendB:
          baseConfig: someShared
    

    Here is the updated fallback method

    public TestResponse storeResponseFallback(String apiURL, String entityrequest, java.lang.Throwable t) {
            log.error("Executing Fallback Method For General exceptions "+t.getMessage());
            return new TestResponse("Frm Fallback");// Making sure to send a blank response
        }