Order of execution for Resilience4j annotations?

765 Views Asked by At

We are calling few downstreams from our service, and we need to provide the circuit breaker, retry, timelimiter and bulkhead feature to all the downstream services call.

Current code-base:

@Override
@Bulkhead(name = "downstream1", type = Type.THREADPOOL, fallbackMethod = "downstream1Fallback")
@TimeLimiter(name = "downstream1", fallbackMethod = "downstream1Fallback")
@CircuitBreaker(name = "downstream1", fallbackMethod = "downstream1Fallback")
@Retry(name = "downstream1", fallbackMethod = "downstream1Fallback")
public CompletableFuture<String> downstream1(String a) {
    return CompletableFuture.completedFuture(process(a)));
}

public CompletableFuture<String> downstream1Fallback(String a,
        Exception e) {
    return CompletableFuture.completedFuture(""); // empty string.
}

app.properties :

# timeouts
resilience4j.timelimiter.instances. downstream1.timeout-duration=30 #30 ms

# retry
resilience4j.retry.instances.downstream1.max-attempts=1

# bulkhead
resilience4j.thread-pool-bulkhead.instances.downstream1.core-thread-pool-size=30
resilience4j.thread-pool-bulkhead.instances.downstream1.max-thread-pool-size=50
resilience4j.thread-pool-bulkhead.instances.downstream1.queue-capacity=10

Use-case :

If the thread-pool by Bulkhead is full OR there's a timeout from downstream OR circuit-is-open, I don't want to do the retrying in all of these scenarios, and retry in every other scenario.

Retry should only happen if there's an server exception from the downstream.

As per the official docs : https://resilience4j.readme.io/docs/getting-started-3#aspect-order. This is the order.

Retry ( CircuitBreaker ( RateLimiter ( TimeLimiter ( Bulkhead ( Function

My understanding from docs :

case 1: If thread-pool is full, fallback will be called with 
exception type BulkheadFullException.class - no retry
     : else new thread will be spawned to call downstream.

case 3: If spawned thread takes more time than what's set on 
"resilience4j.timelimiter", fallback will be called with 
exception type TimeoutException.class - no retry

case 4: If circuit is open, fallback will be called with 
exception type CallNotPermittedException.class - no retry.

case 5: If thread-pool is not full and no timeout and circuit is 
closed as well, and we got some other exception than 
the above mentioned exceptions, than only retry to downstream 
call will trigger using config values set on "resilience4j.retry".

Is this flow understanding correct, about how resilience4j would work OR do I need to change/add any config in the app.properties file, to serve the use-case described above.

1

There are 1 best solutions below

1
Raghvendra Garg On

you could either define exceptions on which you want to retry using retryExceptions or define exceptions you wish to ignore using ignoreExceptions If either of the solutions does not work for you then you can have a predicate retryOnResultPredicate which evaluates if a result should be retried. The Predicate must return true if the result should be retried, otherwise, it must return false.

like below.

import org.springframework.web.client.HttpClientErrorException;
import java.util.function.Predicate;

public class IgnoreErrorExceptionPredicate implements Predicate<Throwable> {

  @Override
  public boolean test(Throwable e) {
    return e instanceof HttpClientErrorException || e.getCause() instanceof HttpClientErrorException;
  }

}

and configure this predicate in retry properties as

resilience4j.retry:
    configs:
        default:
            maxAttempts: 3
            waitDuration: 100
            retryExceptions:
                - org.springframework.web.client.HttpServerErrorException
                - java.util.concurrent.TimeoutException
                - java.io.IOException
            ignoreExceptions:
                - io.github.robwin.exception.BusinessException
            retryOnResultPredicate:
                - your.predicate.package.IgnoreErrorExceptionPredicate