새소식

Java

[Java] Spring Retry

  • -
728x90
Spring Retry 에 대해 학습하고, 이를 적용한 코드를 통한 깊이 더하기 

 

 

⛅ Intro

최근 각광받는 MSA 구조, 분산 시스템 구조에서 서버간 네트워크 통신은 매우 중요하다. 특히 네트워크를 통한 API 호출은 언제 어떻게 실패할지 예측하기 어렵기 때문에 이를 감시할 수 있는 모니터링 기술도 매우 중요하게 평가받고 있다. Datadog 같은 기업의 클라우드 환경 솔루션도 이런 분산 시스템 모니터링과 실시간 오류, 성능 분석을 위한 사용자의 니즈를 기반으로 급속하게 성장하고 있는 중이다.

 

이런 분산 시스템에서 서버와 서버간 API 호출 실패에 대한 재시도는 매우 중요한 행위이다. 1번의 네트워크 호출 실패로 서비스의 비지니스 로직을 모두 실패처리하거나 fallback 처리하는 것은 몇 번 다시 호출하는 것보다 큰 리소스 낭비가 될 수 있기 때문이다.

또한, DNS서버, 스위치, 로드 밸런서 등 수 많은 네트워크 구성 요소들은 요청이 이루어지는 모든 단계에서 오류를 일으킬 수 있다. 네트워크 환경에서는 클라이언트 애플리케이션의 재시도 기술이 어플리케이션의 안정성을 높일 뿐만 아니라 운영 비용을 절감하는 효과도 있다. 따라서, 보통은 특정 API 호출 실패 상황에서는 최대 3번의 호출 재시도를 하는 등의 방법을 많이 사용한다. 대표적인 예로 명확한 비지니스 로직의 실패의 응답을 받는 상황이 아닌 네트워크의 일시적 장애로 Read Timeout 실패 응답을 받는 경우나 api throttling 문제가 재시도를 해볼만한 상황이다.

 

명확한 비지니스 로직 실패의 경우는 몇번의 재시도를 하여도 동일하게 실패할 것이다. 하지만 문제는 이런 평범함 재시도 행위 자체가 대부분은 의미 없거나 네트워크에 부담을 더 가중하는 결과로 끝난다는 점에 있다. 대부분의 Read Timeout 상황은 특정 시간동안 네트워크 이슈가 지속되는 경우가 많기 때문에 3회 재시도를 하더라도 모두 실패로 끝날 가능성이 높다. 또한 재시도 자체를 시간 간격 두고 하지 않는 경우 문제가 발생한 네트워크에 더 부담을 줄 가능성이 크다. 예를 들어 트래픽이 몰려서 요청 자체가 지연되고 있는데 모든 클라이언트가 재시도를 연속으로 시도한다고 생각해보자. 네트워크 트래픽이 더 증가할 것이다.

 

따라서 Retry 행위는 똑똑해야 하며, Spring에서 재시도 기능을 사용하기 위해서는 Resilience4j, Spring Retry 라이브러리를 보통 많이 사용한다.

 

Spring Retry

Spring Retry란 애플리케이션에서 재시도 패턴을 구현할 수 있도록 지원하는 프레임워크다. 재시도 패턴은 일시적인 오류가 발생했을 때, 자동으로 재처리를 시도하는 방법을 제공한다. 

 

Spring Retry의 기능

Spring Retry 는 다음과 같은 기능을 제공한다.

 

1. 선언적 및 프로그래매틱 재시도

: 어노테이션을 사용하여 선언적으로 재시도 정책을 적용하거나, 직접 코드로 재시도 로직을 구현할 수 있다.

 

2. 재시도 정책

: 여러 가지 내장된 재시도 정책을 제공하며, 사용자가 커스텀 재시도 정책을 정의할 수도 있다. 예를 들어, 최대 재시도 횟수, 재시도 간의 지연 시간, 특정 예외에 대한 재시도 여부 등을 설정할 수 있다.

 

3. 백오프 정책

: 재시도 간의 대기 시간을 관리하는 백오프 정책을 제공한다. 고정된 지연 시간을 설정하거나, 지연 시간을 점진적으로 증가시키는 등의 정책을 사용할 수 있다.

 

4. 리스너

: 재시도 과정에서 발생하는 이벤트를 처리할 리스너를 등록할 수 있어, 재시도 전후의 처리 로직을 구현할 수 있다.

 

Spring Retry 를 사용하는 이유

재처리를 할 때 보통 아래와 같은 것들을 고려하게 된다.

 

- 재시도를 몇 번 실행할 것인가?

- 재시도 하기 전에 지연 시간을 얼마나 줄 것인가?

- 재시도를 모두 실패했을 경우 어떻게 처리할 것인가?

 

물론 이를 자바 코드로 구현하여 사용할 수 있지만, 비즈니스 로직에 집중이 가능하도록 스프링에서 제공하는 라이브러리를 사용했을 때 코드를 간결하게 하고, 유지보수하기 쉽다는 장점이 있다.

 

Spring Retry 를 사용하는 방법 중에는 어노테이션을 이용하는 방법과 RetryTemplate 을 이용하여 재시도 하는 방법이 있는데 어노테이션 사용법에 초점을 맞춰 글을 작성하겠다.

 

Retry with annotations

 실제 프로젝트에서 Spring Retry 를 사용한 예시를 통해 사용법에 대해 설명한다 😀

 

먼저 어노테이션을 이용하여 Spring Retry 를 활성화하려면 @EnableRetry 어노테이션을 추가해야 한다.

@EnableRetry
@Configuration
public class RetryTemplateConfig {
    //...
}

 

그 후 @Retryable 어노테이션을 사용하여 메소드에 재시도 기능을 추가할 수 있다.

 

@Service
@RequiredArgsConstructor
public class KakaoAddressSearchService {

    @Retryable(value = {RuntimeException.class})
    public KakaoApiResponseDto requestAddressSearch(String address) {
        // ...
    }
}

 

위의 코드에서 RuntimeException이 발생하면 재시도를 하게 된다. 현재는 default 이기 때문에 재시도는 최대 3번,  재시도 딜레는 1초이다.

 

재시도 동작을 사용자 정의하기 위해 maxAttempts 및 backoff 매개변수를 사용할 수 있다. 아래는 최대 2회 재시도(첫번째 시도를 포함) 를 하고 재시도 전 3초의 지연을 주었다.

 

@Service
@RequiredArgsConstructor
public class KakaoAddressSearchService {

    @Retryable(
            value = {RuntimeException.class},   // 여러 exception 선택 가능
            maxAttempts = 2,
            backoff = @Backoff(delay = 3000)
    )
    public KakaoApiResponseDto requestAddressSearch(String address) {
        // ...
    }
}

 

또한 Fallback 처리를 할 수 있는 기능을 제공하는데, @Recover 어노테이션을 사용하면 된다.

 

@Recover
public KakaoApiResponseDto recover(RuntimeException e, String address) {   
    log.error("All the retries failed. address: {}, error : {}", address, e.getMessage());    
    return null;    
}

 

이제 최대 2번 재시도(첫번 째 시도 포함)를 하고, 모두 실패하게 된다면 recover 메서드가 실행된다.

여기서 주의할 점은 Recover 메서드의 반환 타입은 반드시 맞춰야 하는데, requestAddressSearch 메서드의 반환타입을 맞춰 주었다.

 

파라미터의 경우는 선택적으로 던져진 예외와 retryable 메서드에서 사용한 파라미터를 추가 가능하다. 단, 예외는 첫번째 파라미터에 위치해야 하며, retrable에 사용된 파라미터도 사용시 순서는 동일하게 맞춰야 한다.
아래는 공식문서에서 제공된 예시이다.

 

@Service
class Service {
    @Retryable(RemoteAccessException.class)
    public void service(String str1, String str2) {
        // ... do something
    }
    
    @Recover
    public void recover(RemoteAccessException e, String str1, String str2) {
       // ... error handling making use of original args if required
    }
}

 

retryable 메소드와 recover 메소드 반환 타입을 맞춰주지 않으면 Cannot locate recovery method 에러가 발생한다. recover 메서드에서는 특정 값을 리턴해줄 수도 있고, exception을 throw 할 수도 있다.

728x90
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.