ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [23.12.27] @Retryable 사용법
    블로그 번역 2023. 12. 27. 11:12
    반응형

     

     

    이번 주제는 @Retryable 사용법에 관한 주제입니다.

     

     

    @Retryable 이란?

     

    현재 어플리케이션은 외부 서비스, 데이터베이스 등과 상호작용을 자주 한다.

    그러면서 Third-party 서비스의 정지 시간 및 네트워크 지연 등 일시적인 오류가 발생한다.

    이런 경우 개발자는 적어도 일시적인 오류에 대해서는 스스로 회복할 수 있는 어플리케이션을 만들고 싶을 것이다.

    이러한 경우 @Retryable 어노테이션을 사용해 어플리케이션의 내결함성을 높여주자.

     

     

    @Retryable 사용법

    @Transactional 과 같이 Proxy 기반으로 동작하여 기존 while문 Retry로직을 깔끔하게 작성할 수 있다.

    테스트 결과 일반 메서드가 아닌 인터페이스 메서드에 붙여야 정상동작을 한다.

    현재 retryFor에 예외를 지정해주지 않으면 모든 예외에 대해서 동작한다. default retry 횟수는 3번 이다.

    public interface MyService {
    
        @Retryable
        String fetchDataFromRemote();
    
    }
    @Slf4j
    @Service
    public class MyServiceImpl implements MyService {
        private int cnt = 0;
    
        public String fetchDataFromRemote() {
            if (++cnt < 3) {
                log.error("occur error. cnt: {}", cnt);
                throw new IllegalArgumentException();
            }
            cnt = 0;
            return "Success";
        }
    
    }

     

    @Recover 사용해보자.

    아래와 같이 구현하였다. 몇 가지 주의사항이 있다.

    @Recover 메서드는 @Retryable 메서드의 리턴 타입과 같아야한다.

    같지 않으면 "Cannot locate recovery method..." 에러가 발생한다.

    @EnableRetry(proxyTargetClass=true) 해줘야한다. 

    동작은 만약 3번 이상 Retry했지만 실패 시 recover메서드가 실행된다.

    public interface MyService {
    
        @Retryable(retryFor = Exception.class)
        String fetchDataFromRemote();
    
        @Recover
        String recover(Exception e, String message);
    }
    

     

    @BackOff 사용해보자.

    동작은 재시도를 몇 초마다 할지 설정할 수 있다. default 값은 1000ms이다.

    또한 동적으로 간격을 주고 싶은 경우가 있다. 그럴 땐 multiplier 옵션을 사용해보자.

    1초 -> 2초 -> 4초.... 증가 할 것이다.

    public interface MyService {
    
        @Retryable(retryFor = Exception.class, backoff = @Backoff(delay = 100))
        String fetchDataFromRemote();
    
        @Recover
        String recover(Exception e, String message);
    }
    
    @Retryable(retryFor = Exception.class, backoff = @Backoff(delay = 1000, multiplier = 2))
    String fetchDataFromRemote();
    

     

    Yaml 혹은 Properties 파일에 설정을 추가하여 활용할 수도 있다.

    retry.maxAttempts=9
    retry.maxDelay=100
    
    @Retryable(retryFor = Exception.class
            , backoff = @Backoff(delayExpression = "${retry.maxDelay}")
            , maxAttemptsExpression = "${retry.maxAttempts}"
    )
    String fetchDataFromRemote();
    

     

    @Retryable 외 방법

     

    방법1: RetryTemplate

    @Configuration
    public class AppConfig {
    
        //...
    
        @Bean
        public RetryTemplate retryTemplate() {
            RetryTemplate retryTemplate = new RetryTemplate();
    
            FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
            fixedBackOffPolicy.setBackOffPeriod(2000l);
            retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
    
            SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
            retryPolicy.setMaxAttempts(2);
            retryTemplate.setRetryPolicy(retryPolicy);
    
            return retryTemplate;
        }
    }
    

     

    Configuration 등록 후 RetryTemplate 사용.

    
    retryTemplate.execute(new RetryCallback<Void, RuntimeException>() {
    	@Override
    	public Void doWithRetry(RetryContext arg0) {
            myService.templateRetryService();
            ...
        }
    });

     

     

    방법2: Listener 구현

    public class DefaultListenerSupport extends RetryListenerSupport {
    
        @Override
        public <T, E extends Throwable> void close(RetryContext context,
                                                   RetryCallback<T, E> callback, Throwable throwable) {
            logger.info("onClose");
            ...
            super.close(context, callback, throwable);
        }
    
        @Override
        public <T, E extends Throwable> void onError(RetryContext context,
                                                     RetryCallback<T, E> callback, Throwable throwable) {
            logger.info("onError"); 
            ...
            super.onError(context, callback, throwable);
        }
    
        @Override
        public <T, E extends Throwable> boolean open(RetryContext context,
                                                     RetryCallback<T, E> callback) {
            logger.info("onOpen");
            ...
            return super.open(context, callback);
        }
    }
    

     

    참고: https://www.baeldung.com/spring-retry

    반응형

    댓글

Designed by Tistory.