Spring Cloud의 컴포넌트 활용
클라우드 네이티브한 애플리케이션을 제작하는 과정은 MSA 형태의 애플리케이션 제작뿐만 아니라 실제 서비스가 유연하게 동작하기 위하여 여러 클라우드 컴포넌트와 연동되어야 한다. 아래는 스프링 클라우드에서 제공하는 대표 클라우드 컴포넌트로써 각가의 역할과 실제 서비스에 적용해 보자.
적용하는 Cloud 컴포넌트
- Circuit Breaker - resilience4j
- Client LoadBalancer -LoadBalanacer
- Service Registry - Eureka
- API Gateway - Zuul
- Config Server
Circuit Breaker - Resilience4j
Hystrix는 한때 서킷 브레이커 구현에 대한 대표적인 솔루션 중 하나였지만, Netflix에서 2018년 말부터 더 이상 Hystrix를 활발히 개발하거나 유지보수하지 않는다는 공지가 있었다. 이에 따라 Hystrix의 커뮤니티 지원도 점점 줄어들었지만 Resilience4j는 활발히 개발되고 있으며, 많은 기능을 제공하고 있다.
Resilience4j를 사용하는 이유는 크게 네가지가 있다:
- 빠르고 가볍습니다: Resilience4j는 Hystrix보다 가볍고 빠르게 설계되었다. Hystrix는 별도의 스레드 풀을 사용하여 격리를 제공하지만, 이는 많은 오버헤드를 발생시킬 수 있다. 반면에 Resilience4j는 자바 8의 함수형 프로그래밍 패러다임을 활용하여 더 적은 오버헤드로 구현되었다.
- 다양한 기능 제공: Resilience4j는 Circuit Breaker, Rate Limiter, Retry, Bulkhead, TimeLimiter 등 다양한 장애 복구 패턴을 지원한다.
- 설정과 모니터링이 쉽습니다: Resilience4j는 Micrometer, Prometheus 등 다양한 모니터링 도구와 통합되어 있어, 기능별로 세밀한 모니터링과 메트릭 수집이 가능하다. 또한 Spring Boot와 잘 통합되어 있어 설정이 간편하다.
- 커뮤니티 지원: Resilience4j는 활발히 유지보수되고 있으며, 꾸준히 업데이트되고 있다.
그럼 이제 Resilience4j의 역할을 알아보자.
- Circuit Breaker: 네트워크 요청이 실패할 경우, 계속해서 요청을 시도하는 것이 아니라 일정 시간 동안 요청을 중단하고 서비스가 복구되길 기다린다. 이는 불필요한 요청을 줄이고, 실패한 서비스에 대한 빠른 복구를 돕는다.
- Rate Limiter: 특정 시간 동안 서버에 보낼 수 있는 요청의 수를 제한. 이는 서버에 과도한 부하가 가해지는 것을 방지하고, 서비스의 안정성을 유지한다.
- Retry: 요청이 실패했을 때 일정 횟수만큼 재시도. 이는 일시적인 네트워크 문제나 서버의 일시적인 과부하 등에 대응할 수 있다.
- Bulkhead: 서비스가 여러 구성 요소로 나눠져 있는 경우, 한 구성 요소에서 문제가 발생해도 다른 구성 요소에 영향을 미치지 않도록 격리한다. 이는 서비스의 전반적인 가용성을 높인다.
- Time Limiter: 네트워크 요청의 응답 시간을 제한한다. 이를 통해 응답이 너무 오래 걸리는 경우 클라이언트의 리소스를 효율적으로 활용할 수 있다.
사용 방법
사용 방법은 resilience4j 공식 문서에서 확인할 수 있다.
https://resilience4j.readme.io/docs/getting-started
Introduction
Resilience4j is a lightweight fault tolerance library designed for functional programming. Resilience4j provides higher-order functions (decorators) to enhance any functional interface, lambda expression or method reference with a Circuit Breaker, Rate Lim
resilience4j.readme.io
1. Catalogs 서비스의 build.gradle에 Resilience4j 2 의존성을 추가한다.
**Java 17버전 이상부터는 Resilience4j 2를 사용해야 한다.
implementation 'io.github.resilience4j:resilience4j-all:2.0.0'
2. Catalogs 서비스에 Resilience4jConfiguration을 작성한다. (설명은 주석 참조)
@Configuration
public class Resilience4jConfiguration {
// 이 메소드는 CircuitBreakerConfig 객체를 생성하고, Spring IoC 컨테이너가 관리하는 Bean으로 등록합니다.
@Bean
public CircuitBreakerConfig circuitBreakerConfig() {
return CircuitBreakerConfig.custom() // 새로운 CircuitBreakerConfig를 생성합니다.
.failureRateThreshold(50) // 실패율 임계값을 50%로 설정합니다.
.waitDurationInOpenState(Duration.ofMillis(1000)) // 회로 차단기가 OPEN 상태로 유지되는 기간을 1000밀리초(1초)로 설정합니다.
.permittedNumberOfCallsInHalfOpenState(2) // HALF_OPEN 상태에서 허용되는 호출 횟수를 2로 설정합니다.
.slidingWindowSize(2) // 회로 차단기의 슬라이딩 윈도우 크기를 2로 설정합니다.
.recordExceptions(IOException.class, TimeoutException.class) // IOException 및 TimeoutException를 기록하도록 설정합니다.
.build(); // 설정에 따라 CircuitBreakerConfig 객체를 생성합니다.
}
@Bean // 이 메소드는 CircuitBreakerRegistry 객체를 생성하고, Spring IoC 컨테이너가 관리하는 Bean으로 등록합니다.
public CircuitBreakerRegistry circuitBreakerRegistry(CircuitBreakerConfig circuitBreakerConfig) {
return CircuitBreakerRegistry.of(circuitBreakerConfig); // 주어진 CircuitBreakerConfig를 사용하여 CircuitBreakerRegistry를 생성합니다.
}
@Bean // 이 메소드는 CircuitBreaker 객체를 생성하고, Spring IoC 컨테이너가 관리하는 Bean으로 등록합니다.
public CircuitBreaker circuitBreaker(CircuitBreakerRegistry circuitBreakerRegistry) {
// CircuitBreakerRegistry에서 "name"이라는 이름의 CircuitBreaker를 가져옵니다. 만약 해당 이름의 CircuitBreaker가 존재하지 않으면 새로 생성합니다.
return circuitBreakerRegistry.circuitBreaker("name01");
}
}
3. Catalogs 서비스의 CustomerApiService.java의 getCustomerDetail 메소드에 아래와 같은 코드를 추가하고 Fallback 메소드를 추가 및 작성한다. (설명은 주석 참조)
@Service // 이 클래스를 Spring IoC 컨테이너가 관리하는 Service 클래스로 선언합니다.
@RequiredArgsConstructor // Lombok 어노테이션으로서 final 또는 @NonNull 필드값으로 생성자를 자동 생성합니다.
public class CustomerApiService {
private final RestTemplate restTemplate; // HTTP 통신을 담당하는 Spring의 RestTemplate 객체
private final CircuitBreaker circuitBreaker; // Resilience4j의 CircuitBreaker 객체
public String getCustomerDetail(String customerId){
// CircuitBreaker를 사용하여 RestTemplate 호출을 장식하는 공급자를 생성합니다.
Supplier<String> decoratedSupplier = CircuitBreaker
.decorateSupplier(circuitBreaker, () -> restTemplate.getForObject("http://localhost:8082/customers/"+customerId, String.class));
// 장식된 공급자를 실행하고 발생할 수 있는 예외에서 복구합니다.
return Try.ofSupplier(decoratedSupplier)
.recover(throwable -> getCustomerDetailFallback(customerId, throwable)).get(); // 예외가 발생하면 fallback 메서드를 호출합니다.
}
// 예외가 발생했을 때 호출되는 fallback 메서드
public String getCustomerDetailFallback(String customerid, Throwable ex){
System.out.println("Error: "+ex.getMessage()); // 에러 메세지 출력
return "고객정보 조회가 지연되고 있습니다."; // 사용자에게 보여줄 메세지를 반환합니다.
}
}
**여기서 Try에 import하라고 에러가 나타나는데, 구글 검색해서 의존성을 추가하자.
4. 원활한 테스트를 위하여 Customers 서비스에 강제로 Exception을 발생하도록 한다.
Customers 서비스의 CustomerController.java를 아래와 같이 수정한다.
//CustomersController.java
@RestController
@RequestMapping("/customers")
public class CustomersController {
@GetMapping("/{customerId}")
public String getCustomerDetail(@PathVariable String customerId){
throw new RuntimeException("I/O Exception");
// System.out.println("request customerId : "+customerId);
//
// return "[Customer id = " + customerId + " at " + System.currentTimeMillis() + "]";
}
}
4. resilience4j 구동 테스트
각각의 Catalogs 서비스 및 Customers 서비스를 구동하고 아래와 같이 테스트 URL에 접속한다.
http://localhost:8081/catalogs/customerinfo/1234
Client Load Balancer - LoadBalancer
Ribbon은 Client에 탑재할 수 있는 소프트웨어 기반의 Load Balancer이다.
일반적으로 사용하는 하드웨어적인 L4 Switch를 사용하지만, MSA에서는 소프트웨어적으로 구현된 Client-Side Load Balancing으로 주로 사용한다. Ribbon은 분산 처리 방법으로 여러 서버를 라운드 로빈 방식으로 부하 분산 기능을 제공한다.
Ribbon의 구성요소로써 Rule, Ping, ServerList가 있다.
1. Rule - 요청을 보낼 서버를 선택하는 논리
- Round Robbin - 한 서버씩 돌아가면서 전달( 기본 설정 )
- Available Filtering - 에러가 많은 서버를 제외
- Weighted Response Time - 서버별 응답 시간에 따라 확률 조절
2. Ping - 서버가 살아 있는지 체크하는 논리
- Static, dynamic 모두 가능
3. ServerList - 로드 밸런싱 대상 서버 목록
- Configuration을 통해 static하게 설정 가능
- Eureka 등을 기반으로 dynamic하게 설정 가능
Ribbon 라이브러리 적용
Ribbon을 각 서비스를 호출하는 서비스인 Catalogs 서비스에 적용하도록 하겠다.
Ribbon의 @LoacBalanced를 RestTemplate에 적용한다.
1. build.gradle에 Ribbon 의존성을 추가한다.
2018년 12월 이후 Ribbon은 EOS되었기 때문에 Spring Cloud LoadBalancer를 사용하는것이 좋다.
그러므로 Spring Cloud LoadBalancer로 적용할 것이다.
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:2022.0.3"
}
}
implementation 'org.springframework.cloud:spring-cloud-starter-loadbalancer'
2. CatalogsApplication.java의 RestTemplate에 @LoadBalanced 어노테이션 추가
3. CustomerApiService.java의 Customer 서비스를 호출하는 URL주소를 명시적으로 변경한다.
localhost:8082 -> customer
4. Application.properties에서 LoadBalancer 설정을 추가한다.
spring.cloud.discovery.client.simple.instances.customer[0].uri=http://localhost:8082
5. Customer 서비스의 강제 예외 발생을 제거하고, 원래 코드로 되돌린다.
6. Spring Cloud Load Balancer 구동 테스트
http://localhost:8081/catalogs/customerinfo/1234
접속하여 Load Balancer가 구동되는지 확인한다.
'DEV > MSA' 카테고리의 다른 글
MSA 개발 가이드(6) - Spring Cloud Gateway(Zuul) (0) | 2023.06.19 |
---|---|
MSA 개발 가이드(5) - Spring Cloud Eureka(Service Registry) (0) | 2023.06.19 |
MSA 개발 가이드(3) - 마이크로 서비스 아키텍처 구현해보기 (0) | 2023.06.16 |
MSA 개발 가이드(2) - Spring Cloud 기반 마이크로서비스(MSA) (0) | 2023.06.16 |
MSA 개발 가이드(1) - MSA란 무엇인가 (0) | 2023.06.16 |