Spring AOP를 활용하여 CircuitBreaker를 구현해 보자.
Annotation 기반의 AOP를 활용하기 위해 Annotation과 Aspect를 다음과 같이 만들어주자.
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CircuitBreaker {}
@Aspect
@Component
public class CircuitBreakerAspect {
@Around("@annotation(com.ms.circuitbreaker.CircuitBreaker)")
public Object round(ProceedingJoinPoint joinPoint) throws Throwable {
// TODO 세부구현
return joinPoint.proceed();
}
}
CircuitBreaker를 적용할 코드도 하나 만들자
@Service
public class FooService {
@CircuitBreaker
public String getFoo() {
return "foo";
}
}
실패 개수와 마지막 실패 시간을 가지고 있는 metadata와 Exception을 만들자
class CircuitBreakerMetadata {
private final AtomicInteger failures = new AtomicInteger();
private volatile long lastFailure;
CircuitBreakerMetadata() {
}
private long getLastFailure() {
return this.lastFailure;
}
private void setLastFailure(long lastFailure) {
this.lastFailure = lastFailure;
}
private AtomicInteger getFailures() {
return this.failures;
}
}
public final class CircuitBreakerOpenException extends RuntimeException {}
기본적인 뼈대는 만들어졌다.
이제 CircuitBreaker 동작을 Aspect에서 구현해 보자
요구사항은 간단하다.
1. @CircuitBreaker가 선언된 곳에서 에러가 연속으로 5회를 넘게 되면
2. 1초 동안 CircuitBreaker가 발동한다.
3. 에러 없이 성공하면 실패 개수를 초기화한다.
@Aspect
@Component
public class CircuitBreakerAspect {
private static final int THRESHOLD = 5;
private static final long HALF_OPEN_AFTER = 1000;
private static final ConcurrentMap<Object, CircuitBreakerMetadata> METADATA_MAP = new ConcurrentHashMap<>();
@Around("@annotation(com.ms.circuitbreaker.CircuitBreaker)")
public Object round(ProceedingJoinPoint joinPoint) throws Throwable {
final Object target = joinPoint.getTarget();
CircuitBreakerMetadata metadata = METADATA_MAP.get(target);
if (metadata == null) {
METADATA_MAP.putIfAbsent(target, new CircuitBreakerMetadata());
metadata = METADATA_MAP.get(target);
}
if (metadata.getFailures().get() >= THRESHOLD &&
System.currentTimeMillis() - metadata.getLastFailure() < HALF_OPEN_AFTER) {
throw new CircuitBreakerOpenException();
}
try {
final Object result = joinPoint.proceed();
metadata.getFailures().set(0);
return result;
} catch (Exception e) {
metadata.getFailures().incrementAndGet();
metadata.setLastFailure(System.currentTimeMillis());
throw e;
}
}
}
CircuitBrekaer를 구현해 보았다.
Spring에서 제공하는 @CircuitBreaker를 사용하다가 어떻게 구현되어있는지 보니 생각에 비해 간단/간결하게 작성되어 있었다.
사실 위에서 작성한 코드는 spring-integration의 RequestHandlerCircuitBreakerAdvice 클래스이다.
프레임워크와 라이브러리가 제공해 준다고 해서 그저 가져다 쓰기보다는 어떻게 구현되었는지 궁금해하고 찾다 보면 더 좋은 개발자가 되리라 생각한다.
'Spring' 카테고리의 다른 글
Spring Batch 5 뭐가 달라졌나? (2) | 2023.02.19 |
---|---|
WebFlux에서 micrometer로 모니터링 데이터 수집하기 (0) | 2023.01.11 |
JPA를 이용하여 cursor 기반 페이징 구현 (4) | 2019.12.29 |
Spring WebFlux는 어떻게 적은 리소스로 많은 트래픽을 감당할까? (43) | 2019.06.22 |
Spring Cache 구현 원리 (1) | 2019.03.24 |