このガイドでは、負荷分散されたマイクロサービスを作成するプロセスについて説明します。

構築するもの

Spring Cloud LoadBalancer を使用して、別のマイクロサービスへの呼び出しでクライアント側の負荷分散を提供するマイクロサービスアプリケーションを構築します。

必要なもの

  • 約 15 分

  • お気に入りのテキストエディターまたは IDE

  • JDK 1.8 以降

  • Gradle 6 + または Maven 3.5+

  • コードを直接 IDE にインポートすることもできます。

  • Eclipse Spring Tool Suite(STS)または IntelliJ IDEA

ルートプロジェクトを作成する

このガイドでは、2 つのプロジェクトの構築について説明します。一方は、もう一方への依存関係です。ルートプロジェクトに 2 つの子プロジェクトを作成する必要があります。まず、トップレベルでビルド構成を作成します。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>2.4.4</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>1.8</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 '2.4.4'
	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
	id 'java'
}

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

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>2.4.4</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>1.8</java.version>
		<spring-cloud.version>2020.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 '2.4.4'
	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
	id 'java'
}

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

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

ext {
	set('springCloudVersion', "2020.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 WebSay Hello プロジェクトの場合)またはクラウドロードバランサーSpring リアクティブ WebUser プロジェクトの場合)を選択します。

  4. 生成をクリックします。

  5. 結果の ZIP ファイルをダウンロードします。これは、選択して構成された Web アプリケーションのアーカイブです。

Eclipse や IntelliJ IDEA などのように IDE に Spring Initializr が統合されている場合は、ブラウザーでアクセス、ダウンロード、インポートしなくても、IDE からこのプロセスを完了することができます。

「Say Hello」サービスを実装する

「サーバー」サービスは Say Hello と呼ばれています。/greeting でアクセス可能なエンドポイントからランダムなグリーティング(3 つの静的リストから選択)を返します。

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 用に 1 つの @RequestMapping method があり、ルートパス / 用にもう 1 つあります。

このアプリケーションの複数のインスタンスを、クライアントサービスアプリケーションと一緒にローカルで実行します。開始するには:

  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 のエンドポイントにアクセスすると、そのグリーティングをユーザーに送信します。

ユーザーアプリケーションディレクトリの 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));
  }
}

また、負荷分散された WebClient.Builder インスタンスをセットアップする @Configuration クラスも必要です。

次のリストは、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.java の hi エンドポイントにヒットしたときに使用します。hi エンドポイントに到達すると、このビルダーを使用して WebClient インスタンスを作成します。これにより、Say Hello サービスの URL に対して HTTP GET リクエストが作成され、結果が String として提供されます。

UserApplication.java には、同じアクションを実行する /hello エンドポイントも追加されています。ただし、@LoadBalanced アノテーションを使用するのではなく、@Autowired ロードバランサー交換フィルター関数(lbFunction)を使用します。この関数は、filter() メソッドを使用してプログラムで構築した WebClient インスタンスに渡します。

負荷分散された WebClient インスタンスを 2 つのエンドポイントに対してわずかに異なる方法で設定したとしても、両方の終了動作はまったく同じです。Spring Cloud LoadBalancer は、Say Hello サービスの適切なインスタンスを選択するために使用されます。

spring.application.name および server.port プロパティを src/main/resources/application.properties または src/main/resources/application.yml に追加します。

次のリストは user/src/main/resources/application.yml の内容を示しています

spring:
  application:
    name: user

server:
  port: 8888

サーバーインスタンス間の負荷分散

これで、ユーザーサービスで /hi または hello にアクセスして、フレンドリーグリーティングを表示できます。

$ curl http://localhost:8888/hi
Greetings, Mary!

$ curl http://localhost: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.Configuration;
import org.springframework.context.annotation.Primary;

/**
 * @author Olga Maciaszek-Sharma
 */
@Configuration
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)));
  }
}

そのクラスでは、Say Hello サービスを呼び出すときに Spring Cloud LoadBalancer が選択する 3 つのハードコードされたインスタンスを備えたカスタム ServiceInstanceListSupplier を提供します。

このステップは、独自のカスタム構成を Spring Cloud LoadBalancer に渡す方法を説明するために追加されました。ただし、@LoadBalancerClient アノテーションを使用して、LoadBalancer の独自の構成を作成する必要はありません。最も一般的な方法は、サービス検出で Spring Cloud LoadBalancer を使用することです。クラスパスに DiscoveryClient がある場合、デフォルトの Spring Cloud LoadBalancer 構成は、それを使用してサービスインスタンスをチェックします。その結果、稼働中のインスタンスからのみ選択できます。このガイドでは、ServiceDiscovery の使用方法を学ぶことができます。

また、デフォルトの server.port および spring.application.name を使用して application.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

ポート 9092 および 9999 で他のインスタンスを実行できます。Gradle で実行するには、次のコマンドを実行します。

$ SERVER_PORT=9092 ./gradlew bootRun

Maven でこれを行うには、次のコマンドを実行します。

$ SERVER_PORT=9999 mvn spring-boot:run

その後、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 ロードバランサーアプリケーションを開発しました。

新しいガイドを作成したり、既存のガイドに貢献したいですか? 投稿ガイドラインを参照してください: GitHub (英語)

すべてのガイドは、コード用の ASLv2 ライセンス、およびドキュメント用の Attribution、NoDerivatives クリエイティブコモンズライセンス (英語) でリリースされています。