이전에는 MSA의 기본인 Gateway를 구현했습니다. 자 그러면 마이크로 서비스 답게 내부의 서비스들은 각자의 방법으로 메시지를 주고받아야 합니다.
그렇다면 어떤 방법을 사용해야 할까요?
앞에서 만든 Gateway를 사용하는 것도 하나의 방법이 될 수 있습니다. 이 방법을 사용하면 모든 트래픽을 게이트웨이에서 관리하니까 로깅같은 부분에서는 더욱 용이할거라고 생각합니다.
하지만 모든 네트워크 통신이 Gateway라는 단일 진입점을 사용하게 되면, 서비스의 트래픽이 커졌을 때, 게이트웨이의 장애가 전체 서비스의 장애가 될 수 있습니다. 이를 단일 장애점 (Single point of failure) 이라고 합니다.
따라서 각각의 서비스는 peer-to-peer 방식으로 통신이 이루어지는게 이상적인 아키텍처라고 생각합니다.
Service Mesh
마이크로 서비스간에 통신을 담당하는 역할을 하고, Service Mesh는 아래와 같은 특징들이 있습니다.
- Service Discovery
- 로드밸런싱
- 동적 라우팅
- Circuit Breaking
- 암호화(TLS)
- 보안
- Health check, Retry and Timeout
- Metric 수집
개발 전 준비사항
Product Service
- port : 8081
- api : GET /products/{productId}
Display Service
- port : 8080
- api : GET /display/{productId}
실습
Spring boot에서 다른 서비스를 호출하는 가장 먼저 생각나는 방법은 RestTemplate을 이용한 방법입니다.
@Service
public class DisplayService {
private final RestTemplate restTemplate;
public DisplayService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public List<Product> getProducts() {
var products = restTemplate.getForObject("http://localhost:8081/products", Product[].class);
return Arrays.asList(products);
}
}
Blocking-IO에 동기식으로 동작하는 RestTemplate은 향후 Deprecated될 예정이기 때문에 Netty 기반으로 Non Blocking-IO에 비동기식으로 동작하는 WebClient 사용을 권고 하고 있습니다.
WebClient는 나중에 사용해보겠습니다.
또 다른 방법은 Feign Client를 이용한 방법입니다.
Feign은 Netflix에서 개발한 Http Client Binder로 interface를 만들고 annotation을 작성만 해주면 간단하게 웹 서비스 클라이언트를 만들 수 있습니다.
의존성
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
}
프로젝트에 @EnableFeignClients 어노테이션 추가 후 Feign Client로 사용할 인터페이스를 만들어 주면 끝입니다.
사용할 때는 Feign Client 빈을 주입받아 사용합니다.
@EnableFeignClients
public class DisplayApplication {
..
}
@FeignClient(name = "product", url = "http://localhost:8081/")
public interface FeignProductService {
@RequestMapping(path = "/products/{productId}")
Product getProduct(@PathVariable("productId") String productId);
@RequestMapping(path = "/products")
List<Product> getProducts();
}
@Service
public class DisplayService {
private final FeignProductService feignProductService;
public List<Product> getProducts() {
return feignProductService.getProducts();
}
}
이걸로 Service Mesh 구현이 끝난걸까요? 그렇기에는 마땅히 Service Mesh의 특징에 부합되는 게 없습니다. 몇 가지 위에서부터 알아보도록 하겠습니다.
Service Discovery
이름에서 유추해 볼 수 있듯이, 서비스를 발견할 수 있어야 합니다. 지금 Display API는 Product API를 호출 하기 위해 직접 http://localhost:8081/products/로 요청을 보내었습니다. 하지만 서비스의 규모가 많아지거나, 고가용성을 위해 scale-out되어있다면, 매번 서비스의 호출하기 위해 주소를 수정하는 것은 많이 힘들거같습니다.
그런 우리에게 Netflix는 Eureka라는 서비스들의 정보를 레지스트리에 등록하고, 서비스의 동적인 탐색과 로드밸런싱을 제공해주는 오픈소스 프로젝트를 공개했습니다.
Circuit Breaker
MSA는 서비스에 장애가 발생할 경우 이 서비스에 종속된 다른 서비스까지 장애가 전파될 수 있습니다. Circuit Breaker는 회로차단기라는 의미로 MSA에서는 이 Circuit Breaker패턴을 사용해 위와같은 문제를 해결하였습니다. Circuit Breaker는 A라는 서비스에 장애가 발생하면 이 서비스를 호출하는 B 서비스가 A 서비스를 호출할 때 즉각적으로 호출을 끊어 스레드를 점유하지 않도록 해줍니다.
Netflix는 Circuit Breaker 또한 오픈소스로 Hystrix라는 자바 라이브러리를 제공해주고 있습니다.
Load Balancing
MSA는 Scale-out이 용이한 아키텍처인 만큼 고가용성을 위해 로드밸런싱을 수행합니다. Netflix는 Load Balancer 역할을 하는 오픈소스로 Ribbon을 개발했습니다. Ribbon은 클라이언트 사이드의 로드 밸런서로, 여러 서버를 여러 알고리즘으로 로드밸런싱을 제공합니다. (기본 라운드로빈)
이제 Service Mesh에 필요한 요소들을 하나하나 추가해보겠습니다.
github.com/zkdlu/spring-boot/tree/main/spring-service
'Spring' 카테고리의 다른 글
뭐? MSA? 그렇다면 고가용성을 보장해보자! Load Balancer. - 04 (0) | 2021.04.19 |
---|---|
뭐? MSA? 그렇다면 장애 전파를 막아보자! Circuit Breaker. - 03 (0) | 2021.04.16 |
OAuth2.0 + JWT를 사용한 토큰 기반 서버 인증 구현하기 (0) | 2021.04.12 |
TDD.. 테스트 코드는 습관이에요! (0) | 2021.04.02 |
뭐? MSA? 그렇다면 Zuul로 Gateway를 구현해보자. - 01 (0) | 2021.03.30 |