Search code examples
springspring-retrycircuit-breaker

Spring-Retry with Circuit breaker


I am trying to leverage both the retry and circuit breaker mechanism of spring-retry. I tried to use both annotations(@Retryable and @CircuitBreaker) in a particular function(like below), but Circuit Breaker was not working.

@Service
public class CommandAndRetry {

    private static final Logger LOGGER = LoggerFactory.getLogger(SampleRetryService.class);

    @CircuitBreaker(maxAttempts = 1, openTimeout = 10000)
    @Retryable(
            value = {TypeOneException.class},
            maxAttempts = 3, backoff = @Backoff(2000))
    public void retryWhenException() throws TypeOneException {
        LOGGER.info("Retrying");
        throw new TypeOneException();
    }

    @Recover
    public void recover(Throwable t) throws Throwable {
        LOGGER.info("SampleRetryService.recover");
        throw t;
    }
}

Then I tried dividing the functionality into two different functions, both having @Retryable and @CircuitBreaker respectively. In this case, retry mechanism was not working. Please find below code snippet.

PS: exec method(Circuit Breaker method) is invoked from a controller.

@Service
public class CommandAndRetry {

    private static final Logger LOGGER = LoggerFactory.getLogger(SampleRetryService.class);


    @CircuitBreaker(maxAttempts = 1, openTimeout = 10000)
    public void exec() throws TypeOneException {
        retryWhenException();
    }

    @Retryable(
            value = {TypeOneException.class},
            maxAttempts = 3, backoff = @Backoff(2000))
    public void retryWhenException() throws TypeOneException {
        LOGGER.info("Retrying");
        throw new TypeOneException();
    }

    @Recover
    public void recover(Throwable t) throws Throwable {
        LOGGER.info("SampleRetryService.recover");
        throw t;
    }
}

Can anyone please tell why it's behaving like this.

Also please advise if there exists a better way to implement both retry and circuit-breaker. PS: I neither want to use resilience4j nor retryTemplate.


Solution

  • If you want retry within circuit breaker, they must be in different beans. If you call one @Retryable directly from another, in the same bean, you will bypass the interceptor.

    This works fine for me...

    @SpringBootApplication
    @EnableRetry
    public class So52193237Application {
    
        public static void main(String[] args) {
            SpringApplication.run(So52193237Application.class, args);
        }
    
        @Bean
        public ApplicationRunner runner(Foo foo) {
            return args -> {
                try {
                    foo.exec();
                }
                catch (Exception e) {
                    try {
                        foo.exec();
                    }
                    catch (Exception ee) {
                        Thread.sleep(11000);
                        try {
                            foo.exec();
                        }
                        catch (Exception eee) {
    
                        }
                    }
                }
            };
        }
    
        @Component
        public static class Foo {
    
            private static final Logger LOGGER = LoggerFactory.getLogger(Foo.class);
    
            private final Bar bar;
    
            public Foo(Bar bar) {
                this.bar = bar;
            }
    
            @CircuitBreaker(maxAttempts = 1, openTimeout = 10000, resetTimeout=10000)
            public void exec() throws TypeOneException {
                LOGGER.info("Foo.circuit");
                this.bar.retryWhenException();
            }
    
            @Recover
            public void recover(Throwable t) throws Throwable {
                LOGGER.info("Foo.recover");
                throw t;
            }
    
        }
    
        @Component
        public static class Bar {
    
            private static final Logger LOGGER = LoggerFactory.getLogger(Bar.class);
    
            @Retryable(value = { TypeOneException.class }, maxAttempts = 3, backoff = @Backoff(2000))
            public void retryWhenException() throws TypeOneException {
                LOGGER.info("Retrying");
                throw new TypeOneException();
            }
    
            @Recover
            public void recover(Throwable t) throws Throwable {
                LOGGER.info("Bar.recover");
                throw t;
            }
    
        }
    
    }