-
[Springboot] 외부 HTTP 호출 Retry와 재처리 전략Server/Spring Boot 2025. 4. 15. 14:28728x90반응형
외부 API를 호출할 때는 항상 실패 가능성을 염두에 두고 안정적인 Retry 정책을 세워야 합니다. 이 글에서는 Spring Boot 환경에서 Resilience4j를 활용한 Retry와 재시도 큐(DB + 스케줄러) 전략을 함께 설명합니다.
✅ 1. 어떤 요청에 Retry를 적용해야 할까?
- Retry 가능한 요청
- GET, HEAD, OPTIONS 같은 멱등한 요청
- HTTP 5xx 또는 네트워크 예외 (timeout 등)
- Retry 피해야 할 요청
- POST, PUT, DELETE 같은 멱등하지 않은 요청
- 비즈니스 오류나 중복 처리 위험이 있는 경우
✅ 2. 어떤 예외/상태코드에 Retry를 걸어야 할까?
- Retry 권장: SocketTimeoutException, ConnectTimeoutException, HTTP 502/503/504
- Retry 피함: HTTP 400/401/403/404 또는 사용자 요청 오류
✅ 3. Retry 간격(backoff) 전략
- Fixed delay: 일정 간격으로 재시도 (ex. 3초마다)
- Exponential backoff: 1초 → 2초 → 4초 등 점점 늘리기
- Jitter: 랜덤 요소 추가해 트래픽 폭주 방지
resilience4j.retry: instances: myRetry: max-attempts: 3 wait-duration: 2s retry-exceptions: - java.io.IOException - java.net.SocketTimeoutException ignore-exceptions: - com.example.exception.BusinessException
✅ 4. Resilience4j란?
Resilience4j는 마이크로서비스 환경에서 흔히 사용하는 장애 대응(Fault Tolerance) 라이브러리입니다.
- Retry - 실패 시 자동 재시도
- Circuit Breaker - 계속 실패 시 요청 차단
- Rate Limiter - 트래픽 제한
- Time Limiter - 시간 초과 제어
- Bulkhead - 리소스 격리
장점: 가볍고, Spring Boot와 궁합 좋으며, Micrometer 기반 모니터링 가능
@Retry(name = "myRetry", fallbackMethod = "fallback") @CircuitBreaker(name = "myCircuit", fallbackMethod = "fallback") public String callExternalApi() { return restTemplate.getForObject("https://external-api.com", String.class); } public String fallback(Exception ex) { return "기본 응답값"; }
✅ 5. Retry 후에도 실패하면? → DB 저장 + 스케줄러 재시도
Retry로도 실패할 경우, 요청을 DB에 저장하고 스케줄링으로 재시도하는 방식이 실무에서 많이 사용됩니다.
📌 흐름
- HTTP 요청 시도 → 실패
- Retry 시도 → 계속 실패
- 요청 정보를 DB에 저장
- 스케줄러가 주기적으로 조회해서 다시 시도
📋 테이블 예시
CREATE TABLE failed_requests ( id BIGINT AUTO_INCREMENT PRIMARY KEY, request_url VARCHAR(255), payload TEXT, headers TEXT, retry_count INT DEFAULT 0, status VARCHAR(20) DEFAULT 'PENDING', last_attempt_at TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
⚙️ 실패 저장 코드 예시
try { restTemplate.postForObject(url, payload, String.class); } catch (Exception ex) { failedRequestRepository.save(new FailedRequest(url, payload, headers)); }
⏱ 스케줄러 재시도 코드
@Scheduled(fixedDelay = 60000) public void retryFailedRequests() { List<FailedRequest> list = repository.findPending(); for (FailedRequest req : list) { try { restTemplate.postForObject(req.getUrl(), req.getPayload(), String.class); req.markSuccess(); } catch (Exception ex) { req.incrementRetry(); if (req.getRetryCount() > 5) { req.markFailed(); } } repository.save(req); } }
✅ 마무리 정리
- 1차 방어: Resilience4j로 Retry 설정
- 2차 보완: DB에 실패 요청 저장
- 3차 복구: 스케줄러로 재시도 및 알림/모니터링
이 방식은 결제 시스템, 외부 정산 API, 메시지 연동 등에서 데이터 유실 없이 안정적인 운영을 가능하게 합니다.
🎁 더 나아가고 싶다면 Kafka나 Redis Stream을 활용한 비동기 재처리 구조도 고려해볼 수 있습니다.
728x90반응형'Server > Spring Boot' 카테고리의 다른 글
MapStruct 의 @Mapping 사용 방법을 알아보자! (1) 2025.04.16 MapStruct 의 @Mapper 사용에 대해 알아보자! (0) 2025.04.16 [Springboot] 엔티티와 DTO 간 변환시 MapStruct 사용해야 하는 이유? (0) 2025.04.16 Springboot Timezone(타임존) 에 대해 알아보자! (1) 2025.04.15 [Springboot] 스프링부트 개요 (0) 2023.01.18 - Retry 가능한 요청