Integrating circuitbreaker, retry and timelimiter in Resilience4j
Asked Answered
I

2

5

I am trying to use Resilience4j features.

My use case is to combine the 3 modules:

  • circuitbreaker
  • retry
  • timelimiter

I want to combine all these modules and execute the method only once.

Code

Here is what I have tried.

Supplier<R> supplier = this::doSomething;

timeLimiter.executeFutureSupplier(() -> CompletableFuture.supplyAsync(supplier));

return Decorators.ofSupplier(supplier)
                .withCircuitBreaker(circuitBreaker)
                .withRetry(retry)
                .withBulkhead(bulkhead)
                .decorate();

Issue

My doSomething() method executes twice instead of expected once.

Has anyone seen this issue earlier?

Immortality answered 13/2, 2020 at 20:59 Comment(0)
T
6

You are using timeLimiter.executeFutureSupplier which executes the Future instead of decorating it.

Please use it in exactly this order:

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(3);
Supplier<R> supplier = this::doSomething;

CompletableFuture<R> future = Decorators.ofSupplier(supplier)
    .withThreadPoolBulkhead(threadPoolBulkhead)
    .withTimeLimiter(timeLimiter, scheduledExecutorService)
    .withCircuitBreaker(circuitBreaker)
    .withRetry(retry)
    .get().toCompletableFuture();
Toombs answered 14/2, 2020 at 6:53 Comment(2)
What would be the order if a rate limiter was added to the decorators, i.e. where would withRateLimiter(rateLimiter) go in the above example?Assiniboine
After giving some thought to it, the rate limiter should probably go in between bulkhead and time limiter.Assiniboine
V
4

As Resilience4J creator Robert Winkler answered, use the decorator to combine/apply the 3 resilience-patterns on the supplier. Adjust their order as needed for execution and resiliency.

Order of decorators has effect on execution

The JavaDoc of the Decorators builder explains the effect of build-chain order:

Decorators are applied in the order of the builder chain.

For example, consider:

Supplier<String> supplier = Decorators
     .ofSupplier(() -> service.method())  // 1. called and result handled in order:
     .withCircuitBreaker(CircuitBreaker.ofDefaults("id"))  // 2. circuit-breaker
     .withRetry(Retry.ofDefaults("id"))  // 3. retry
     .withFallback(CallNotPermittedException.class, 
          e -> service.fallbackMethod())  // 4. fallback
     .decorate();

(I added comments to emphasize execution order)

This results in the following composition when executing the supplier: Fallback(Retry(CircuitBreaker(Supplier)))

This means the Supplier is called first, then its result is handled by the CircuitBreaker, then Retry and then Fallback.

Integrating client-side RateLimiter

As recommended in Reflectoring's tutorial Implementing Rate Limiting with Resilience4j - Reflectoring:

Using RateLimiter and Retry Together

[..] We would create RateLimiter and Retry objects as usual. We then decorate a rate-limited Supplier and wrap it with a Retry: [example]

Order suggestion

Comparing this "Retry with RateLimiter" with above JavaDoc example and answer from Robert I would suggest to choose following execution order:

  1. supplier, then decorate in order with resilience:
  2. RateLimiter (prevent a call if rate-limit exceeded)
  3. TimeLimiter (time-out a call)
  4. CircuitBreaker (fail-fast)
  5. Retry (retry on exceptions)
  6. Fallback (fallback as last resort)

A suitable reference order is for example auto-configured in the Spring-Boot extension. See the official Guides, Getting started with resilience4j-spring-boot2 about Aspect order:

The Resilience4j Aspects order is following:

Retry ( CircuitBreaker ( RateLimiter ( TimeLimiter ( Bulkhead ( Function ) ) ) ) )

so Retry is applied at the end (if needed).

See also:

Vipul answered 8/1, 2022 at 14:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.