使用 Spring Cloud LoadBalancer 的用戶端負載平衡

本指南將引導您完成建立負載平衡微服務的流程。

您將建構的內容

您將建構一個微服務應用程式,該應用程式使用 Spring Cloud LoadBalancer 在呼叫另一個微服務時提供用戶端負載平衡。

您需要的東西

  • 約 15 分鐘

  • 您慣用的文字編輯器或 IDE

  • JDK 1.8 或更高版本

  • Gradle 6+ 或 Maven 3.5+

  • 您也可以將程式碼直接匯入您的 IDE

  • Spring Tool Suite (STS) 或 IntelliJ IDEA

建立根專案

本指南將逐步說明如何建構兩個專案,其中一個專案是另一個專案的依賴項。因此,您需要在根專案下建立兩個子專案。首先,在頂層建立組建設定。對於 Maven,您需要一個具有 <modules>pom.xml,其中列出了子目錄

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.springframework</groupId>
    <artifactId>gs-spring-cloud-loadbalancer</artifactId>
    <version>0.1.0</version>
    <packaging>pom</packaging>

    <modules>
      <module>say-hello</module>
      <module>user</module>
    </modules>
</project>

對於 Gradle,您需要一個包含相同目錄的 settings.gradle

rootProject.name = 'gs-spring-cloud-loadbalancer'

include 'say-hello'
include 'user'

或者,您可以包含一個空的 build.gradle(以協助 IDE 識別根目錄)。

建立目錄結構

在您想要作為根目錄的目錄中,建立以下子目錄結構(例如,在 *nix 系統上使用 mkdir say-hello user

└── say-hello
└── user

在專案的根目錄中,您需要設定組建系統,本指南將向您展示如何使用 Maven 或 Gradle。

從 Spring Initializr 開始

如果您為 Say Hello 專案使用 Maven,請造訪 Spring Initializr 以產生一個具有所需依賴項(Spring Web)的新專案。

以下清單顯示了當您選擇 Maven 時建立的 pom.xml 檔案

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.2.0</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>spring-cloud-loadbalancer-say-hello</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>spring-cloud-loadbalancer-say-hello</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<spring-boot.repackage.skip>true</spring-boot.repackage.skip>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

如果您為 Say Hello 專案使用 Gradle,請造訪 Spring Initializr 以產生一個具有所需依賴項(Spring Web)的新專案。

以下清單顯示了當您選擇 Gradle 時建立的 build.gradle 檔案

plugins {
	id 'org.springframework.boot' version '3.2.0'
	id 'io.spring.dependency-management' version '1.1.4'
	id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
	useJUnitPlatform()
}

bootJar {
	enabled = false
}

如果您為 User 專案使用 Maven,請造訪 Spring Initializr 以產生一個具有所需依賴項(Cloud Loadbalancer 和 Spring Reactive Web)的新專案。

以下清單顯示了當您選擇 Maven 時建立的 pom.xml 檔案

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.2.0</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>spring-cloud-loadbalancer-user</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>spring-cloud-loadbalancer-user</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<spring-boot.repackage.skip>true</spring-boot.repackage.skip>
		<java.version>17</java.version>
		<spring-cloud.version>2023.0.0</spring-cloud.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-webflux</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-loadbalancer</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>io.projectreactor</groupId>
			<artifactId>reactor-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
	<repositories>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
		</repository>
	</repositories>

</project>

如果您為 User 專案使用 Gradle,請造訪 Spring Initializr 以產生一個具有所需依賴項(Cloud Loadbalancer 和 Spring Reactive Web)的新專案。

以下清單顯示了當您選擇 Gradle 時建立的 build.gradle 檔案

plugins {
	id 'org.springframework.boot' version '3.2.0'
	id 'io.spring.dependency-management' version '1.1.4'
	id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
	mavenCentral()
	maven { url 'https://repo.spring.io/milestone' }
}

ext {
	set('springCloudVersion', "2023.0.0")
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-webflux'
	implementation 'org.springframework.cloud:spring-cloud-starter-loadbalancer'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'io.projectreactor:reactor-test'
}

dependencyManagement {
	imports {
		mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
	}
}

test {
	useJUnitPlatform()
}

bootJar {
	enabled = false
}

手動初始化(可選)

如果您想要手動初始化專案,而不是使用先前顯示的連結,請按照以下步驟操作

  1. 導覽至 https://start.spring.io。此服務會提取應用程式所需的所有依賴項,並為您完成大部分設定。

  2. 選擇 Gradle 或 Maven 以及您想要使用的語言。本指南假設您選擇了 Java。

  3. 按一下 Dependencies 並選擇 Spring Web(適用於 Say Hello 專案)或 Cloud LoadbalancerSpring Reactive Web(適用於 User 專案)。

  4. 按一下 Generate

  5. 下載產生的 ZIP 檔案,這是一個已使用您的選擇設定的 Web 應用程式的封存檔。

如果您的 IDE 具有 Spring Initializr 整合,您可以從 IDE 完成此流程。

實作 "Say Hello" 服務

我們的「伺服器」服務稱為 Say Hello。它從可透過 /greeting 存取的端點傳回隨機問候語(從三個靜態清單中選取)。

src/main/java/hello 中,建立檔案 SayHelloApplication.java

以下清單顯示了 say-hello/src/main/java/hello/SayHelloApplication.java 的內容

package hello;

import java.util.Arrays;
import java.util.List;
import java.util.Random;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@SpringBootApplication
public class SayHelloApplication {

  private static Logger log = LoggerFactory.getLogger(SayHelloApplication.class);

  public static void main(String[] args) {
    SpringApplication.run(SayHelloApplication.class, args);
  }

  @GetMapping("/greeting")
  public String greet() {
  log.info("Access /greeting");

  List<String> greetings = Arrays.asList("Hi there", "Greetings", "Salutations");
  Random rand = new Random();

  int randomNum = rand.nextInt(greetings.size());
  return greetings.get(randomNum);
  }

  @GetMapping("/")
  public String home() {
  log.info("Access /");
  return "Hi!";
  }
}

這是一個簡單的 @RestController,我們在其中為 /greeting 和根路徑 / 提供了一個 @RequestMapping method

我們將在本機與用戶端服務應用程式一起執行此應用程式的多個執行個體。若要開始

  1. 建立 src/main/resources 目錄。

  2. 在目錄中建立 application.yml 檔案。

  3. 在該檔案中,設定 server.port 的預設值。

(我們將指示應用程式的其他執行個體在其他埠上執行,以便在我們讓用戶端執行時,沒有任何 Say Hello 執行個體與用戶端衝突)。當我們在此檔案中時,我們也可以設定服務的 spring.application.name

以下清單顯示了 say-hello/src/main/resources/application.yml 的內容

spring:
  application:
    name: say-hello

server:
  port: 8090

從用戶端服務存取

我們的使用者看到 User 應用程式。它呼叫 Say Hello 應用程式以取得問候語,然後在使用者造訪 /hi/hello 的端點時將該問候語傳送給我們的使用者。

在 User 應用程式目錄的 src/main/java/hello 下,新增 UserApplication.java 檔案

以下清單顯示了 user/src/main/java/hello/UserApplication.java 的內容

package hello;

import reactor.core.publisher.Mono;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;

/**
 * @author Olga Maciaszek-Sharma
 */
@SpringBootApplication
@RestController
public class UserApplication {

  private final WebClient.Builder loadBalancedWebClientBuilder;
  private final ReactorLoadBalancerExchangeFilterFunction lbFunction;

  public UserApplication(WebClient.Builder webClientBuilder,
      ReactorLoadBalancerExchangeFilterFunction lbFunction) {
    this.loadBalancedWebClientBuilder = webClientBuilder;
    this.lbFunction = lbFunction;
  }

  public static void main(String[] args) {
    SpringApplication.run(UserApplication.class, args);
  }

  @RequestMapping("/hi")
  public Mono<String> hi(@RequestParam(value = "name", defaultValue = "Mary") String name) {
    return loadBalancedWebClientBuilder.build().get().uri("http://say-hello/greeting")
        .retrieve().bodyToMono(String.class)
        .map(greeting -> String.format("%s, %s!", greeting, name));
  }

  @RequestMapping("/hello")
  public Mono<String> hello(@RequestParam(value = "name", defaultValue = "John") String name) {
    return WebClient.builder()
        .filter(lbFunction)
        .build().get().uri("http://say-hello/greeting")
        .retrieve().bodyToMono(String.class)
        .map(greeting -> String.format("%s, %s!", greeting, name));
  }
}

我們還需要一個 @Configuration 類別,在其中設定負載平衡的 WebClient.Builder 執行個體

以下清單顯示了 user/src/main/java/hello/WebClientConfig.java 的內容

package hello;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;

@Configuration
@LoadBalancerClient(name = "say-hello", configuration = SayHelloConfiguration.class)
public class WebClientConfig {

  @LoadBalanced
  @Bean
  WebClient.Builder webClientBuilder() {
    return WebClient.builder();
  }

}

組態提供了一個 @LoadBalanced WebClient.Builder 執行個體,當有人點擊 UserApplication.javahi 端點時,我們會使用它。一旦點擊 hi 端點,我們就會使用此建構器建立一個 WebClient 執行個體,該執行個體向 Say Hello 服務的 URL 發出 HTTP GET 請求,並以 String 形式傳回結果。

UserApplication.java 中,我們還新增了一個執行相同動作的 /hello 端點。但是,我們沒有使用 @LoadBalanced 註釋,而是使用了 @Autowired 負載平衡器交換篩選器函數 (lbFunction),我們透過使用 filter() 方法將其傳遞給我們以程式設計方式建構的 WebClient 執行個體。

即使我們為兩個端點稍微不同地設定了負載平衡的 WebClient 執行個體,但兩者的最終行為完全相同。Spring Cloud LoadBalancer 用於選取 Say Hello 服務的適當執行個體。

spring.application.nameserver.port 屬性新增至 src/main/resources/application.propertiessrc/main/resources/application.yml

以下清單顯示了 user/src/main/resources/application.yml 的內容

spring:
  application:
    name: user

server:
  port: 8888

跨伺服器執行個體進行負載平衡

現在我們可以存取 User 服務上的 /hihello,並看到友善的問候語

$ curl https://127.0.0.1:8888/hi
Greetings, Mary!

$ curl https://127.0.0.1:8888/hi?name=Orontes
Salutations, Orontes!

WebClientConfig.java 中,我們透過使用 @LoadBalancerClient 註釋傳遞 LoadBalancer 的自訂組態

@LoadBalancerClient(name = "say-hello", configuration = SayHelloConfiguration.class)

這表示,每當聯絡名為 say-hello 的服務時,Spring Cloud LoadBalancer 都會使用 SayHelloConfiguration.java 中提供的組態,而不是使用預設設定執行。

以下清單顯示了 user/src/main/java/hello/SayHelloConfiguration.java 的內容

package hello;

import java.util.Arrays;
import java.util.List;

import reactor.core.publisher.Flux;

import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;

/**
 * @author Olga Maciaszek-Sharma
 */
public class SayHelloConfiguration {

  @Bean
  @Primary
  ServiceInstanceListSupplier serviceInstanceListSupplier() {
    return new DemoServiceInstanceListSuppler("say-hello");
  }

}

class DemoServiceInstanceListSuppler implements ServiceInstanceListSupplier {

  private final String serviceId;

  DemoServiceInstanceListSuppler(String serviceId) {
    this.serviceId = serviceId;
  }

  @Override
  public String getServiceId() {
    return serviceId;
  }

  @Override
  public Flux<List<ServiceInstance>> get() {
    return Flux.just(Arrays
        .asList(new DefaultServiceInstance(serviceId + "1", serviceId, "localhost", 8090, false),
            new DefaultServiceInstance(serviceId + "2", serviceId, "localhost", 9092, false),
            new DefaultServiceInstance(serviceId + "3", serviceId, "localhost", 9999, false)));
  }
}

在該類別中,我們提供了一個自訂的 ServiceInstanceListSupplier,其中包含三個硬式編碼的執行個體,Spring Cloud LoadBalancer 在呼叫 Say Hello 服務時會從中選擇。

新增此步驟是為了說明您如何將自己的自訂組態傳遞給 Spring Cloud LoadBalancer。但是,您不需要使用 @LoadBalancerClient 註釋並為 LoadBalancer 建立自己的組態。最典型的方式是將 Spring Cloud LoadBalancer 與服務探索搭配使用。如果您在類別路徑上有任何 DiscoveryClient,則預設 Spring Cloud LoadBalancer 組態會使用它來檢查服務執行個體。因此,您只會從已啟動並執行中的執行個體中選擇。您可以透過此指南瞭解如何將 ServiceDiscovery 與其搭配使用。

我們也新增了一個具有預設 server.portspring.application.nameapplication.yml 檔案。

以下清單顯示了 user/src/main/resources/application.yml 的內容

spring:
  application:
    name: user

server:
  port: 8888

測試負載平衡器

以下清單顯示了如何使用 Gradle 執行 Say Hello 服務

$ ./gradlew bootRun

以下清單顯示了如何使用 Maven 執行 Say Hello 服務

$ mvn spring-boot:run

若要實現負載平衡,您需要兩個伺服器執行相同應用程式的不同執行個體。您可以透過在不同埠上執行 Say Hello 服務的第二個執行個體來實現此目的。在此範例中,我們使用埠 9999。

若要使用 Gradle 執行此操作,請開啟新的終端機並執行以下命令

export SERVER_PORT=9092
./gradlew bootRun

若要使用 Maven 執行此操作,請開啟新的終端機並執行以下命令

export SERVER_PORT=9999
mvn spring-boot:run

然後您可以啟動 User 服務。此時,您應該有三個終端機:兩個用於 Say Hello 的兩個執行個體,一個用於 User。然後您可以存取 localhost:8888/hi 並監看 Say Hello 服務執行個體。

您對 User 服務的請求應導致對 Say Hello 的呼叫以循環配置方式分散在執行中的執行個體之間

2016-03-09 21:15:28.915  INFO 90046 --- [nio-8090-exec-7] hello.SayHelloApplication                : Access /greeting

摘要

恭喜!您剛剛開發了一個 Spring Loadbalancer 應用程式!

想要撰寫新的指南或貢獻現有的指南嗎?請查看我們的貢獻指南

所有指南均以 ASLv2 授權發佈程式碼,並以 姓名標示-非衍生性著作創用 CC 授權發佈寫作內容。