使用 Spring Cloud LoadBalancer 进行客户端负载均衡

本指南将引导您完成创建负载均衡微服务的流程。

您将构建什么

您将构建一个微服务应用程序,该应用程序使用 Spring Cloud LoadBalancer 在对另一个微服务的调用中提供客户端负载均衡。

您需要什么

  • 大约 15 分钟

  • 您喜欢的文本编辑器或 IDE

  • JDK 1.8 或更高版本

  • Gradle 6+ 或 Maven 3.5+

  • 您也可以将代码直接导入到您的 IDE 中

  • Spring Tool Suite (STS) 或 IntelliJ IDEA

创建根项目

本指南将逐步介绍如何构建两个项目,其中一个项目依赖于另一个项目。因此,您需要在根项目下创建两个子项目。首先,在顶层创建构建配置。对于 Maven,您需要一个包含子目录列表的 pom.xml 文件,其中包含 <modules> 标签。

<?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. 点击“依赖项”并选择“Spring Web”(对于“Say Hello”项目)或“Cloud Loadbalancer”和“Spring Reactive Web”(对于“User”项目)。

  4. 点击“生成”。

  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 方法,以及另一个用于根路径 / 的方法。

我们将在此客户端服务应用程序旁边本地运行此应用程序的多个实例。要开始

  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 注解传递负载均衡器的自定义配置。

@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 注解并创建您自己的负载均衡器配置。最典型的方法是将 Spring Cloud LoadBalancer 与服务发现结合使用。如果您的类路径上有任何 DiscoveryClient,则默认的 Spring Cloud LoadBalancer 配置将使用它来检查服务实例。因此,您只需从运行正常的实例中进行选择。您可以通过本 指南 了解如何使用 ServiceDiscovery

我们还添加了一个 application.yml 文件,其中包含默认的 server.portspring.application.name

以下列表显示了 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 许可证和写作的 署名-非衍生作品创作共用许可证 发布。