티스토리 뷰

- Exception 발생시 특정 횟수만큼 재시도를 진행하고자 한다.
- 저같은 경우 주로 다른 애플리케이션을 API로 통신시 네트워크등의 이슈로 문제가 발생시 재시도를 진행하고자 할때 사용한다.
- 방법은 두가지가 존재한다.
  spring에서 지원하는 spring-retry net.jodah.failsafe RetryPolicy이다.

● git 주소

technology-team / failsafe-retry — Bitbucket

 

● @Retryable 어노테이션

1.  dependencies 설정
- spring-retry 사용 시 aspectjweaver 가 필요하다.

implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-web:2.5.4'

implementation 'org.springframework.retry:spring-retry:1.3.1'
runtimeOnly 'org.aspectj:aspectjweaver:1.9.7'

2.  EnableRetry 설정
- SpringBootApplication 에 @EnableRetry 를 추가하여 활성화 시켜준다.

@SpringBootApplication
@EnableRetry
public class FailsafeRetryApplication {

    public static void main(String[] args) {
        SpringApplication.run(FailsafeRetryApplication.class, args);
    }

}

3.  @Retryable 으로 재시도 진행
@Retryable(maxAttempts = 2, backoff = @Backoff(2000), value = IllegalStateException.class,
            exclude = { NullPointerException.class, NullPointerException.class })
- value, include : 여기에 설정된 특정 Exception이 발생했을 경우 retry한다.

- exclude : 설정된 Exception 재시도 제외

- maxAttempts : 최대 재시도 횟수 (기본 3회)

- backoff : 재시도 pause 시간
- @Recover 의 경우 발생한 Exception에 대한 return 처리를 진행할 수 있다. 단, 리턴타입은 @Retryable에  에 정의한 리턴타입과 동일해야 한다.

아래 코드에서는 IllegalStateException일 경우 재시도, NullPointerException, NumberFormatException 의 경우 리턴을 재정의하였습니다.

package net.suby.failsaferetry.retryable;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
public class RetryableController {

    private final RetryableService retryableService;

    @GetMapping("/retryable")
    public String getRetryable(@RequestParam(required = false) Integer intValue) {
        return retryableService.getRetryable(intValue);
    }
}
package net.suby.failsaferetry.retryable;

import java.util.Random;

import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

@Service
public class RetryableService {

    @Retryable(maxAttempts = 2, backoff = @Backoff(2000), value = IllegalStateException.class,
            exclude = { NullPointerException.class, NullPointerException.class })
    public String getRetryable(Integer intValue) {
        int rand = new Random().nextInt();
        if (!ObjectUtils.isEmpty(intValue)) {
            rand = intValue;
        }

        if (rand < 0) {
            throw new NullPointerException("값이 음수일수 없습니다.");
        } else if (rand == 0) {
            throw new NumberFormatException("0의 값입니다.");
        } else if (rand % 3 != 0) {
            throw new IllegalStateException("fail retry");
        }

        return String.format("성공 : %s", rand);
    }

    @Recover
    String recover(NullPointerException e) {
        return e.getMessage();
    }

    @Recover
    String recover(NumberFormatException e, Integer intValue) {
        return String.format("%s : %s", e.getMessage(), intValue);
    }
}

● Failsafe.RetryPolicy

1.  dependencies 설정

implementation 'net.jodah:failsafe:2.4.3'

2.  RetryPolicy 으로 재시도 진행

new RetryPolicy<>()
    .handle(IllegalStateException.class) // 재시도 할 Exception

    .withDelay(Duration.ofSeconds(5)) // 딜레이 시간
    .handleResultIf(result -> result == "fail")  // 특정 결과값이면 재시도

    .withMaxRetries(3);  // 최대 재시도 횟수

package net.suby.failsaferetry.failsafe;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
public class FailSafeController {

    private final FailSafeService failSafeService;

    @GetMapping("/fail-safe")
    public String getRetryable(@RequestParam(required = false) Integer intValue) {
        return failSafeService.getRetryPolicy(intValue);
    }
}
package net.suby.failsaferetry.failsafe;

import java.time.Duration;
import java.util.Random;

import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import net.jodah.failsafe.Failsafe;
import net.jodah.failsafe.RetryPolicy;

@Service
public class FailSafeService {

    public String getRetryPolicy(Integer intValue) {
        RetryPolicy<Object> retryPolicy = new RetryPolicy<>().handle(IllegalStateException.class, NumberFormatException.class,
                                                                     NullPointerException.class)
                                                             .withDelay(Duration.ofSeconds(5))
                                                             .handleResultIf(result -> result == "fail")
                                                             .withMaxRetries(3);
        return Failsafe.with(retryPolicy).get(() -> getRetryable(intValue));
    }

    private String getRetryable(Integer intValue) {
        int rand = new Random().nextInt();
        if (!ObjectUtils.isEmpty(intValue)) {
            rand = intValue;
        }

        if (rand < 0) {
            throw new NullPointerException("값이 음수일수 없습니다.");
        } else if (rand == 0) {
            throw new NumberFormatException("0의 값입니다.");
        } else if (rand % 3 != 0) {
            throw new IllegalStateException("fail retry");
        }

        return String.format("성공 : %s", rand);
    }
}

 

● @Retryable vs Failsafe.RetryPolicy

어노테이션으로 사용할 것인지 소스 코드 안에서 사용할 것인지의 차이며 주기능은 동일하며 옵션만 조금 다르다.

개인적으로는 @Retryable이 더 깔끔하고 직관적이여서 읽기도 편한것 같다. 

 

● 참고 URL

https://jobc.tistory.com/197

https://www.pymoon.com/entry/Java-FailSafe-%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-Retry-%EC%98%88%EC%A0%9C

 

 

'라이브러리' 카테고리의 다른 글

java deep copy 비교  (0) 2022.05.19
image 메타정보 및 색상 추출(rgb, cmyk)  (0) 2021.12.24
bean validation 수동 호출  (0) 2021.06.30
JsonDiff vs MapDifference  (0) 2021.04.20
spring-boot-graphql 설정하기  (1) 2021.03.25
댓글
공지사항