2.12 재시도와 복원력 (Retry / Resilience4j)
외부 API·DB·메시지 브로커 호출은 일시적 장애로 실패할 수 있습니다. 재시도(Retry) 와 서킷 브레이커로 실패를 흡수하고, 장애 전파를 줄일 수 있습니다.
작성 기준: Spring Boot 3.2.x, spring-retry 또는 Resilience4j
1. Spring Retry (@Retryable)
implementation 'org.springframework.retry:spring-retry'
@SpringBootApplication
@EnableRetry
public class Application { ... }
@Service
public class PaymentClient {
@Retryable(
retryFor = { RestClientException.class },
maxAttempts = 3,
backoff = @Backoff(delay = 1000, multiplier = 2)
)
public PaymentResult callExternalApi(PaymentRequest req) {
return restTemplate.postForObject(...);
}
@Recover // 모든 재시도 실패 후 호출
public PaymentResult recover(RestClientException e, PaymentRequest req) {
log.error("결제 API 호출 실패, 폴백", e);
return PaymentResult.failed("일시적 오류");
}
}
- retryFor: 이 예외일 때만 재시도
- maxAttempts: 최대 시도 횟수
- backoff: 대기 시간 (delay ms, multiplier로 증가)
- @Recover: 시그니처가 (Exception, 메서드 인자...)와 일치해야 함
2. Resilience4j — 서킷 브레이커
연속 실패 시 일정 시간 호출을 차단해 리소스를 보호하려면 Resilience4j를 사용합니다.
implementation 'io.github.resilience4j:resilience4j-spring-boot3'
resilience4j:
circuitbreaker:
instances:
paymentApi:
registerHealthIndicator: true
slidingWindowSize: 10
minimumNumberOfCalls: 5
failureRateThreshold: 50
waitDurationInOpenState: 10s
@Service
public class PaymentService {
private final CircuitBreaker circuitBreaker;
private final PaymentClient client;
public PaymentService(CircuitBreakerRegistry registry, PaymentClient client) {
this.circuitBreaker = registry.circuitBreaker("paymentApi");
this.client = client;
}
public PaymentResult pay(PaymentRequest req) {
return circuitBreaker.executeSupplier(() -> client.callExternalApi(req));
}
}
- failureRateThreshold: 실패 비율이 이 값을 넘으면 서킷이 OPEN (호출 차단)
- waitDurationInOpenState: OPEN 상태 유지 시간 후 HALF_OPEN으로 전환해 재시도
3. Retry + CircuitBreaker 조합
Resilience4j는 Retry와 CircuitBreaker를 같이 등록할 수 있습니다.
먼저 Retry로 몇 번 재시도하고, 그래도 실패율이 높으면 CircuitBreaker가 열리도록 구성하는 패턴이 많습니다.
팁
재시도할 예외를 retryFor로 한정하고, 비즈니스 예외(4xx 등)는 재시도하지 않도록 구분하는 것이 좋습니다. 외부 의존성 타임아웃은 RestTemplate/WebClient 설정과 함께 조정하세요.