Spring Cloud Eureka(유레카)에 대해 알아보자.


1. Eureka란?

스프링 클라우드 Eureka는 Load balance, Failover 등을 목적으로 사용되는 REST 기반의 서비스로 Netflix OSS에서 유래했다.

부하 분산을 위해서는 Ribbon을 사용한다.

유사한 솔루션으로는 Apache ZooKeeper와 Consul이 있다.


2. MSA와 Spring Cloud 

영역 제품
Service Discovery Eureka Server
API Gateway  Zuul + Hystrix + Ribbon + Eureka Client
API 관리 Swagger
호출 추적 Zipkin

 


3. 아키텍처

  1. 사용자 증가
  2. -> 마이크로 서비스 인스턴스 증설
  3. -> 해당 마이크로 서비스 인스턴스는 Eureka 서버에 자신을 등록
  4. -> Ribbon에 의해 여러 인스턴스간 부하 분산

 


4. DiscoveryClient

DiscoveryClient는 Eureka, ZooKeeper 등을 다 사용 가능한 Abstraction이다. (어댑터 제공) 특정 서비스 레지스트리에 종속성이 없는 장점이 있다. 또 여러 복합적 디스커버리도 사용 가능하다.

단, 클라이언트 로드 밸런스는 지원하지 않으므로 이는 Ribbon을 사용한다.


5. Endpoint

  • Register & Deregister : Register 시에는 XSD가 포함된 XML 혹은 JSON 바디를 POST로 전송
  • Renew Registration : PUT /eureka/v2/apps/MYAPP/i-6589ef6
  • Cancel : DELETE /eureka/v2/apps/MYAPP/i-6589ef6
  • Fetch Regitry

6. Eureka Server Spring Boot

starter.spring.io 사이트에서 디펜던시에 Eureka Server 추가하고 다운받는다.

@EnableEurekaServer 어노테이션은 이 애플리케이션이 Eureka 서버임을 선언하는 것이다.

package io.sarc.springcloud.EurekaServerDemo;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
 
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerDemoApplication {
  public static void main(String[] args) {
    SpringApplication.run(EurekaServerDemoApplication.class, args);
  }
}

application.properties 파일에는 이런 내용이 들어간다.

server.port=8761
spring.application.name=eureka-server
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
  • eureka.client.registerWithEureka=false는 자기자신을 서비스로 등록하지 않는다. 이를 true로 동작하면 자기 자신에게 계속 Health check를 하게 되고 다른 Eureka 클라이언트가 보내는 요청을 자기 자신에게 보내게 될된다.
  • eureka.client.fetchRegistry=false는 마이크로 서비스 인스턴스 목록을 로컬에 캐시할 것인지의 여부이다.  

eureka.client.fetchRegistry=false가 없으면 Eureka Server 기동 시 다음과 같은 에러가 난다.

com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server
        at com.netflix.discovery.shared.transport.decorator.RetryableEurekaHttpClient.execute(RetryableEurekaHttpClient.java:112) ~[eureka-client-1.9.8.jar:1.9.8]
        at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.cancel(EurekaHttpClientDecorator.java:71) ~[eureka-client-1.9.8.jar:1.9.8]
        at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator$2.execute(EurekaHttpClientDecorator.java:74) ~[eureka-client-1.9.8.jar:1.9.8]
        at com.netflix.discovery.shared.transport.decorator.SessionedEurekaHttpClient.execute(SessionedEurekaHttpClient.java:77) ~[eureka-client-1.9.8.jar:1.9.8]
        at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.cancel(EurekaHttpClientDecorator.java:71) ~[eureka-client-1.9.8.jar:1.9.8]
        at com.netflix.discovery.DiscoveryClient.unregister(DiscoveryClient.java:923) [eureka-client-1.9.8.jar:1.9.8]
        at com.netflix.discovery.DiscoveryClient.shutdown(DiscoveryClient.java:901) [eureka-client-1.9.8.jar:1.9.8]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_221]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_221]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_221]

정상적으로 기동된 로그이다.

2019-08-30 09:01:16.385  INFO 1328752 --- [           main] c.n.eureka.DefaultEurekaServerContext    : Initializing ...
2019-08-30 09:01:16.389  INFO 1328752 --- [           main] c.n.eureka.cluster.PeerEurekaNodes       : Adding new peer nodes [http://localhost:8761/eureka/]
2019-08-30 09:01:17.149  INFO 1328752 --- [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using JSON encoding codec LegacyJacksonJson
2019-08-30 09:01:17.155  INFO 1328752 --- [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using JSON decoding codec LegacyJacksonJson
2019-08-30 09:01:17.156  INFO 1328752 --- [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using XML encoding codec XStreamXml
2019-08-30 09:01:17.161  INFO 1328752 --- [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using XML decoding codec XStreamXml
2019-08-30 09:01:17.701  INFO 1328752 --- [           main] c.n.eureka.cluster.PeerEurekaNodes       : Replica node URL:  http://localhost:8761/eureka/
2019-08-30 09:01:19.267  INFO 1328752 --- [           main] c.n.e.registry.AbstractInstanceRegistry  : Finished initializing remote region registries. All known remote regions: [] 2019-08-30 09:01:19.381  INFO 1328752 --- [           main] c.n.eureka.DefaultEurekaServerContext    : Initialized
2019-08-30 09:01:19.400  INFO 1328752 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 2 endpoint(s) beneath base path '/actuator'
2019-08-30 09:01:19.857  INFO 1328752 --- [           main] o.s.c.n.e.s.EurekaServiceRegistry        : Registering application EUREKA-SERVER with eureka with status UP
2019-08-30 09:01:19.919  INFO 1328752 --- [      Thread-13] o.s.c.n.e.server.EurekaServerBootstrap   : Setting the eureka configuration..
2019-08-30 09:01:20.903  INFO 1328752 --- [      Thread-13] o.s.c.n.e.server.EurekaServerBootstrap   : Eureka data center value eureka.datacenter is not set, defaulting to default 2019-08-30 09:01:20.904  INFO 1328752 --- [      Thread-13] o.s.c.n.e.server.EurekaServerBootstrap   : Eureka environment value eureka.environment is not set, defaulting to test
2019-08-30 09:01:21.236  INFO 1328752 --- [      Thread-13] o.s.c.n.e.server.EurekaServerBootstrap   : isAws returned false
2019-08-30 09:01:21.241  INFO 1328752 --- [      Thread-13] o.s.c.n.e.server.EurekaServerBootstrap   : Initialized server context
2019-08-30 09:01:21.241  INFO 1328752 --- [      Thread-13] c.n.e.r.PeerAwareInstanceRegistryImpl    : Got 1 instances from neighboring DS node
2019-08-30 09:01:21.242  INFO 1328752 --- [      Thread-13] c.n.e.r.PeerAwareInstanceRegistryImpl    : Renew threshold is: 1
2019-08-30 09:01:21.242  INFO 1328752 --- [      Thread-13] c.n.e.r.PeerAwareInstanceRegistryImpl    : Changing status to UP
2019-08-30 09:01:21.369  INFO 1328752 --- [      Thread-13] e.s.EurekaServerInitializerConfiguration : Started Eureka Server
2019-08-30 09:01:21.519  INFO 1328752 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8761 (http) with context path ''
2019-08-30 09:01:21.521  INFO 1328752 --- [           main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8761
2019-08-30 09:01:21.523  INFO 1328752 --- [           main] i.s.s.E.EurekaServerDemoApplication      : Started EurekaServerDemoApplication in 37.687 seconds (JVM running for 50.317)
2019-08-30 09:02:21.408  INFO 1328752 --- [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry  : Running the evict task with compensationTime 0ms

그리고 http://localhost:8761/ 에 접속할 수 있게 된다. 


7. Eureka Client Spring Boot

Eureka Client의 경우는 Eureka Client Discovery와 Web Starter를 추가한다. 다음은 데모 코드이다.

package io.sarc.springcloud.EurekaClientDemo;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
import com.netflix.discovery.EurekaClient;
 
@SpringBootApplication
@RestController
public class EurekaClientDemoApplication {
    @Autowired
    private EurekaClient eurekaClient;
 
    @Value("${spring.application.name}")
    private String appName;
 
  public static void main(String[] args) {
    SpringApplication.run(EurekaClientDemoApplication.class, args);
  }
 
    @RequestMapping("/hello")
    public String hello() {
        return String.format("Hello from '%s'!", eurekaClient.getApplication(appName).getName());
    }
}

다음은 Eureka Client의 application.properties이다.

spring.application.name=eureka-client
server.port=8188

이제 Eureka Client를 기동한다. 기동이 완료되면 다음 사항들을 확인할 수 있다.

  • Eureka Server 대시보드(http://localhost:8761/)에서 Eureka Client가 표시됨
  • Eureka Client에 GET 요청을 날리면 답을 받는다.
$ curl http://localhost:8188/hello
Hello from 'EUREKA-CLIENT'!

참고로 spring.application.name 넣지 않고 기동하면 에러난다.

Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'spring.application.name' in value "${spring.application.name}"
    at org.springframework.util.PropertyPlaceholderHelper.parseStringValue (PropertyPlaceholderHelper.java:178)
    at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders (PropertyPlaceholderHelper.java:124)
    at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders (AbstractPropertyResolver.java:237)
    at org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders (AbstractPropertyResolver.java:211)