| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
- Spring
- 큐시즘
- 불변객체
- kusitms
- 중간 지점 추천
- 모임 장소 추천
- ddd
- java
- Spring Batch
- 객체지향 쿼리 언어
- GitHub Actions
- cicd
- 최범균
- RESTClient
- redis
- 쿠버네티스
- JPQL
- 모이삼
- 이펙티브자바
- JPA
- Container Registry
- 약속 장소 추천
- 자바 ORM 표준 JPA 프로그래밍
- 도메인 주도 개발 시작하기
- Domain Driven Design
- springboot
- 중간 장소 추천
- 백엔드
- K3S
- 한국대학생it경영학회
- Today
- Total
코딩은 마라톤
[Resilience4j] configs vs instances, 그리고 AspectOrder 문제까지 본문
[Resilience4j] configs vs instances, 그리고 AspectOrder 문제까지
anxi 2025. 10. 2. 21:09최근 외부 API 장애를 대응하기 위해 Timeout, Retry, Circuit Breaker을 적용했다.
특히 Retry, Circuit Breaker는 Resilience4j를 활용해 application.yml에 값을 설정함으로써 개발을 진행했다.
하지만 막상 적용해 보니 우여곡절을 겪어, 이번 글에서는 문제점과 해결 과정에 대해 공유하고자 한다.
이번에 사용한 Resilience4j dependencies
implementation 'io.github.resilience4j:resilience4j-spring-boot3'
implementation 'org.springframework.boot:spring-boot-starter-aop'
1️⃣ configs VS instances
// configs 사용 전
resilience4j:
retry:
instances:
A:
maxAttempts: 3
waitDuration: 1s
enableExponentialBackoff: true
exponentialBackoffMultiplier: 1.5
retryExceptions:
- org.springframework.web.client.RestClientException
- java.io.IOException
- java.util.concurrent.TimeoutException
B:
maxAttempts: 3
waitDuration: 5s
enableExponentialBackoff: false
---
// configs 사용 후
resilience4j:
retry:
configs:
default:
maxAttempts: 3
instances:
A:
baseConfig: default // baseConfig 지정
waitDuration: 1s
enableExponentialBackoff: true
exponentialBackoffMultiplier: 1.5
retryExceptions:
- org.springframework.web.client.RestClientException
- java.io.IOException
- java.util.concurrent.TimeoutException
B:
baseConfig: default
waitDuration: 5s
enableExponentialBackoff: false
configs는 설정 값의 공통분모 역할을 한다.
만약 여러 인스턴스(A, B)의 최대 시도 횟수(maxAttempts)가 동일하다면, configs에 정의하고 각 인스턴스에서 baseConfig를 사용해의 기본 설정을 덮어 씌우는 방식으로 사용할 수 있다.
@Retry(name = "routeApi")
@CircuitBreaker(name = "routeApi", fallbackMethod = "circuitBreakerFallback")
public KakaoMobilityResponse sendRequest(KakaoMobilityRequest request) {
...
}
instances는 서비스에 사용될 설정을 정의하는 인스턴스 역할을 한다.
따라서 @Retry, @CircuitBreaker의 name은 application.yml에 지정한 인스턴스를 사용해야 한다.

인스턴스가 없다면 configs의 default를 가져와 사용한다.
만약 인스턴스가 있을 경우, baseConfig을 default로 설정했다면 default 값을 인스턴스에 덮어씌운다.
따라서 Resilience4j에서 제공하는 기능(Retry, CircuitBreaker...)을 적용하려면 다음 둘 중 하나는 반드시 설정해야 한다.
- 모든 서비스에 공통으로 적용될 configs.default 설정
- 각 서킷 브레이커에 해당하는 instances.[인스턴스명] 설정
2️⃣ Resilience4j의 여러 서비스를 사용할 경우 aspect order 설정하기
‼️ 애노테이션(@Retry, @CircuitBreaker...)을 사용할 경우에만 해당합니다 ‼️
이번에 Retry와 Circuit Breaker를 동시에 적용하게 되면서 아래와 같은 문제를 마주치게 되었다.

내가 기대한 동작은 아래와 같이 재시도 이후 서킷 브레이커를 통해 Open 상태로 변경되어야 했다.

왜 이런 문제가 발생했을까?
애노테이션을 사용할 때, Spring AOP를 기반으로 애노테이션 적용 메소드 호출 전후에 로직이 실행된다.
Resilience4j의 Aspect 실행 순서는 다음과 같다.
Retry ( CircuitBreaker ( RateLimiter ( TimeLimiter ( Bulkhead ( Function ) ) ) ) )
가장 바깥에 위치한 Retry의 순서가 가장 늦고, Bulkhead의 순서가 가장 앞선다.
앞선 문제가 발생한 곳에선 Circuit Breaker가 먼저 수행됐기에 Retry가 동작하지 않은 것이다.
따라서 이 순서를 변경하고 싶다면 AspectOrder를 사용한다.
- resilience4j.retry.retryAspectOrder
- resilience4j.circuitbreaker.circuitBreakerAspectOrder
- resilience4j.ratelimiter.rateLimiterAspectOrder
- resilience4j.timelimiter.timeLimiterAspectOrder
- resilience4j.bulkhead.bulkheadAspectOrder
---
resilience4j:
retry:
retryAspectOrder: 2
instances:
...
circuitbreaker:
circuitBreakerAspectOrder: 1
instances:
...
나는 Retry 먼저 동작하고 Circuit Breaker를 동작시킬 것이기 때문에 retryAspectOrder의 값을 circuitBreakerAspectOrder보다 크게 설정했다.
즉, 먼저 동작시킬 서비스의 order 값을 이전 서비스의 order 값보다 크게 하면 된다. (the higher value = the higher priority).
참고
'Backend > Spring Cloud' 카테고리의 다른 글
| [Spring Cloud] Feign: 선언적 REST Client (0) | 2025.03.08 |
|---|