시작하는 이유
앞선 게시글에서 Feign에서 CircuitBreaker 역할로 Hystrix를 사용해보았고 편리하게 application.yml에 설정을 추가하여 메서드 별로 적용해보았습니다.
하지만 이걸로는 yml 파일이 계속 지저분해지고 분리해야 하는 메서드들이 많아질수록 관리가 적용이 불편해질 것 같았습니다.
그래서 간단하게 Custom Annotation 형식으로 적용하는 예제를 만들어보려 합니다.
개요
Feign Client에서 Hystrix를 이용한 Fallback 설정을 메서드 별로 설정하기 위해 Hystrix Configuration을 Custom Annotation형태로 등록하도록 함
예제 Github
https://github.com/MarrRang/feign-hystrix-study/tree/master
사용법
1. Dependency 등록
우선은 OpenFeign과 Hystrix 그리고 reflection을 활용해야 할 것 같으니 편리하게 하기 위해서 reflections 유틸 Dependency도 추가해줍니다.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.10.2</version>
</dependency>
2. Feign CircuitBreaker 활성화
Feign의 CircuitBreaker를 우선 활성화 시켜줍니다. 이것이 활성화되어있어야 fallback이 작동합니다.
# application.yml
feign:
circuitbreaker:
enabled: true
3. Custom Annotation 작성
Hystrix 개별 적용을 위해 Custom Annotation을 작성합니다. 이 예시에서는 ElementType.TYPE에도 적용이 가능하도록 해두었습니다. 이는 추후에 Feign Client Interface에 적용 시 인터페이스 내부 메서드에 전체 적용이 가능하도록 추가 개발할 예정이어서 해두었습니다. 불편하시다면 ElementType.METHOD만 남겨두셔도 됩니다.
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface CustomFeignHystrix {
String value() default "";
int executionIsolationThreadTimeoutInMilliseconds() default 3000;
int executionTimeoutInMilliseconds() default 3000;
boolean executionTimeoutEnabled() default true;
int metricsRollingStatisticalWindowInMilliseconds() default 10000;
int circuitBreakerRequestVolumeThreshold() default 10;
int circuitBreakerErrorThresholdPercentage() default 50;
int circuitBreakerSleepWindowInMilliseconds() default 5000;
}
4. Configuration 작성
이제 Custom Annotation이 붙은 메서드들을 찾아서 제가 원하는 대로 설정을 추가하도록 Configuration을 작성해보겠습니다.
@Slf4j
@Configuration
public class CustomFeignHystrixConfiguration {
@Bean
public CircuitBreakerFactory CustomHystrixCircuitBreakerFactory() {
HystrixCircuitBreakerFactory circuitBreakerFactory = new HystrixCircuitBreakerFactory();
List<Customizer<HystrixCircuitBreakerFactory>> customizerList = getCircuitBreakerCustomizer();
customizerList.forEach(customizer -> customizer.customize(circuitBreakerFactory));
//default
circuitBreakerFactory.configureDefault(id -> HystrixCommand.Setter
.withGroupKey(HystrixCommandGroupKey.Factory.asKey(id))
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
.withExecutionTimeoutEnabled(true)
.withExecutionTimeoutInMilliseconds(3000)
)
.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
.withCoreSize(30))
);
return circuitBreakerFactory;
}
private List<Customizer<HystrixCircuitBreakerFactory>> getCircuitBreakerCustomizer() {
Reflections reflections = new Reflections(new ConfigurationBuilder()
.setUrls(ClasspathHelper.forPackage("com.example.feignhystrixstudy")).setScanners(Scanners.MethodsAnnotated));
Set<Method> methodSet = reflections.getMethodsAnnotatedWith(CustomFeignHystrix.class);
List<Customizer<HystrixCircuitBreakerFactory>> customizerList = new ArrayList<>();
try {
methodSet.forEach(method -> {
String superClassName = method.getDeclaringClass().getSimpleName();
String methodName = method.getName();
Class<?>[] methodParameterClasses = method.getParameterTypes();
List<String> parameterClassNameList = Arrays.stream(methodParameterClasses).map(Class::getSimpleName).toList();
CustomFeignHystrix customFeignHystrix = method.getAnnotation(CustomFeignHystrix.class);
HystrixCommandProperties.Setter setter = HystrixCommandProperties.Setter()
.withExecutionIsolationThreadTimeoutInMilliseconds(customFeignHystrix.executionIsolationThreadTimeoutInMilliseconds())
.withExecutionTimeoutInMilliseconds(customFeignHystrix.executionTimeoutInMilliseconds())
.withExecutionTimeoutEnabled(customFeignHystrix.executionTimeoutEnabled())
.withMetricsRollingStatisticalWindowInMilliseconds(customFeignHystrix.metricsRollingStatisticalWindowInMilliseconds())
.withCircuitBreakerEnabled(true)
.withCircuitBreakerRequestVolumeThreshold(customFeignHystrix.circuitBreakerRequestVolumeThreshold())
.withCircuitBreakerErrorThresholdPercentage(customFeignHystrix.circuitBreakerErrorThresholdPercentage())
.withCircuitBreakerSleepWindowInMilliseconds(customFeignHystrix.circuitBreakerSleepWindowInMilliseconds());
Customizer<HystrixCircuitBreakerFactory> factoryCustomizer = getCustomHystrixCustomizer(superClassName, methodName, parameterClassNameList, setter);
customizerList.add(factoryCustomizer);
});
} catch (RuntimeException e) {
log.error("CustomHystrix Bean Setting Error : ", e);
}
return customizerList;
}
private Customizer<HystrixCircuitBreakerFactory> getCustomHystrixCustomizer(
String className,
String methodName,
List<String> parameterClassList,
HystrixCommandProperties.Setter options
) {
// Feign에서 찾는 CommandKey가 {클래스명}#{메서드명}({파라미터 클래스명}...) 형태
String id = className + "#" + methodName + "(" + String.join(",", parameterClassList) + ")";
return HystrixCircuitBreakerFactory -> HystrixCircuitBreakerFactory.configure(
hystrixConfigBuilder -> hystrixConfigBuilder.commandProperties(options), id
);
}
}
- CustomHystrixCircuitBreakerFactory() : 각각의 커스텀 한 설정들과 Default 설정을 HystrixCircuitBreakerFactory에 등록하고 그 Factory Bean을 등록하는 메서드
- getCircuitBreakerCustomizer() : Custom Annotation이 붙은 메서드를 찾아서 각각의 설정 내용을 만드는 메서드
- getCustomHystrixCustomizer() : Feign에 맞는 Command Key 값을 만들고 해당 Key(Id)와 사용자 지정 설정을 가진 Customizer를 만들어서 반환
- Feign에서 찾는 Command Key 형태 : {클래스명}#{메서드명}({파라미터 클래스명}...)
5. 사용해보기
@FeignClient(...)
public interface ProviderApiClient {
@CustomFeignHystrix(
executionTimeoutInMilliseconds = 1000
)
@GetMapping(
value = "/sleep",
produces = "application/json",
headers = {"User-Agent=FeignClient", "Cache-Control=no-cache"}
)
String provide(@RequestParam int sleepTime);
}
정리
이 예제는 굉장히 불안정합니다. Feign에서 찾는 Circuit Breaker의 키 값을 하드코딩 형식으로 만들고 설정하는 부분에 있어서 Feign이 조금이라도 수정된다면 바로 사용하지 못하게 됩니다. 그리고 현재는 메서드에만 적용이 가능해서 클래스 전체에 적용하는 것도 추가해야 조금이라도 더 효용성이 높아질 것 같습니다.
그래서 추후에는 아래의 작업을 진행할 예정입니다.
- 클래스에도 적용하도록 추가
- Feign에서 찾는 Circuit Breaker의 키 값의 형식을 직접 지정할 수 있도록 변경해보기
- Hystrix 말고 resilience4j 사용해보기
'Web > 웹 상식' 카테고리의 다른 글
[Web] 헥사고날 아키텍처 알아보기 (0) | 2022.11.06 |
---|---|
[Spring] Spring 6.0 과 Spring Boot 3.0에는 뭐가 달라질까 (4) | 2022.07.28 |
[Spring Cloud] Feign에서 Hystrix 편하게 사용해보기 (0) | 2022.06.30 |
배포 전략에 대해서 (0) | 2022.05.25 |
[Spring] @InitBinder에 대해서 (0) | 2022.03.31 |