본문 바로가기

IT Book Summary/스프링 마이크로 서비스 코딩공작소

Chapter 04 서비스 디스커버리

 

 

 

4.1 서비스 위치 찾기

 

클라우드가 아닌 환경에서 서비스 위치 확인은 대개 DNS와 네트워크 로드 밸런서로 해결하였다.

 

DNS와 로드밸런서를 사용하는 전통적 서비스 위치 확인모델

 

정적서버 그룹에서 실행되는 소수 서비스의 경우

- 서비스 소비자에게 요청

- 로드밸런서는 액서스 경로를 기반으로 라우팅 테이블에서 물리적 주소 찾음

- 해당 서비스를 호스팅하는 서버목록에서 하나를 선택해 요청 전달

- 보조 밸런서가 유휴상태로 대기. 주 로드 밸랜서가 정상인지 핑으로 확인

- 정상이 아니면 보조 로드밸런서가 활성화. 두 로드 밸런서의 IP 주소를 인수해 요청을 처리.

 

클라우드 기반의 마이크로서비스 앱에서 잘 작동하지 못하는 이유

  • 단일 장애 지점
    - 하나의 로드밸런서에 의존, 병목지점이 될 가능성
  • 수평 확장의 제약성
    - 하드웨어 제약으로 수평확장 제약
  • 정적관리
    - 신속히 서비스 등록과 취소 불가
  • 복잡성
    - 서비스 매핑규칙을 수동으로 정의 해야함

 


4.2 클라우드에서 서비스 디스커버리

 

서비스 디스커버리 매커니즘

  • 고가용성
  • 피어 투 피어
  • 부하 분산
  • 회복성
  • 장애 내성

 

1 - 서비스 디스커버리 아키텍처

 

네가지 개념

  • 서비스 등록
  • 클라이언트가 서비스 주소 검색
  • 정보 공유
  • 상태 모니터링

서비스 인스턴스 추가 - 제거될때 서비스 디스커버리 에이전트 업데이트

서비스 인스턴스가 시작하면 서비스 디스커버리가 인스턴스가 접근할 수 있는 자신의 물리적 위치와 경로, 포트를 등록

동일한 서비스 ID로 등록하여 동일한 서비스 인스턴스 그룹을 고유하게 식별

서비스 디스커버리 구현체는 데이터를 클러스터 다른노드에 전파

구현에 따라 클러스터에서 발생된 변경을 다른 노드가 발견하게 할수 있다.

정상상태를 반환하지 못하는 서비스는 가용 인스턴스 풀에서 제거된다.

 

클라이언트측 부하분산 방식 - 서비스 디스커버리 연결없이 서비스 위치 캐시

더 견고한 클라이언트 측 부하분산 방법

  1. 소비자가 디스커버리 서비스에 접속한 후 데이터를 서비스 소비자 기기에 로컬 캐시
  2. 클라이언트가 서비스를 호출하려고 할 때마다 서비스 소비자는 캐시에서 위치정보를 검색. 
    클라이언트 측 캐싱은 '라운드로빈' 부하분산 알고리즘처럼 단순한 알고리즘을 사용해 서비스 호출을 여러 인스턴스로 분산.
  3. 클라이언트는 주기적으로 서비스 디스커버리 서비스에 접속해 서비스 인스턴스 캐시를 새로고침.
    서비스를 호출하는 동안 서비스 호출이 실패하면 로컬에 있는 서비스 디스커버리 캐시가 무효화되고 서비스 디스커버리 클라이언트는 에이전트 목록 새로고침을 시도한다.

 

 

2 - 스프링과 넷플릭스 유레카를 사용한 서비스 디스커버리

 

스프링 클라우드와 유레카 서비스 디스커버리 엔진을 사용해 서비스 디스커버리 패턴을 구현하고,

클라이언트 측 부하분산을 위해 스프링 클라우드와 넷플릭스의 리본 라이브러리를 사용할 것.

 

라이선싱 및 조직 서비스에 클라이언트 측 캐싱과 유레카를 구현

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

4.3 스프링 유레카 서비스 구축

https://github.com/carnellj/spmia-chapter4 깃헙 참고

 

메이븐 pom.xml 유레카 설정

 

application.yml 파일의 유레카 구성설정

#Default port is 8761
server:
  port: 8761 --유레카 서버가 수신 대기하라 포트

eureka:
  client:
    registerWithEureka: false --유레카 서비스에 자신을 등록하지 않음
    fetchRegistry: false --레지스트리 정보를 로컬에 캐싱
  server:
    waitTimeInMsWhenSyncEmpty: 5 --서버가 요청을 받기 전 대기할 초기 시간
  serviceUrl:
    defaultZone: http://localhost:8761

 

https://github.com/carnellj/spmia-chapter4/blob/master/eurekasvr/src/main/java/com/thoughtmechanix/eurekasvr/EurekaServerApplication.java

EurekaServerApplication.java

 

@EnableEurekaServer // 스프링 서비스에서 유레카 서버 활성화

 


4.4 스프링 유레카에 서비스 등록

 

조직 서비스의 pom.xml에 스프링 유레카 의존성 추가

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

 

bootstrap.yml

애플리케이션 ID 는 항상 spring.application.name 프로퍼티 값으로 설정됨.

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

 

application.yml

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

 

defaultZone 프로퍼티는 클라이언트 위치를 확인하는데 사용할 유레카 서비스 목록을 쉼표로 구분해 보관

- 클라이언트가 통신할 수 있는 유레카 서비스 목록만 제공

- 유레카 클러스터 구축 http://projects.spring.io/spring-cloud/spring-cloud.html

 

레지스트리에 있는 조직 서비스를 보려면

http://localhost:8761/eureka/apps/organizationservice 호출


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

 

라이선싱 서비스를 조직 서비스 위치를 직접 알지 못해도 호출가능.

유레카를 사용해 조직서비스의 물리적 위치를 검색하기 때문.

 

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

 

@RequestMapping(value="/{licenseId}/{clientType}",method = RequestMethod.GET)

clientType 매개변수는 예제에 사용할 클라이언트 타입을 결정.

  • Discovery
  • Rest
  • Feign

/licensing-service/src/main/java/com/thoughtmechanix/licenses/clients/

패키지 에서 라이브러리 사용한 클라이언트 참고

 

1 - 스프링 DiscoveryClient로 서비스 인스턴스 검색

DiscoveryClient를 사용해 리본에서 조직서비스 URL 하나를 검색한 후 표준 RestTemplate 클래스로 서비스 호출. 

 

@EnableDiscoveryClient 애너테이션 추가. DiscoveryClient와 리본 라이브러리 사용가능하게.

 

@Component
public class OrganizationDiscoveryClient {
  @Autowired
  private DiscoveryClient discoveryClient; //DiscoveryClient 자동연결

  public Organization getOrganization(String organizationId) {
    RestTemplate restTemplate = new RestTemplate();
    List<ServiceInstance> instances =
      discoveryClient.getInstances("organizationservice"); //조직서비스 인스턴스 목록
    if (instances.size()==0) return null;
    String serviceUri = String.format("%s/v1/organizations/%s",
     instances.get(0).getUri().toString(),
     organizationId);
   
    ResponseEntity< Organization > restExchange = //서비스를 호출하는데 표준스프링 RestTemplate 클래스 사용
        restTemplate.exchange(
            serviceUri,
            HttpMethod.GET,
            null, Organization.class, organizationId);
    return restExchange.getBody();
  }
}

 

2 - 리본 지원 스프링 RestTemplate을 사용한 서비스 호출

스프링을 사용해 리본과 상호작용할 수 있는 훨씬 더 일반적인 매커니즘.

 

@LoadBalanced 애너테이션으로 RestTemplate 빈 생성메서드를 정의

라이선싱 서비스에서 Application.java

@SpringBootApplication
@EnableDiscoveryClient // 여러 클라이언트 타입이 사용되므로 포함.
@EnableFeignClients
public class Application {

  @LoadBalanced //스프링 클라우드가 리본이 지원하는 RestTemplate 클래스를 생성하도록 지정.
  @Bean
  public RestTemplate getRestTemplate(){
    return new RestTemplate();
  }
  
  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  } 
}

 

리본지원 RestTemplate을 사용한 서비스 호출

public class OrganizationRestTemplateClient {
  @Autowired
  RestTemplate restTemplate;
  
  public Organization getOrganization(String organizationId){
    ResponseEntity<Organization> restExchange =
        restTemplate.exchange( "http://organizationservice/v1/organizations/{organizationId}", HttpMethod.GET,
                        null, Organization.class, organizationId); //유레카 서비스ID

    return restExchange.getBody();
  }                        
}

서비스 인스턴스에 대한 모든 요청을 라운드로빈 방식으로 부하분산.

 

 

3 - 넷플릭스 Feign 클라이언트로 서비스 호출

Feign 라이브러리를 사용하면 개발자가 자바 인터페이스를 먼저 정의한 후 리본이 호출할 유레카 기반의 서비스를 매핑하도록

그 인터페이스 안에 스프링 클라우드 애너테이션을 추가해 REST 서비스를 호출하는 접근법.

@EnableFeignClients

@SpringBootApplication
@EnableDiscoveryClient // 여기서는 제거가능
@EnableFeignClients
public class Application {
  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  } 
}

 

조직 서비스 호출을 위한 Feign 인터페이스 정의

@FeignClient("organizationservice")
public interface OrganizationFeignClient {
  @RequestMapping(method= RequestMethod.GET, value="/v1/organizations/{organizationId}",
      consumes="application/json") //엔드포인트 경로와 액션 정의
  Organization getOrganization(@PathVariable("organizationId") String organizationId); 
  //매개변수정의
}

 


4.6 요약

  • 서비스 디스커버리 패턴은 서비스의 물리적 위치를 추상화하는데 사용
  • 유레카 같은 서비스 디스커버리 엔진은 서비스 클라이언트에 영향을 주지 않고
    해당 환경의 서비스 인스턴스를 원활하게 추가, 삭제 가능.
  • 클라이언트 측 부하 분산을 사용하면 서비스를 호출하는 클라이언트에서
    서비스의 물리적 위치를 캐싱해 더 나은 성능과 회복성을 제공할 수 있다.
  • 유레카를 넷플릭스 프로젝트의 스프링 클라우드와 사용하면 쉽게 구축하고 구성
  • 스프링 클라우드와 넷플릭스 유레카, 넷플릭스 리본으로 사용하는 세가지 매커니즘
    - 스프링 클라우드와 DiscoveryClient
    - 스프링 클라우드와 리본 지원 RestTemplate
    - 스프링 클라우드와 넷플릭스 Feign 클라이언트