Search code examples
springspring-cloudasynchttpclientcircuit-breakerresilience4j

Spring Cloud Resilience4j Circuitbreaker not calling fallback


I am trying to use the spring cloud resilience4j library to implement a circuit breaker for when an vendor api returns 500 errors or when it times out, the api is called using AsyncHttpClient. The problem seems to be that the circuit breaker is never opened and the fallback method is never executed when the API is returning 500 errors.

Could it be due to the fact I am overriding the onFailure (do this to capture metrics)

The factory is declared as follows:

@Autowired
private CircuitBreakerFactory circuitBreakerFactory;

The factory config is defined as follows:

@Bean
public Customizer<Resilience4JCircuitBreakerFactory> specificCustomConfiguration1() {

    TimeLimiterConfig timeLimiterConfig = TimeLimiterConfig.custom()
            .timeoutDuration(Duration.ofSeconds(2))
            .build();
    CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
            .failureRateThreshold(50)
            .waitDurationInOpenState(Duration.ofMillis(1000))
            .slidingWindowSize(2)
            .build();

    return factory -> factory.configure(builder -> builder.circuitBreakerConfig(circuitBreakerConfig)
            .timeLimiterConfig(timeLimiterConfig).build(), "circuitBreaker");
}

The API is called with the following method, which also has the circuit breaker and .run method:

private void getRecommendationsAsync(final GatewayRecommendationsRequest request,
                                     final String variantName,
                                     final ThinkCallbackHandler callbackHandler)
        throws URISyntaxException, JsonProcessingException {

    CircuitBreaker circuitBreaker = circuitBreakerFactory.create("circuitBreaker");



    final AsyncHttpClient.BoundRequestBuilder builder = requestBuilder
            .prepareRequest(asyncHttpClient, request, variantName);


    circuitBreaker.run(() -> builder.execute(new AsyncCompletionHandler<Response>() {
        @Override
        public Response onCompleted(final Response response)
                throws Exception {
            processResponseStatus(response, variantName);

            GatewayRecommendations gatewayRecommendations = ((ThinkResponseMapper) responseMapper).map(request,
                    response,
                    useCaseId,
                    variantName);
            validator.validateResponse("/v1/recommendations", Request.Method.GET, response, gatewayRecommendations);
            callbackHandler.onSuccess(gatewayRecommendations);
            return response;
        }

        @Override
        public void onThrowable(final Throwable t) {
            callbackHandler.onFailure(t, recommendationsUri + " " + parameters.toString(), variantName);
        }


    }), throwable -> fallback());

}

and finally here is the fallback method i would like to invoke:

public String fallback() {
    LOGGER.warn("The fallback method is being used");
    System.out.println("Fallback Method is being executed");
    return "The circuit breaker is open";
}

Solution

  • Are you using AsyncHttpClient?

    You could give our Resilience4j Spring Boot 2 Starter a try. It provides annotation support, external configuration, metrics, retry and many more features.

    If you could return a CompletableFuture, it could look as follows:

    @CircuitBreaker(name = "circuitBreaker", fallbackMethod="fallback")
    public CompletableFuture<GatewayRecommendations> getRecommendationsAsync(final GatewayRecommendationsRequest request,
                                         final String variantName,
                                         final ThinkCallbackHandler callbackHandler)
            throws URISyntaxException, JsonProcessingException {
    
        final AsyncHttpClient.BoundRequestBuilder builder = requestBuilder
                .prepareRequest(asyncHttpClient, request, variantName);
    
        return builder.execute().toCompletableFuture()            
                .exceptionally(t -> { /* Something wrong happened... */  } )
                .thenApply(response -> { /*  Validate response and extract GatewayRecommendations  */  
       });
    
    
    }
    
    public CompletableFuture<GatewayRecommendations> fallback(RequestNotPermitted ex) {
        // The circuit breaker is open
        // Return a static list of recommendations
        return CompletableFuture.completedFuture(..)
    }