psms
psms

Reputation: 883

Resilience4j Circuit Breaker and Retry with WebClient Not Working as Expected

I'm using Resilience4j to configure a circuit breaker and retry mechanism for my WebClient requests in a Spring Boot application. However, I'm facing two issues:

  1. The retry mechanism is not working at all.
  2. The circuit breaker opens for exceptions not specified in recordException as well.

Below is my WebClient code:

private TileResult getData(TileRequest tileRequest, String url) {
    ResponseEntity<Flux<DataBuffer>> responseEntity = webClient.get().uri(url, tileRequest.getZ(), tileRequest.getX(), tileRequest.getY())
            .retrieve()
            .onStatus(status -> status == HttpStatus.NOT_FOUND,
                    error -> Mono.error(new ResourceNotFoundException("Tile not found")))
            .onStatus(HttpStatusCode::is4xxClientError,
                    error -> {
                        log.error("Client error response from tile service API. Status code {}, Error {}", error.statusCode(), error.bodyToMono(String.class));
                        return Mono.error(new RemoteServiceException(error.statusCode().value(), "Error response from tile service API"));
                    })
            .onStatus(HttpStatusCode::is5xxServerError,
                    error -> {
                        log.error("Server error response from tile service API. Status code {}, Error {}", error.statusCode(), error.bodyToMono(String.class));
                        return Mono.error(new WebClientServerException(error.statusCode().value(), "Error response from tile service API"));
                    })
            .toEntityFlux(DataBuffer.class)
            .transformDeferred(CircuitBreakerOperator.of(circuitBreaker)) 
            .transformDeferred(RetryOperator.of(retry))
            .block();

    if (responseEntity != null && responseEntity.getStatusCode().is2xxSuccessful()) {
        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            DataBufferUtils.write(responseEntity.getBody(), outputStream).blockLast();
            return new TileResult(tileRequest, outputStream.toByteArray());
        } catch (Exception e) {
            log.error(withTileInfo("Error while downloading and saving tile data to cache", tileRequest, url), e);
            throw new ServiceException("Error while downloading and saving tile data");
        }
    }
    return null;
}

Here are my retry and circuit breaker configurations:

@Configuration
@Slf4j
public class ResilienceConfig {

    public static final String CIRCUIT_BREAKER_CONFIG_NAME = "tileService";
    public static final String RETRY_CONFIG_NAME = "tileService";

    @Bean
    public CircuitBreakerRegistry configureCircuitBreakerRegistry() {
        final CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
                .slidingWindowSize(10)
                .slidingWindowType(CircuitBreakerConfig.SlidingWindowType.COUNT_BASED)
                .minimumNumberOfCalls(4)
                .failureRateThreshold(50)
                .slowCallRateThreshold(100)
                .slowCallDurationThreshold(Duration.ofMillis(30000))
                .waitDurationInOpenState(Duration.ofMillis(10000))
                .permittedNumberOfCallsInHalfOpenState(2)
                .automaticTransitionFromOpenToHalfOpenEnabled(true)
                .recordException(new RecordFailurePredicate())
                .build();
        return CircuitBreakerRegistry.of(Map.of(CIRCUIT_BREAKER_CONFIG_NAME, circuitBreakerConfig));
    }

    @Bean
    public RetryRegistry configureRetryRegistry() {
        final RetryConfig retryConfig = RetryConfig.custom()
                .maxAttempts(3)
                .intervalFunction(IntervalFunction.ofExponentialBackoff(IntervalFunction.DEFAULT_INITIAL_INTERVAL, 2))
                .retryOnException(new RecordFailurePredicate())
                .build();
        return RetryRegistry.of(Map.of(RETRY_CONFIG_NAME, retryConfig));
    }

}

class RecordFailurePredicate implements Predicate<Throwable> {

    @Override
    public boolean test(Throwable e) {
        return recordFailures(e);
    }

    private boolean recordFailures(Throwable throwable) {
        return throwable instanceof WebClientServerException ||
                throwable instanceof TimeoutException || throwable instanceof IOException;
    }
}

Could anyone help me understand what I might be missing?

Upvotes: 0

Views: 154

Answers (0)

Related Questions