Netflix 有提供了一個稱為 Eureka 的 open source,可以作為服務註冊之用,spring cloud 將它做了封裝,這裡來看一下怎麼使用。
我總共會寫三支程式,分別為 ...
- myEureka: eureka server,註冊服務的地方。
- myEureka-Client: 提供服務的程式,會到 eureka server 註冊,供其它程式使用。
- myConsumer: 想要使用服務的程式,也會到 eureka server 註冊,註冊後即可使用 server 上的服務。
buildscript {
ext {
springBootVersion = '2.0.2.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
group = 'idv.steven.eureka'
version = '1.0'
sourceCompatibility = 1.8
repositories {
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
}
ext {
springCloudVersion = 'Finchley.RC2'
}
configurations.all {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
}
dependencies {
def log4j2Version = '2.7'
compile('org.springframework.boot:spring-boot-starter')
compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-server')
{
exclude group: 'org.springframework.cloud', module: 'spring-cloud-starter-netflix-eureka-client'
}
compile('org.springframework.boot:spring-boot-starter-tomcat')
compileOnly('org.projectlombok:lombok')
testCompile('org.springframework.boot:spring-boot-starter-test')
// Log4j2
compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: "${log4j2Version}"
compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: "${log4j2Version}"
compile group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: "${log4j2Version}"
compile group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.0'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
使用 gradle 下載所有需要的 jar 檔,特別注意紅色的部份,是有關 spring cloud 及 eureka。
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class MyEurekaApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(MyEurekaApplication.class).web(true).run(args);
}
}
只要加入 @EnableEurekaServer 即可啟動一個 Eureka Server,當然,還是要有一些設定,如下面的 application.properties、application-peer1.properties、application-peer2.properties,為什麼會有三個設定檔? 因為我們會啟用兩個 Eureka Server,並讓兩個 server 相互註冊,這樣可以相互備援及負載平衡。兩個 server 分別放在hostname 為 peer1 及 peer2 的伺服器上,因為我是在一台電腦上測試,所以我在 hosts 中加入如下內容:
127.0.0.1 peer1
127.0.0.1 peer2
spring.applicaion.name=eureka-server
server.port=1111
eureka.instance.hostname=localhost
eureka.client.register-with-eureka=false
eureak.client.fetch-registry=false
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka
注意紅色的部份,eureka.client.register-with-eureka=false 表示不向註冊中心註冊自己,eureak.client.fetch-registry=false 表示不需要檢索服務。
- application-peer1.properties
eureka.instance.hostname=peer1
server.port=1111
eureka.client.serviceUrl.defaultZone=http://peer2:1112/eureka/
peer1 上的 Eureka Server 啟動時,服務會佔用 1111 port,並且會向 peer2 註冊。
- application-peer2.properties
eureka.instance.hostname=peer2
server.port=1112
eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka/
peer2 上的 Eureka Server 啟動時,服務會佔用 1112 port,並且會向 peer1 註冊。因為我實際上只有一台電腦,port 才會設不一樣,如果是真的在兩台電腦,port 最好設成一樣。程式都寫好之後,用 gradle build 指令產生 jar 檔。
java -jar myEureka-1.0.jar --spring.profiles.active=peer1
java -jar myEureka-1.0.jar --spring.profiles.active=peer2
開兩個命令列視窗,進入 jar 檔所在目錄,第一個視窗執行第一條指令,第二個視窗執行第二條指令,這樣就可以分別啟動 peer1 及 peer2 的 server 了,如果對於 application.properties 檔的設定不熟,可以參考「
application.properties@spring boot」。
開啟瀏覽器,輸入 http://localhost:1111/ 進入 peer1 的管理介面,可以看到如下,peer2 已經註冊到 peer1 來了。
再開啟一個新的頁面,輸入 http://localhost:1112/ 進入 peer2 的管理介面,會看到如下,peer1 註冊到 peer2 了。
buildscript {
ext {
springBootVersion = '2.0.2.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
group = 'idv.steven.eureka'
version = '1.0'
sourceCompatibility = 1.8
repositories {
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
}
ext {
springCloudVersion = 'Finchley.RC2'
}
configurations.all {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
}
dependencies {
def log4j2Version = '2.7'
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client')
compileOnly('org.projectlombok:lombok')
testCompile('org.springframework.boot:spring-boot-starter-test')
// Log4j2
compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: "${log4j2Version}"
compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: "${log4j2Version}"
compile group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: "${log4j2Version}"
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
特別注意紅色部份,這樣就可以載入 Eureka Client 相關的 jar 檔。
- MyEurekaClientApplication.java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class MyEurekaClientApplication {
public static void main(String[] args) {
SpringApplication.run(MyEurekaClientApplication.class, args);
}
}
加上 @EnableDiscoveryClient,這個程式就成為 Eureka Client,會將自己註冊到 server。
spring.application.name=hello-service
eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka/,http://peer2:1112/eureka/
有兩台 server,就將它們都寫到 eureka.client.serviceUrl.defaultZone 後面,這樣 client 會將服務註冊到 peer1、peer2。
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import lombok.extern.slf4j.Slf4j;
@RestController
@Slf4j
public class HelloController {
@Autowired
private DiscoveryClient client;
@Autowired
private Registration registration;
@RequestMapping(value ="/hello", method = RequestMethod.GET)
public String index() {
List<ServiceInstance> list = client.getInstances(registration.getServiceId());
list.forEach(s -> {
System.out.println("service id: " + s.getServiceId());
});
return "Hello";
}
}
在這裡紅色部份是可以不用寫,這裡寫出來只是為了展示 client 端可以透過這個方式抓到所有的 service instance。
現在再觀察一下 peer1、peer2 的管理介面,可以看到 client 已經註冊到兩台 server,因為在 application.properties 中,我將 client 提供的服務命名為 hello-service,所以在兩台 server 上都可以看到這個服務。
buildscript {
ext {
springBootVersion = '2.0.2.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
group = 'idv.steven.consumer'
version = '1.0'
sourceCompatibility = 1.8
repositories {
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
}
ext {
springCloudVersion = 'Finchley.RC2'
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client')
compileOnly('org.projectlombok:lombok')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
載入 Eureka Client 所需的 jar 檔。
spring.application.name=myconsumer
server.port=9090
eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka/,http://peer2:1112/eureka/
我只在一台電腦上執行四支程式,這裡的 port 就避開上面三支程式的即可,我設為 9090,另外,這也是一個 client 程式,所以也要將自己註冊到 server。
- MyConsumerApplication.java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@EnableDiscoveryClient
@SpringBootApplication
public class MyConsumerApplication {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(MyConsumerApplication.class, args);
}
}
與前面的 client 服務程式一樣,加上 @EnableDiscoverClient,因為這也是一支 Eureka Client 程式,只是不提供服務,而是要到 server 上去找到別的服務並執行該服務。RestTemplate 設定為 @Bean 讓 spring 控管,是為了下面的程式要使用,加上 @LoadBalanced 的話,會開啟 client 的負載平衡。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/myconsumer", method = RequestMethod.GET)
public String helloConsumer() {
return restTemplate.getForEntity("http://HELLO-SERVICE/hello", String.class).getBody();
}
}
這支程式只是發動一個 REST request 到上面那支 client 服務去使用該服務,要注意的是,網址寫的是該服務註冊於 server 上的名稱,也就是說,使用服務不再需要知道服務所在的伺服器在那,只要將自己註冊到 Eureka Server,即可以服務的名稱存取到該服務。
開啟一個瀏覽器,輸入 http://localhost:9090/myconsumer 去觸發這支要使用服務的程式,讓這支程式發動一個 REST reuqest 去 HELLO-SERVICE 存取該服務,如下所示,服務傳回 Hello。