마이크로서비스 이해하기

  • 단일 애플리케이션의 문제점
    • 코드 전체를 파악하기 어렵다.
    • 테스트가 복잡해진다.
    • 라이브러리 간의 충돌이 생기기 쉽다.
    • 확장 시 비효율적이다. 확장 목적으로 더 많은 서버에 애플리케이션을 배포해야 할 때는 애플리케이션의 일부가아닌 전체를 배포해야 한다.
    • 적용할 기술을 결정할 때도 애플리케이션 전체를 고려해야 한다.
    • 프로덕션으로 이양하기 위해 많은 노력이 필요하다. 단일 애플리케이션은 크기와 복잡도 때문에 더 엄격한 개발 프로세스와 테스트가 필요하다.
  • 마이크로서비스 아키텍처
    • 코드를 이해하기 쉽다.
    • 테스트가 쉽다.
    • 라이브러리 비호환성 문제가 생기지 않는다.
    • 독자적으로 규모를 조정할 수 있다. 마이크로서비스의 규모가 더 커야 한다면, 다른 마이크로서비스에 영향을 주지 않고 메모리 할당이나 인스턴스의 수를 더 크게 조정할 수 있다.
    • 각 마이크로서비스에 적용할 기술을 다르게 선택할 수 있다.
    • 마이크로서비스는 언제든 프로덕션으로 이양할 수 있다.
  • 마이크로서비스 아키텍처는 분산 아키텍처이므로 네트워크 지연과 같은 문제들이 발생할 수 있다.
  • 애플리케이션이 상대적으로 작거나 간단하면 단일 애플리케이션으로 재발하는 것이 좋다. 그리고 점차 규모가 커질 때 마이크로서비스 아키텍처로 변경하는 것을 고려할 수 있다.

서비스 레지스트리 설정하기

  • 서비스 레지스트리: 마이크로서비스가 서로를 찾을 때 사용되는 마이크로서비스

  • 유레카: 넷플릭스 오픈 소스인 스프링 서비스 레지스트리

  • some-service 인스턴스가 시작될 때 해당 서비스는 자신의 이름을 유레카에 등록한다.

    • 등록되는 인스턴스는 여러개 생성될 수 있고 모두 같은 이름으로 유레카에 등록된다.
    • other-service가 some-service를 사용할 때는 some-service의 호스트 이름과 포트 정보를 하드코딩하지 않고, some-service라는 이름을 유레카에서 찾으면 된다.
    • other-service는 some-service의 어떤 인스턴스를 사용할지 결정해야 된다. 이때 클라이언트 측에서 로드 밸런싱 알고리즘을 적용하는 것이 좋고, 이때 사용할 수 있는 것이 넷플릭스 프로젝트인 리본이다.
  • 중앙 집중화된 로드 밸런서가 아닌 클라이언트 측의 로드밸런서를 사용하는 이유

    • 각 클라이언트에 하나의 로컬 로드 밸런서가 있으므로 클라이언트의 수에 비례하여 자연스럽게 로드 밸런서의 크기가 조정된다.
    • 서버에 연결된 모든 서비스에 획질적으로 같은 구성을 사용하는 대신, 각 클라이언트에 가장 적합한 로드 밸런싱 알고리즘을 사용하도록 구성할 수 있다.
  • 유레카 서버 스타터 의존성

  • 부트스트랩 클래스에 @EnableEurekaServer 애노테이션을 추가하면 실행가능하다.

@EnableEurekaServer  
@SpringBootApplication  
class ServiceRegistryApplication  
  
fun main(args: Array<String>) {  
    runApplication<ServiceRegistryApplication>(*args)  
}

유레카 구성하기

server.port=8761  
eureka.instance.hostname=localhost  
eureka.client.fetch-registry=false  
eureka.client.register-with-eureka=false  
eureka.client.service-url.defalt-zone=http://${eureka.instance.hostname}:${server.port}/eureka/
eureka.server.enable-self-preservation=false
  • 일반적으로 단일 장애점 방지를 위해 여러 유레카 서버들이 클러스터로 구성된다.
  • 그러나 개발 시에 두 개 이상의 유레카 서버를 실행하는 것은 불편하기도 하고 불필요하다. 개발 목적으로는 하나의 유레카 서버면 충분하다.
  • server.port: 유레카 서버 포트
  • eureka.instance.hostname: 유레카 서버 호스트 네임
  • eureka.client.fetch-registry: 해당 서버가 다른 유레카 서버로부터 레지스트 정보를 가져오는지 여부
  • eureka.client.register-with-eureka: 해당서버가 다른 유레카 서버의 서비스로 자신을 등록하는지 여부
  • eureka.client.service-url: 해당 키 하위의 정보는 Map 형태로 저장된다.
    • 위 예시에서는 키인 defaultZone 은 자신이 원하는 영역을 지정하지 않았을 때 사용한다.
  • eureka.server.enable-self-preservation: 자체-보존 모드 비활성화
    • 유레카 서버는 다른 서비스 인스턴스가 살아 있는지 확인하기 위해 30초마다 등록 갱신 요청을 전송하기를 기대한다.
    • 일반적으로 세 번의 갱신기간(90초) 동안 서비스 인스턴스로부터 등록 갱신 요청을 받지 못하면 해당 서비스 인스턴스의 등록을 취소하게 된다.
    • 만일 이렇게 중단되는 서비스가 임계값을 초과하면 유레카 서버는 네트워크 문제가 생긴 것으로 간주하고 레지스트리에 등록된 나머지 서비스 데이터를 지우지 않는 자체-보존 모드가 된다.
    • 프로덕션 환경에서는 도움이되지만 개발 환경에서는 여러 가지 이유로 갱신 요청을 받을 수 없기 때문에 비활성화 한다.

유레카 확장하기

  • 가장 간단한 방법은 properties 파일에 스프링 프로파일을 지정하고, 한 번에 하나씩 프로파일을 사용해서 유레카를 두 번 시작시키면 된다.
eureka.client.service-url.default-zone=http://${other.eureka.host}:${other.eureka.port}/eureka  
#---  
spring.config.activate.on-profile=eureka-1  
spring.application.name=eureka-1  
server.port=8761  
eureka.instance.hostname=eureka1.tacocloud.com  
other.eureka.host=eureka2.tacocloud.com  
other.eureka.port=8762  
#---  
spring.config.activate.on-profile=eureka-2  
spring.application.name=eureka-2  
server.port=8762  
eureka.instance.hostname=eureka2.tacocloud.com  
other.eureka.host=eureka1.tacocloud.com  
other.eureka.port=8761

서비스 등록하고 찾기

  • 애플리케이션을 서비스 레지스트리 클라이언트로 활성화 하기 위해 의존성 추가
    • 유레카 클라이언트 스타터 의존성을 추가하면 유레카를 이용해서 서비스를 찾는 데 필요한 모든 것이 자동으로 추가된다.
      • 유레카의 클라이언트 라이브러리, 리본 로드 밸런서 등

유레카 클라이언트 속성 구성하기

  • 유레카 서버에 서비스 이름을 등록하기 위해서 클라이언트에 spring.application.name 프로퍼티에 서비스 이름을 명시한다.
  • 유레카 서버의 위치를 등록하기위해 eureka.client.service-url 속성을 사용할 수 있다.
    • 어떤 이유로든 한 유레카 서버가 중단되면 클라이언트가 서비스 등록에 실패하는 것을 방지하기 위해서 두 개 이상의 유레카 서버를 사용하도록 구성할 수 있다.
    • 아래와 같이 구성하면 서비스가 시작될 때 첫 번째 유레카 서버에 등록을 시도하고, 실패하면 두 번째에 등록을 시도하게 된다.

서비스 사용하기

  • 스프링 클라우드의 유레카 클라이언트 지원에 포함된 리본 클라이언트 로드 밸런스를 사용해서 서비스 인스턴스를 쉽게 찾아 선택할 수 있다.
  • 유레카 서버에서 찾은 서비스를 선택 및 사용하는 방법에는 두 가지가 있다.
    • 로드 밸런싱된 RestTemplate
    • Feign에서 생성된 클라이언트 인터페이스
  • RestTemplate
    • 이전의 방법: 요청을 보내기 위해 호스트와 포트를 하드 코딩했다.
    • 로드 밸런싱된 RestTemplate 사용법
      • config 클래스에 @LoadBalanced 어노테이션을 붙여 빈으로 등록
      • @LoadBalanced를 붙여서 로드 밸런싱된 RestTemplate를 주입하고 사용
        • 위 사진과 같이 호스트와 포트를 하드 코딩하지 않고 서비스명을 적어두면 된다.
  • WebClient: 리액티브 프로그래밍에서 사용할 수 있는 HTTP 클라이언트
    • RestTemplate과 같은 방법으로 로드 밸런싱된 클라이언트를 사용할 수 있다.
  • Feign: 인터페이스를 기반으로 하는 방법을 사용해서 REST 클라이언트를 정의한다.
    • 넷플릭스 프로젝트였지만, 자웅에 독립된 오픈 소스 프로젝트가 되었다.
    • 의존성 추가
    • @EnableFeignClients 애노테이션을 추가하면 Feign이 활성화된다.
    • 인터페이스를 통한 정의
      • @FeignClient 애노테이션에 명시한 이름의 서비스를 리본을 통해 찾게된다.
      • @GetMapping@PathVariable 애노테이션은 스프링 MVC에서 사용했던 것과 같은 애노테이션이다.
      • 아래 사진에서는 ‘ingredient-service’ 서비스를 리본을 통해서 찾아서 선택하게 되고, 선택한 호스트와 포트의 ‘/ingredients/{id}’ 경로로 GET 요청을 보내게 된다.