Spring Cloud - Eureka를 이용한 마이크로서비스 동적등록&탐색&부하분산처리

2021. 4. 15. 02:04 Spring Cloud/Spring Cloud

스프링 클라우드 유레카는 넷플릭스 OSS에서 유래됐다. 

자가 등록, 동적 탐색 및 부하 분산에 주로 사용되며, 부하 분산을 위해 내부적으로 리본을 사용한다.

마이크로서비스의 장점 중 하나인 동적인 서비스 증설 및 축소를 유레카를 이용하면

아주 쉽게 가능하다.

 

위의 그림과 같이 사용자의 사용이 급격하게 많아졌다고 가정해보자.

그렇다면 위와 같이 서비스 인스턴스를 증설할 것이다. 

여기에서 유레카를 사용한다면 마이크로서비스 인스턴스를 하나 추가하면

자가 등록을 통해 유레카서버에 자신의 서비스를 등록한다.

그러면 동적으로 추가된 인스턴스를 탐색할 수 있게 되고 내부적으로 리본에 의해

같은 인스턴스 4개가 부하 분산(로드밸런싱) 처리가 될 것이다.

 

만약 유레카와 같은 것을 사용하지 않았다면? 개발자가 수동으로 전부다 등록해야하고 

그렇게 함으로써 추가된 인스턴스만 배포하는 것이 아니라, 관련된 다른 인스턴스까지 추가로 

재배포가 필요할 수도 있을 것이다.

 

위의 구성에 대해서 간단히 설명하자면 유레카는 서버와 클라이언트 컴포넌트로 이루어져있다.

서버 컴포넌트는 모든 마이크로서비스가 자신의 가용성을 등록하는 레지스트리이다.

등록되는 정보는 일반적으로 서비스 ID&URL이 포함된다.

마이크로서비스 인스턴스는 유레카 클라이언트를 이용해서 자기 자신의 가용성을 유레카 서버의 레지스트리에 

등록한다. 등록된 마이크로서비스를 호출해서 사용하는 컴포넌트도 유레카 클라이언트를 이용해서 

필요한 서비스를 탐색한다.

 

마이크로서비스가 시작되면 유레카 서버에 접근해 서비스 ID&URL 등의 정보를 등록하고 자신이

기동되었다는 것을 알린다.(통신은 모두 REST) 일단 등록이 되면 유레카 서버의 레지스트리에 

30초 간격으로 ping을 날리면서 자신의 status가 정상이다라는 것을 알린다.

만약 이 ping요청이 제대로 이루어지지 않는다면 유레카서버는 서비스가 죽은 것으로 

판단하여 레지스트리에서 제거한다.

 

유레카 클라이언트는 서비스의 정보를 받기 위하여 매번 유레카 서버에서 요청을 보내지않고

한번 받으면 로컬캐시에 저장을 해둔다. 그리고 기본 30초마다 계속 서버에 요청을 보내서

서비스의 목록을 들여다보며 변경이 있다면 로컬캐시에 저장된 것을 갱신시킨다.

(로컬캐시와 서버에 있는 서비스 정보를 비교해차이가 있는 것을 가져오는 Delta Updates 방식으로 갱신)

 

 

예제로 만들어볼 소스는 우선 Spring Cloud Config를 이용할 것이다.

만약 스프링 클라우드 컨피그에 대한 개념을 모른다면 아래 링크를 통해 한번 보고와도 좋을 것같다.

 

▶︎▶︎▶︎

[Spring Cloud/Spring Cloud] - Spring Cloud - Spring Cloud Config(스프링 클라우드 컨피그)

 

우선 유레카 서버로 이용할 스프링 부트 프로젝트를 생성한다.

 

Cloud Config>Config Client

Cloud Discovery>Eureka Server

Ops>Actuator

 

를 체크하여 프로젝트를 생성해준다.

 

spring.application.name=eureka
spring.profiles.active=server1
server.port=8889
spring.cloud.config.uri=http://localhost:8888

 

spring.application.name=eureka,spring.profiles.active=server1는 

클라우드 컨피그에서 가져올 프로퍼티 파일명을 뜻한다.

> eureka-server1.properties

나머지설정은 위의 클라우드 컨피그 링크에서 참조하면 될 것같다.

 

 

유레카 서버는 Standard alone과 cluster mode 모두가 가능하다. 하지만

이번 예제에서는 Standard alone mode로 진행할 것이다.

 

spring.application.name=eureka-server1
eureka.client.serviceUrl.defaultZone=http://localhost:8899/eureka/
eureka.client.registerWithEureka=false
eureka.client.fetchRegistry=false

 

위의 설정정보는 git에 저장된 eureka-server1.properties에 작성될 설정정보이다.

유레카서버는 서버임과 동시에 클라이언트가 될수 있다. 즉, 유레카서버도 결국은 유레카 클라이언트로

동작하는 것이다.(유레카 서버가 여러대일때, peer 관계에 있는 유레카서버의 서비스 목록을 가져오기 위하여

자신의 클라이언트를 이용해서 가져온다. 하지만 지금은 일단 클라이언트들과 동일한 동작이 계속 시도되지 않도록 false로 한것이다.) 

eureka.client.serviceUrl.defaultZone 설정으로 Zone을 지정해준다.

그리고 eureka.client.registerWithEureka=false로 자기자신을 서비스로 등록하지 않는다.

마지막으로 eureka.client.fetchRegistry=false로 마이크로서비스인스턴스 목록을 로컬에 캐시할 것인지의

여부로 등록한다. 하지만 여기서 유레카서버는 동적 서비스 탐색등의 목적으로

사용되지는 않음으로 밑의 두개의 설정은 false로 등록한다.(즉,Standard alone이면 두개다 false)

만약 registerWithEureka를 true로 동작하면 자기 자신에게 계속 health check 요청 및 다른 유레카 클라이언트가 보내는 요청을

자기스스로에게 보내게 될것이다.

 

@EnableEurekaServer
@SpringBootApplication
public class EurekaserverApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(EurekaserverApplication.class, args);
    }
 
}

 

@EnableEurekaServer 어노테이션으로 자기자신이 유레카서버임을 명시한다.

http://localhost:8889 로 접속하면 유레카 관리페이지가 나온다.

현재는 아무런 서비스도 등록되어 있지않은 상태이다.

 

나머지 유레카 클라이언트들의 코드는 편의상 클라우드 컨피그를 이용하지 않았다.

server.port=8070
eureka.client.serviceUrl.defaultZone=http://localhost:8889/eureka/
spring.application.name=eureka-call-client

 

위의 설정파일은 마이크로서비스 인스턴스들을 호출할 하나의 클라이언트 설정이다. 유레카클라이언트는 defaultZone 속성값이 같은

다른 유레카 클라이언트와 동료관계를 형성하므로, 해당 애플리케이션의 defaultZone설정으로 유레카서버와 동일하게 작성한다.

그 다음 spring.application.name 설정은 유레카서버에 등록될 서비스이름이다.

유레카서버에게 동적서비스 등록을 하고,

동적탐색의 대상이 되는 어떠한 서비스들을 호출하기 위한 애플리케이션도 유레카 클라이언트이어야한다.

 

@EnableDiscoveryClient
@SpringBootApplication
public class Eurekaclient3Application {
 
    
    public static void main(String[] args) {
        SpringApplication.run(Eurekaclient3Application.class, args);
    }
    
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
    
    @RestController
    class EurekaClientController{
        
        @Autowired
        RestTemplate restTemplate;
        
        @GetMapping("/eureka/client")
        public String eurekaClient() {
            
            String result = restTemplate.getForObject("http://eurekaclient/eureka/client", String.class);
            
            return result;
        }
    }
}

 

위의 소스를 설명하자면 우선 애플리케이션이 유레카 클라이언트임을 @EnableDiscoveryClient 어노테이션으로 명시한다.

그리고 우리가 많이 사용하던 RestTemplate을 빈으로 등록할때 @LoadBalanced 어노테이션을 등록하여

Ribbon에 의해 로드벨런싱할 RestTemplate임을 명시한다.(@LoadBalanced 때문에 서비스로 등록된 마이크로서비스 인스턴스 등을 호출할때

라운드로빈 방식으로 분산으로 요청이 가게된다.)

 

그런데 RestTemplate을 사용하는 메소드 안의 URL정보가 조금 특이하다. eurekaclient? 우리는 로컬환경이고

따로 호스트를 등록하지도 않았는데, localhost가 아니고 다른 DNS명으로 호출하고 있다.

해답은 다음 과정에 나오게 된다.

 

다음은 서비스로 등록될 마이크로서비스 인스턴스 애플리케이션 2개이다.(2개의 애플리케이션 코드는 동일하고 설정정보만 조금 다르니, 소스코드는

하나의 애플리케이션만 명시한다.)

 

server.port=8090
spring.application.name=eurekaclient
eureka.client.serviceUrl.defaultZone=http://localhost:8889/eureka/
server.port=8080
spring.application.name=eurekaclient
eureka.client.serviceUrl.defaultZone=http://localhost:8889/eureka/

 

두개의 마이크로서비스 인스턴스의 설정정보이다. 다른 것은 서버포트 하나뿐이다. 그리고 해당 애플리케이션들은

같은 애플리케이션은 증설한 상황이다. 즉, 서비스 이용자 입장에서는 같은 인스턴스인 것이다. 그렇기 때문에

spring.application.name을 eurekaclient로 동일하게 등록한다. 어? 이건 이 인스턴스들을

호출한 클라이언트에서 RestTemplate의 메소드의 DNS였는데? 맞다. 그것이다.

 

즉, 유레카서버에 등록한 서비스 이름으로 RestTemplate 요청을 보내는 것이다. 그런 다음 해당 서비스 이름으로

서비스가 등록되어있는지 확인하고 있다면 Ribbon이 로드밸런싱(라운드로빈 방식) 해줘서 요청이 가게된다.

 

@EnableDiscoveryClient
@SpringBootApplication
public class EurekaclientApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(EurekaclientApplication.class, args);
    }
    
    @RestController
    class EurekaClientController{
        
        @GetMapping("/eureka/client")
        public String eurekaClient() {
            return "eureka client - 1";
        }
    }
}

 

마이크로서비스 인스턴스의 소스이다. 애플리케이션을 하나더 생성하여 위의 소스에서 반환값만 수정하자.

 

그런다음 유레카 관리자 페이지를 들어가보자.

 

 

총 3개의 서비스가 등록되어 있는 것을 볼 수 있다.(Eureka-call-client(1),EurekaClient(2))

 

마지막으로 postman 툴이나 curl로 Eureka-call-client의 "/eureka/client"를 호출해보자

 

계속해서 반환되는 값이 "eureka client - 1" , "eureka client - 2" 로 번갈아가면서

반환될것이다. 지금은 로컬에서 나자신 혼자만 요청을 보내니 라운드로빈방식으로 각각 번갈아가면서

한번씩 호출된다.

 

이렇게 독립모드형 유레카서버,클라이언트 구성을 해보았다.

 

마지막으로 간단한 유레카서버,클라이언트 용어 및 부트설정 설명이다.

 

#<Eureka 등장 용어 정리>
#    <Eureka 행동 관련>
#        Service Registration: 서비스가 자기 자신의 정보를 Eureka에 등록하는 행동
#        Service Registry: 서비스가 스스로 등록한 정보들의 목록, 가용한 서비스들의 위치 정보로 갱신됨
#        Service Discovery: 서비스 클라이언트가 요청을 보내고자 하는 대상의 정보를 Service Registry를 통해 발견하는 과정
#    <Eureka 구성 요소 관련>
#        Eureka Client: 서비스들의 위치 정보를 알아내기 위해 Eureka에 질의하는 서비스를 가리킴 (like Service consumer)
#        Eureka Service: Eureka Client에 의해 발견의 대상이 되도록 Eureka에 등록을 요청한 서비스를 가리킴 (like Service provider)
#        Eureka Server: Eureka Service가 자기 자신을 등록(Service Registration)하는 서버이자 Eureka Client가 가용한 서비스 목록(Service Registry)을 요청하는 서버
#        Eureka Instance: Eureka에 등록되어 목록에서 조회 가능한 Eureka Service를 의미

 

일단 이번 포스팅은 간단하게 유레카의 사용법을 익혀봤다. 다음 포스팅에서는 더 다양한 유레카 설정과 유레카 서버를 클러스터구성으로

예제를 진행할것이다. 이번에는 대략적인 유레카의 사용법을 익히는 것으로 간다.



출처: https://coding-start.tistory.com/121?category=790355 [코딩스타트]