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. 아키텍처
- 사용자 증가
- -> 마이크로 서비스 인스턴스 증설
- -> 해당 마이크로 서비스 인스턴스는 Eureka 서버에 자신을 등록
- -> 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)