Server/Spring Boot
[Springboot] 외부 HTTP 호출 Retry와 재처리 전략
hoonylab
2025. 4. 15. 14:28
728x90
반응형
외부 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
반응형