-
[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); } }
반응형'블로그 번역' 카테고리의 다른 글
[23.12.29] Java 21 - Sequenced Collections (0) 2023.12.29 [23.12.28] 2024년 자바 개발자를 위한 면접 질문 - 1 (0) 2023.12.28 [23.12.26] Outbox Transaction Pattern (0) 2023.12.26 [23.12.20] Spring Data JPA 성능 최적화 (0) 2023.12.21 [23.12.19] 단위 테스트에서 환경 변수 Mocking (0) 2023.12.19