일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- mysql
- catalina.out
- 자바
- redis + spring boot 함께
- select
- docker 컨테이너로 띄우기
- architecture
- jQuery
- Java
- js
- for문
- 리눅스
- sftp
- 정규식
- 자바스크립트
- aws elasticache 활용
- 만들면서 배우는 클린 아키텍처
- insert
- 엔티티 코드 치환
- Tomcat
- Docker Compose
- springboot+redis
- javascript
- 특수문자 치환
- Entity Code 치환
- 제이쿼리
- Linux
- 톰캣
- 초단위
- feignClient
- Today
- Total
꾸준하게, 차근차근
[Spring] FeignClient의 재시도(Retry), 너란 녀석! 본문
드디어 입사하고 첫 서비스가 운영에 배포되는 날이었다.
출근하자마자 각 서버의 인스턴스들을 모니터링하고, 모든 서비스가 배포된 후 운영 테스트가 진행되었다. 🫣
1. 문제 상황
한참 운영 테스트가 진행되고 있는 와중에 서비스에서 예외가 발생했다. 😱
확인 결과, 데이터 취득을 위해 타 서비스로 API를 요청하는데 이 과정에서 400(Bad Request) 응답 코드를 받게 된 것이었다. 🤯
결론적으로 요청을 받는 서비스 측의 문제로 문제가 해결되기를 기다릴 수밖에 없었지만, 문득 의아한 생각이 들었다.
정책상 위 과정에서 예외가 발생할 경우 2회 재시도가 발생했어야 하는데? 로그상으로는 1회 요청으로 끝난 것이다. 😳
그래서 어김 없이 오늘도 원인을 파악하기 위해 삽질이 시작되었다.
2. Feign의 기본 Retryer 설정
Feign으로 API 통신 중, 장애 상황이나 네트워크 불안정으로 재시도(Retry) 설정을 한다.
하지만, 이번 상황에서는 재시도(Retry)가 일어나지 않았다. 😳
FeignClient에서는 재시도 횟수, 간격을 지정한 Retryer를 Bean으로 등록한 설정 클래스를 @FeignClinet 어노테이션의 configuration 속성에 설정해주면 예외가 발생했을 경우 재시도(Retry)를 시도한다.
@Bean
public Retryer retryer() {
// (initialInterval, maxInterval, maxAttempts)
return new Retryer.Default(100, 1000, 3); // 최대 3회 시도
}
나는 위 예제와 같이 설정해두면 API 요청이 실패할 경우 무조건 재시도를 할 줄 알았다.
3. Feign의 Retryer와 ErrorDecoder, 그리고 상태 코드
하지만 Feign의 재시도는
- 네트워크 예외(IOException 등)
- RetryableException
이 두 가지가 발생할 때만 동작한다.
HTTP 상태 코드는 보통
- ErrorDecoder에서 FeignException으로 변환
- 4xx, 5xx 둘 다 그냥 FeignException 발생
- RetryableException이 아니라서, Retryer는 재시도 안함
즉, Retryer는 FeignException에는 반응하지 않고, RetryableException일 때만 재시도를 한다.
4. 공식 문서 Search-Search
Spring Cloud OpenFeign 공식 문서에도 아래처럼 명시되어 있다.
Spring Cloud OpenFeign은 기본적으로 재시도가 꺼져 있고,
Retryer를 등록해야만 "네트워크 오류(IOException)"와 "RetryableException"에 대해 재시도가 동작한다.
4xx, 5xx는 커스텀 ErrorDecoder에서 RetryableException을 던지지 않는 한 기본적으로 재시도되지 않는다. 라는 의미!
5. 만약 특정 상태 코드에서만 재시도하려면?
이런 경우엔 ErrorDecoder를 커스터마이징해서 특정 상태 코드에서만 RetryableException을 직접 throw 해야 한다.
public class CustomErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultDecoder = new Default();
@Override
public Exception decode(String methodKey, Response response) {
if (response.status() == 400) {
System.out.println("### [ErrorDecoder] " + response.status() + "에서 RetryableException 발생");
return new RetryableException(
response.status(),
"retry on " + response.status(),
response.request().httpMethod(),
(Long) null,
response.request()
);
}
return defaultDecoder.decode(methodKey, response);
}
}
그리고 Config에 등록
@Configuration
public class FeignConfig {
@Bean
public Retryer retryer() {
return new Retryer.Default(100, 1000, 3);
}
@Bean
public ErrorDecoder errorDecoder() {
return new CustomErrorDecoder();
}
}
이렇게 하면 400에서만 재시도한다.
6. 그럼 테스트를 해봐야지!
테스트를 위한 나머지 코드를 작성해보자.
@FeignClient(
name = "test-api",
url = "http://localhost:8080",
configuration = FeignConfig.class
)
public interface TestFeignClient {
@GetMapping("/remote/test")
String callRemote();
}
@RestController
@RequiredArgsConstructor
public class TestController {
private int callCount = 0;
@GetMapping("/remote/test")
public ResponseEntity<String> testApi() {
callCount++;
System.out.println("#### [Server] 호출 횟수: " + callCount);
return ResponseEntity.status(400).body("Bad Gateway");
}
private final TestFeignClient testFeignClient;
@GetMapping("/feign/test")
public String feignTest() {
try {
return testFeignClient.callRemote();
} catch (Exception e) {
System.out.println("### [FeignClient] 최종 예외 발생: " + e.getClass().getSimpleName() + " / " + e.getMessage());
throw e;
}
}
}
위와 같이 코드를 작성하고 포스트맨으로 호출한 결과!
최초 1회 + 재시도 2회를 시도한 것을 확인할 수 있었다.
하지만 여기서 드는 궁금증 🤔
오늘 운영 테스트 중 발생한 예외에서 ErrorDecoder를 커스터마이징 + 등록하지 않아서 재시도가 정말 안된게 맞을까?
CustomErrorDecoder를 주석 처리하고 다시 요청을 시도해보자.
정말 Feign의 기본 Retryer는 4xx 응답코드에 대해선 재시도를 하지 않는다. 😆
뿌듯한 삽질이었다. 🙂🙃🙂🙃
END.
'Spring' 카테고리의 다른 글
[트러블슈팅 회고] JPA 엔티티 단순 조회만 했는데 update 쿼리가 남발된 이유 (0) | 2025.07.14 |
---|---|
[Spring] 스케줄러에서 ShedLock 실행 누락, ThreadPoolTaskScheduler, 그리고 millisecond의 함정 (0) | 2025.06.17 |
[Spring] Querydsl Gradle 설정 (Spring Boot 3.0 이상) (0) | 2024.04.18 |