Spring/Spring
Spring Retry로 안전한 Feign 호출 처리하기
ghyeong
2024. 11. 20. 12:44
비즈니스 로직을 처리하다 보면 비동기적으로 데이터가 생성되거나, 외부 API 호출 시 타이밍 이슈로 데이터가 준비되지 않아 실패하는 상황을 자주 겪게 됩니다. 특히, Feign 클라이언트를 사용할 때 외부 API가 아직 데이터 준비를 마치지 않은 상태라면 null 값이나 예외가 발생할 수 있습니다.
최근 저 역시 이런 문제를 경험했는데, 이를 해결하기 위해 Spring Retry를 활용해 재시도 로직을 도입했고, 최종적으로 실패한 경우에만 알림을 전송하는 구조로 개선했습니다. 이 과정을 공유하고자 합니다.
문제 상황
- Feign 호출 실패
외부 API 호출 시 데이터가 아직 생성되지 않아 예외(null)가 발생. - 재시도 필요
외부 시스템이 데이터를 생성하는 데 약간의 지연 시간이 필요하므로, 요청을 일정 시간 간격으로 재시도할 필요가 있었음. - 최종 실패 처리
모든 재시도가 실패한 경우에만 텔레그램으로 알림을 보내야 했음. 기존 로직에서는 예외 발생 시 즉시 알림이 전송되었기 때문에 불필요한 경고가 너무 많이 발생.
Spring Retry 도입
Spring Retry를 활용하여 아래와 같은 동작을 구현했습니다.
- Feign 호출 실패 시 최대 3번까지 재시도.
- 재시도 간격은 1초.
- 모든 재시도가 실패한 경우, 텔레그램 알림 전송.
구현 코드
1. Feign Client 정의
먼저 Feign 클라이언트를 정의합니다.
@FeignClient(name = "example-service", url = "http://example.com")
public interface ExampleFeignClient {
@PostMapping("/api/data")
ResponseDto fetchData(@RequestBody RequestDto requestDto);
}
2. 서비스 클래스에 @Retryable 적용
@Retryable 애노테이션을 사용해 재시도 로직을 추가합니다. 모든 재시도가 실패하면 @Recover 메서드가 호출됩니다.
@Service
public class ExampleService {
private final ExampleFeignClient feignClient;
private final TelegramService telegramService;
public ExampleService(ExampleFeignClient feignClient, TelegramService telegramService) {
this.feignClient = feignClient;
this.telegramService = telegramService;
}
@Retryable(
value = { FeignException.class }, // 재시도 대상 예외
maxAttempts = 3, // 최대 재시도 횟수
backoff = @Backoff(delay = 1000) // 재시도 간격 (1초)
)
public void fetchDataAndProcess(RequestDto requestDto) {
ResponseDto response = feignClient.fetchData(requestDto);
if (response == null || !response.isSuccessful()) {
throw new FeignException.BadRequest("API 호출 실패");
}
// 비즈니스 로직 처리
processResponse(response);
}
private void processResponse(ResponseDto response) {
// 응답 데이터 처리 로직
System.out.println("응답 처리 완료: " + response);
}
@Recover
public void handleFailure(FeignException e, RequestDto requestDto) {
// 최종 실패 시 텔레그램 알림 전송
telegramService.sendMessage(
"데이터 처리 실패\n요청 데이터: " + requestDto + "\n에러 메시지: " + e.getMessage()
);
System.err.println("최종 실패 처리: " + e.getMessage());
}
}
동작 설명
- Feign 호출
fetchDataAndProcess 메서드는 ExampleFeignClient를 호출해 데이터를 가져옵니다. - Retryable
호출 중 FeignException이 발생하면 최대 3번까지 재시도하며, 각 재시도는 1초 간격으로 이루어집니다. - Recover
재시도가 모두 실패하면 handleFailure 메서드가 호출되어 텔레그램으로 실패 알림을 전송합니다.
원래 로직의 경우 실패시에는 무조건 텔레그램을 발송하는 로직이 있었습니다.
저는 retry를 할 경우 3번의 시도중에 실패하는 경우마다 텔레그램이 발송이 되기 때문에 모든 retry가 실패했을 경우에만 텔레그램을 받고 싶어 recover를 통해 retry가 모두 실패했을 경우에만 텔레그램을 발송할 수 있도록 하여 이렇게 로직을 수정 하였습니다.
반응형
장점
- 자동 재시도: 실패 상황에서 수동으로 재시도를 구현하지 않아도 되므로 코드가 깔끔해집니다.
- 최종 실패 처리: @Recover를 사용해 모든 시도가 실패했을 때만 별도로 처리할 수 있습니다.
- 재시도 정책 설정 가능: 재시도 간격, 최대 횟수 등 정책을 유연하게 설정할 수 있습니다.
적용 시 주의사항
- 비동기 작업과 병렬 처리
재시도가 필요한 작업이 많을 경우, 병렬 처리 시 스레드 충돌이 발생하지 않도록 주의해야 합니다. - 재시도 정책 과도 설정 방지
재시도 횟수나 간격을 과도하게 설정하면 시스템 부하가 증가할 수 있으니 적절히 조율해야 합니다. - 장애 감지
@Recover에서는 알림을 보내는 외에도, 실패시에 발생 할 수 있는 장애 모니터링 시스템과 연동하면 더욱 효과적입니다.
결론
Spring Retry는 재시도가 필요한 로직을 깔끔하고 유연하게 처리할 수 있는 방법입니다. 특히, Feign 클라이언트와 같이 외부 시스템과의 통신에서 발생하는 타이밍 이슈를 완화하는 데 유용합니다.