Google Code Prettify

顯示具有 spring cloud 標籤的文章。 顯示所有文章
顯示具有 spring cloud 標籤的文章。 顯示所有文章

2021年2月18日 星期四

Spring Cloud Gateway: getting started

Spring Cloud 在 2020 年用 Gateway 取代原本的網關 Zuul,新的網關 Gateway 功能更強,效率更高,對 developer 來說,使用上是一樣的簡單。


如上圖是我的環境,我的所有服務放在一台 CentOS 7 上的 Docker 裡,Docker 上有服務註冊的 consul,有提供服務的 wealth-system,這個服務當輸入 http://192.168.50.13:8080/customer/E243350588 會傳回客戶的基本資料。現在要來看 gateway 怎麼寫 …

** 建立一個 spring boot 專案 **

         在 build.gradle 的 dependencies 中加入以下內容,以導入所需要的 jar 檔。

implementation 'org.springframework.cloud:spring-cloud-starter-consul-discovery'
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
implementation 'org.springframework.boot:spring-boot-starter-actuator'

  • 第 1 行是 consul 註冊、查詢服務所需要的 jar 檔
  • 第 2 行是導入新版 gateway
  • 第 3 行是 health check 所需要的 jar 檔

server:
  servlet:
    context-path: /
  port: 80
  
management:
  server:
    base-path: /
    port: 5001
  endpoint:
    health:
      probes:
        enabled: true
      show-details: always
      
spring:
  application:
    name: fstop-gateway
  profiles:
    active: ${SPRING_PROFILES_ACTIVE}
  main:
    banner-mode: off
  cloud:
    consul:
      enabled: true
      host: 192.168.50.13
      port: 8500
      discovery:
        prefer-ip-address: true
        instance-id: ${spring.application.name}:${random.value}
        register-health-check: true
        health-check-path: /actuator/health
        health-check-interval: 30s
        tags: ${spring.application.name}
    gateway:
      routes:
      - id: wealth
        uri: lb://wealth-system
        predicates:
        - Path=/customer/**

上面是 application.yml 的內容,說明如下:

  • 第 24~34 行,將 gateway 註冊到 consul,如果不註冊到 consul,就無法使用 consul 的服務查詢功能。
  • 第 35~40 行是 gateway 的設定,第 37 行只是個標識 (命名),取個有意義的名稱即可,第 38 行指出,當網址有 /customer/** 時,導向 wealth-system 這個服務。特別注意,這裡寫的是 lb://wealth-system (lb: load balance),就是要使用 consul 提供的服務發現的方式導向,也可以寫成 http://192.168.50.8080,這樣就是直接導向服務所在的網址。

2018年6月9日 星期六

Spring Cloud Eureka: getting started

Netflix 有提供了一個稱為 Eureka 的 open source,可以作為服務註冊之用,spring cloud 將它做了封裝,這裡來看一下怎麼使用。

我總共會寫三支程式,分別為 ...
  1. myEureka: eureka server,註冊服務的地方。
  2. myEureka-Client: 提供服務的程式,會到 eureka server 註冊,供其它程式使用。
  3. myConsumer: 想要使用服務的程式,也會到 eureka server 註冊,註冊後即可使用 server 上的服務。
  • myEureka
    • build.gradle
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。
    • MyEurekaApplication.java
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
    • application.properties
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 檔。
    • 啟動 server
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 了。
  • myEureka-Client
    • build.gradle
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。
    • application.properties
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。
    • HelloController.java
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 上都可以看到這個服務。

  • myConsumer
    • build.gradle
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 檔。
    • application.properties
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 的負載平衡。
    • ConsumerController.java
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。