본문 바로가기
Framework/🍃Spring

Spring cloud를 통해 MSA 구현해보기 - 2탄 Eureka를 통한 클라이언트 사이드 서비스 디스커버리 패턴 구현

by 발개발자 2022. 1. 19.
반응형

자, 이제 차분하게 MSA를 구성해보기 위한 밑그림을 그려보자..

 

MSA 각각 흩어진 서비스간의 원격 호출로 구성된다.

원격 호출을 하기위해 각각 서버에 대한 IP PORT 이용한다.

기존 하드웨어 기반의 시스템에서의 서비스 인스턴스는 상대적으로 정적이다. 

그러나 최근 많이 사용하는 클라우드 기반 환경에선 네트워크 정보가 동적으로 바뀔 있다.

그러면 이런 동적으로 변하는 네트워크 정보를 어떻게 관리해야할까? 유용하게 쓰는 방법이 있다.

MSA처럼 흩어져있는 분산시스템들의 수시로 변하는 네트워크 정보에 관한 관리포인트를 줄이기 위해 사용하는 패턴이 서비스 디스커버리 패턴(Service Discovery) 이다.

 

  • Service Discovery
 각 서비스들의 네트워크 위치 정보(IP, Port)를 저장하고 관리하며 연결시켜준다.

 

 

이러한 Service discovery 패턴을 구현하는 방법으로는 크게 Clientside Service discovery 방식과 Server side Service discovery 방식이 있다.

 

1) Client Side Service Discovery

Service Client Service Registry에서 서비스의 위치를 찾아서 호출 하는 방식을 Clientside Service discovery 라고 한다.

따라서, Service Client는 Service registry 사용 가능한 서비스 인스턴스를 질문하여, 사용 가능한 서비스 인스턴스의 네트워크 위치를 결정하고 해당 서비스인스턴스에 RR 기반으로 로드밸런싱을 통해 요청을 보내는 책임을 갖는다. 

본인은 Client Side Discovery 패턴을 공부하며 클라이언트의 주체에 대해 혼동이 많았는데, 아래의 사진과 같이 클라이언트의 주체는 서비스를 요청하는 또 다른 서비스 인스턴스이다.

 

Netflix OSS는 클라이언트 측 검색 패턴의 좋은 예다. 그 중, 넷플릭스 유레카는 Service Registry역할을 하며, 서비스 인스턴스 등록을 관리하고 사용 가능한 인스턴스를 쿼리하기 위한 REST API를 제공해준다. Netflix 리본은 유레카와 연동되어 사용 가능한 서비스 인스턴스에게 요청을 로드밸런싱을 해주는 프로세스간 통신 클라이언트이다. 

 

이 패턴의 장점과 단점을 알아보자.

먼저 장점은, 서비스 레지스트리만 있으면 되기 때문에 비교적 간단하다.

또한 클라이언트가 사용 가능한 서비스 인스턴스에 대해 알고 있기 때문에 알아서 로드 밸런싱을 할 수 있다.

반면에, 이 패턴의 한 가지 중요한 단점은 클라이언트가 서비스 레지스트리에게 의존성을 가진다는 점이다.

즉, 각 서비스마다 서비스레지스트리를 구현해야하며, 만약 서비스마다 다른 언어를 사용하고 있다면 언어별 또는 프레임워크별로 구현해야 한다.

 

 

 

2) Server Side Service Discovery

서비스 앞에 일종의 proxy 서버 (로드밸런서)를 넣어, 서비스 클라이언트는 이 로드밸런서를 호출하면 로드밸런서가 Service registry로 부터 등록된 서비스의 위치를 리턴하고, 이를 기반으로 라우팅을 하는 방식이다.

Server Side Service Discovery 패턴의 예로는 Aws ELB와 구글 로드밸런서가 있다. 각각의 프록시 서버가 서비스 레지스트리를 통해 인스턴스를 질문하고 요청을 로드밸런싱을 해준다.

 

 

Server Side Service Discovery 패턴에는 몇 가지 이점과 단점이 있다. 이 패턴의 한 가지 큰 장점은 검색 세부 사항이 클라이언트에서 추상화된다는 점이다. 클라이언트는 단순히 프록시 서버에 요청을 하면 서비스 클라이언트에서 사용하는 각 프로그래밍 언어 및 프레임워크 별로 구현할 필요가 없다.

이에 반해 로드 밸런서가 설정 및 관리해야 하는 또 다른 가용성 시스템 구성 요소라는 점이 관리포인트가 늘어나게 된다.

 

 

흠,,, 여기서 본인이 구현하고자 하는 목표는 2개이다.

 

  • EurekaServer와 Service Client를 구현 후, Ribbon을 통해 라운드로빈방식으로 호출하는 ClientSide Service discovery 패턴의 구현  
  • Eureka Server와 Service Client를 구현 후, Spring Cloud API Gateway를 서비스 앞단에 위치시켜 ServerSide Service Discovery 패턴의 구현

 

일단 1번을 먼저 구현해보자.

 

  • 시나리오

NetFlix Eureka 통해 Eureka Server(서비스 레지스트리) 구현한 ,

Service-a, Service-b, Service-b 3개의 Eureka 클라이언트 서비스 를 구성하여

Service-a에서 2개의 인스턴스인 Service-b를 라운드로빈 방식으로 호출하는 ClientSide Service discovery를 구현해보자.

 

 

* Eureka 서버 구현

 

  •  Spring starter를 이용해 Eureka Server Dependency를 추가한 후 프로젝트 생성

 

 

  • Application.yml 작성
spring:

  application:

    name: eureka-server

   

server:

  port: 8761





eureka:

  client:

    register-with-eureka: false   # Eureka 서버에 클라이언트로 등록 여부

    fetch-registry: false         # Eureka 서버로부터 받은 서비스 리스트에 대한 캐싱여부

 

   

spring.application.name

Eureka 서버에서 서비스를 식별하는 id이다.

 

eureka.client.register-with-eureka

유레카 서버에 자기 자신을 클라이언트로 등록하지 않도록 하는 설정이다.

본 프로젝트는 디스커버리 서버 역할을 하는 유레카 서버이므로 자기 자신을 클라이언트로써 디스커버리 서버에 등록하지 않도록 false로 설정한다.

 

eureka.client.fetch-registry

클라이언트로써 eureka 서버에서 eureka 레지스트리 정보를 가져올지 여부를 설정한다.

위 설정과 마찬가지로 클라이언트가 아니므로 false로 설정한다.

 

 

 

 

  • 어노테이션 추가

 

mainApplication.java

@SpringBootApplication
@EnableEurekaServer
public class EruekaApplication {

public static void main(String[] args) {
	SpringApplication.run(EruekaApplication.class, args);
	}
}

 

 

yml 설정과 단순 어노테이션만으로 Eureka 서버를 구축하는데 완료했다.

 

 

 

서버를 실행시켜보면 아직 클라이언트는 등록되지 않은 Eureka 서버를 조회할 있다.

 

 

그렇다면 이제 클라이언트 2개의 서비스를 구축해보자.

 

  • Spring starter를 이용해 Spring Web, Eureka Discovery Clent Dependencies를 추가한 후 프로젝트 생성 (Service-A)

 

 

 

  • Applicataion.yml 작성
spring:

  application:

    name: service-a        # 서비스를 구분할 name

server:

  port: 8801

eureka:

  instance:

    instance-id: inst001   # 동일한 서비스 name에서 인스턴스 구분자

  client:

    service-url:

      defaultZone: http://localhost:8761/eureka/     # 클라이언트가 속할 Eureka Server

 

 

 

  • mainApplication 작성
@SpringBootApplication
@EnableDiscoveryClient
public class Service1Application {

public static void main(String[] args) {

SpringApplication.run(Service1Application.class, args);

}

 @Bean
 @LoadBalanced
 public RestTemplate getRestTemplate() {

     return new RestTemplate();
 }

}

 

 

서비스간 통신을 위해 RestTemplate을 이용하고 @LoadBalanced 어노테이션을 추가해준다.

@LoadBalenced는 Eureka에 내장된 로드밸런서인 Ribbon을 이용해 라운드 로빈을 기반으로 서비스 인스턴스를 호출할 수 있다. 즉, 어노테이션 추가만으로 Service-A에서 2개의 인스턴스인 Service-B를 자동으로 라운드 로빈 기반으로 호출할 수 있다.

 

 

  • Controller 작성
@RestController
@RequestMapping("/service1")
public class Service1Controller {

 @Autowired
 private RestTemplate restTemplate;

 private static final String SERVICE_B_NAME = "SERVICE-B";


 @GetMapping("/test")

    public String callServiceA() throws UnsupportedOperationException, IOException {
        ResponseEntity<String> res;
        String apiPath = "/service2/statuscheck";
        res = restTemplate.getForEntity("http://" + SERVICE_B_NAME + apiPath, String.class);



        return "Service-A: inst001 호출" + " > " + res.getBody().toString();

    }

}

 

 

  • Ribbon을 통해 서비스끼리 Eureka 등록된 서비스명으로 호출이 가능하다.

 

 

  • 위와 같은 똑같이 클라이언트(Service-B)를 구성한 후, mainClass에서 @LoadBalanced가 적용된 메소드만 지워준 후, Controller 부분만 새롭게 작성한다.

 

@RestController
@RequestMapping("/service2")
public class Service2Controller {

@GetMapping("/statuscheck")

    public String checkState() {

        return "Service-B: inst001 정상";

    }

}

 

  • 같은 클라이언트(Service-B)의 인스턴스를 하나 더 만들어 준다.
@RestController
@RequestMapping("/service2")
public class Service2Controller {
	 @GetMapping("/statuscheck")
	    public String checkState() {
	        return "Service-B: inst002 정상";
	    }
}

 

 

  • 서버 실행

 

2개의 서비스 및 3개의 인스턴스가 정상적으로 올라간걸 확인할 있다.

 

    • 호출 테스트

 

 

결론

Eureka Client인 Service-A가 Eureka Server로 부터 Service-B의 정보를 알 수 있었으며, Service-A는 라운드로빈 방식으로 2개의 Service-B의 인스턴스를 호출하는 것을 알 수 있었다.

즉, Eureka에 내장된 로드밸런서인 Ribbon을 이용해 라운드 로빈을 기반으로 서비스 인스턴스를 호출한 것이며, 이를 통해 Service-A가 직접 로드밸런서역할을 하는 ClientSide Service discovery를 구현해본 것이다.

다음 내용에서는 Service 앞단에 Spring Cloud API Gateway를 위치시켜 Gateway가 로드밸런서 역할을 하는 ServerSide  Service discovery를 구현해보자. 

반응형

댓글