본문 바로가기
MSA/Spring Microservice In Action

4. 서비스 디스커버리

by 화트마 2022. 1. 9.

서비스 디스커버리

  • 서비스의 물리적 위치를 추상화
  • 서비스 클라이언트가 인스턴스의 물리적 위치를 몰라도 되어, 서비스 인스턴스의 수평 확장 및 축소가 자유로움
  • 비정상 인스턴스를 가용 서비스 목록에서 제거하여 회복성 높임
  • 예 : 넷플릭스 유레카

 

주요 특징

1. 고가용성

  • 서비스 목록을 여러 노드가 공유하는 클러스터링 환경 지원.

2. 피어 투 피어

  • 각 노드는 서비스 인스턴스 상태를 공유함.

3. 부하 분산

  • 클라이언트 요청을 동적으로 부하 분산하여 모든 서비스 인스턴스에 분해함.

4. 회복성

  • 클라이언트는 서비스 정보를 로컬에 캐시하여 서비스 디스커버리가 가용하지 않을 때에도 서비스를 계속 찾을 수 있음.

5. 장애 내성

  • 서비스 인스턴스의 비정상을 탐지하고 가용 서비스 목록에서 인스턴스를 제거함.

 

서비스 디스커버리 주요 과정

  • 서비스 인스턴스가 시작하면 서비스 디스커버리 인스턴스가 접근할 수 있는 자신의 물리적 위치와 경로, 포트를 등록
  • 서비스의 각 인스턴스에는 고유한 IP주소와 포트가 있지만 동일한 서비스 ID로 등록. 이때 서비스 ID는 동일한 서비스 인스턴스 그룹을 고유하게 식별하는 키
  • 서비스는 일반적으로 1개의 서비스 디스커버리 인스턴스(노드)에만 등록. 그리고 다른 노드에 전파함.
  • 각 서비스 인스턴스는 자기 상태를 서비스 디스커버리에 푸시하거나 서비스 디스커버리가 인스턴스 상태를 추출.

 

클라이언트 측 부하 분산

  • 모든 서비스 인스턴스 정보를 서비스 디스커버리에서 가져와 서비스 소비자 기기에 로컬 캐시함.
  • 클라이언트가 서비스를 호출하려 할 때 마다 서비스 소비자는 캐시에서 위치 정보 검색.
  • 클라이언트는 주기적으로 서비스 디스커버리 서비스에 접속해 서비스 인스턴스 캐시를 새로고침함.
  • 예 : 넷플릭스 리본 라이브러리

 

서비스 디스커버리 및 클라이언트 측 부하 분산

  • 스프링 클라우드와 넷플릭스의 유레카 서비스 디스커버리 엔진을 사용해 서비스 디스커버리 패턴을 구현하고, 클라이언트 측 부하 분산을 위해 스프링 클라우드와 넷플릭스의 리본 라이브러리를 사용

주요 과정

  • 서비스 부트스트래핑 시점에 라이선싱 및 조직 서비스는 자신을 유레카 서비스에 등록. 이 등록 과정에서 서비스 ID와 함께 각 서비스 인스턴스의 물리적 위치, 포트 번호를 유레카에 알려줌.
  • 라이선싱 서비스가 조직 서비스를 호출할 때 넷플릭스 리본 라이브러리를 사용해 클라이언트 측 부하 분산 기능을 수행. 리본 라이브러리는 유레카 서비스에서 서비스의 위치 정보를 조회하고 로컬에 캐싱.
  • 주기적으로 넷플릭스 리본 라이브러리는 유레카 서비스를 핑해서 로컬 캐시의 서비스 위치를 새로고침.

 

유레카 서비스 디스커버리 구현

새로운 스프링 부트 프로젝트 생성 (eurekasvr)

- pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
    </parent>

    <groupId>com.hmg</groupId>
    <artifactId>eurekasvr</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>eurekasvr</name>
    <description>eureka service discovery</description>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Finchley.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>

	...

</project>

 

유레카 서비스를 독립 실행 모드로 설정하는 데 필요한 구성(예를 들어 클러스터에 다른 노드가 없는 구성)

- application.yml

# 유레카 서버가 수신 대기할 포트
server:
  port: 8761

eureka:
  client:
    # 유레카 서비스에 (자신을) 등록하지 않는다.
    registerWithEureka: false
    # 레지스트리 정보를 로컬에 캐싱하지 않는다.
    fetchRegistry: false
  server:
    # 서버가 요청을 받기 전 대기할 초기 시간 (로컬에서만 사용)
    #waitTimeInMsWhenSyncEmpty: 5
    # 컨테이너 실행시 read-timeout 에러 떠서 추가
    peer-node-read-timeout-ms: 10000
  serviceUrl:
    defaultZone: http://localhost:8761

 

유레카 서비스 애플리케이션 부트스트랩 클래스

- Application.java

package com.hmg.eurekasvr;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer // 스프링 서비스에서 유레카 서버 활성화
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

 

스프링 유레카에 서비스 등록 (서비스 측 구현)

조직(라이선싱) 서비스에 스프링 유레카 의존성 추가

- pom.xml

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-neflix-eureka-client</artifactId> -- 유레카에 서비스를 등록할 수 있도록 유레카 라이브러리 포함
</dependency>

 

스프링 부트에 조직서비스를 유레카에 등록하도록 지정

- bootstrap.yml

spring:
	application:
		name: organizationservice -- 유레카에 등록할 서비스의 논리 이름
	profiles:
		active: default
	cloud:
		config:
			enabled: true

- application.yml

eureka:
  instance:
    # 서비스 이름 대신 서비스 IP 주소 등록
    preferIpAddress: true
  client:
    # 유레카에 서비스 등록
    registerWithEureka: true
    # 유레카 서비스 위치
    fetchRegistry: true
    # 레지스트리 사본을 로컬로 가져오기
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

 

유레카 서비스 디스커버리에 서비스가 등록되면 유레카 REST API를 호출해 레지스트리 내용 확인이 가능

GET http://<eureka service>:8761/eureka/apps/<APPID>

- 예시 : GET http://localhost:8761/eureka/apps/organizationservice

참고
서비스를 유레카에 등록하면 서비스가 가용하다고 확인될 떄 까지 유레카는 30초간 연속 세 번의 상태 정보를 확인하며 대기함(warm-up 시간). 따라서 서비스를 호출하기 전에 30초 정도 기다리자.

 

서비스 디스커버리를 사용해 서비스 검색

서비스 소비자가 리본과 상호 작용할 수 있는 스프링/넷플릭스 클라이언트 라이브러리가 세가지 존재

  1. 스프링 디스커버리 클라이언트
  2. RestTemplate이 활성화된 스프링 디스커버리 클라이언트
  3. 넷플릭스 Feign 클라이언트

그 중 추상화 수준이 가장 높은 넷플릭스 Feign 클라이언트를 통해 서비스 검색 구현

(licensing-service -> organization-service 서비스 검색)

 

dependency 추가

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

 

Feign 클라이언트 활성화

- Application.java

@SpringBootApplication
@EnableFeignClients // FeignClient를 사용하기 위해 애너테이션 추가
public class Application {
  public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
  }
}

 

조직 서비스 엔드포인트를 호출할 Feign 클라이언트 인터페이스 정의

  • 인터페이스만 생성해 주면 스프링 클라우드 프레임워크는 대상 REST 서비스를 호출하는 데 사용되는 프록시 클래스를 동적으로 생성한다.
  • @FeignClient 애너테이션으로 organizationservice의 물리적 위치를 리본에서 받아와 RestTemplate 호출해준다.
  • @Autowired로 클래스를 주입받아 getOrganization() 메서드 사용 가능

- OrganizationFeignClient.java

@FeignClient("organizationservice") // FeignClient 애너테이션으로 조직 서비스를 Feign에 확인
public interface OrganizationFeignClient {
    @RequestMapping(
            method= RequestMethod.GET,
            value="/v1/organizations/{organizationId}", // 조직 정보 조회
            consumes="application/json") // @RequestMapping 애너테이션으로 엔드포인트 경로와 액션 정의
    Organization getOrganization(@PathVariable("organizationId") String organizationId); // @PathVariable 애너테이션으로 엔드포인트에 전달하는 매개변수 정의
}

 

licensing-service 에서 oragnization-service 호출해보기

상세 컨트롤러 및 서비스 로직은 https://github.com/hmg0616/msainaction-repo 에 licensing-service 모듈에서 확인

GET http://licensing-service/v1/organizations/{organizationId}/licenses/{licenseId}
GET http://localhost:8080/v1/organizations/e254f8c-c442-4ebe-a82a-e2fc1d1ff78a/licenses/f3831f8c-c338-4ebe-a82a-e2fc1d1ff78a

댓글