本指南将引导您完成使用Netflix Ribbon为微服务应用程序提供客户端负载平衡的过程。

你会建立什么

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

你需要什么

如何完成本指南

像大多数Spring 入门指南一样,您可以从头开始并完成每个步骤,也可以绕过您已经熟悉的基本设置步骤。无论哪种方式,您最终都可以使用工作代码。

从头开始 ,请继续使用Gradle构建

跳过基础知识 ,请执行以下操作:

完成后 ,您可以根据中的代码检查结果gs-client-side-load-balancing/complete

用Gradle构建

用Gradle构建

首先,您设置一个基本的构建脚本。在使用Spring构建应用程序时,可以使用任何喜欢的构建系统,但是此处包含使用GradleMaven所需的代码。如果您都不熟悉,请参阅使用Gradle 构建Java项目使用Maven构建Java项目

创建目录结构

在您选择的项目目录中,创建以下子目录结构;例如, mkdir -p src/main/java/hello在* nix系统上:

└── src
    └── main
        └── java
            └── hello

创建一个Gradle构建文件

say-hello/build.gradle

buildscript {
	ext {
		springBootVersion = '2.1.6.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'

bootJar {
	baseName = 'say-hello'
	version = '0.0.1-SNAPSHOT'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
	mavenCentral()
}


dependencies {
	compile('org.springframework.boot:spring-boot-starter-web')
	testCompile('org.springframework.boot:spring-boot-starter-test')
}

eclipse {
	classpath {
		 containers.remove('org.eclipse.jdt.launching.JRE_CONTAINER')
		 containers 'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8'
	}
}

user/build.gradle

buildscript {
	ext {
		springBootVersion = '2.1.6.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'

bootJar {
	baseName = 'user'
	version = '0.0.1-SNAPSHOT'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
	mavenCentral()
}


dependencies {
	compile('org.springframework.cloud:spring-cloud-starter-netflix-ribbon')
	compile('org.springframework.boot:spring-boot-starter-web')
	testCompile('org.springframework.boot:spring-boot-starter-test')
}

dependencyManagement {
	imports {
		mavenBom "org.springframework.cloud:spring-cloud-dependencies:Greenwich.SR3"
	}
}

eclipse {
	classpath {
		 containers.remove('org.eclipse.jdt.launching.JRE_CONTAINER')
		 containers 'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8'
	}
}

Spring Boot gradle插件提供了许多方便的功能:

  • 它收集类路径上的所有jar,并构建一个可运行的单个“über-jar”,这使执行和传输服务更加方便。

  • 它搜索public static void main()标记为可运行类的方法。

  • 它提供了一个内置的依赖项解析器,用于设置版本号以匹配Spring Boot依赖项 。您可以覆盖所需的任何版本,但是它将默认为Boot选择的一组版本。

用Maven构建

用Maven构建

首先,您设置一个基本的构建脚本。使用Spring构建应用程序时,可以使用任何喜欢的构建系统,但是此处包含了使用Maven所需的代码。如果您不熟悉Maven,请参阅使用Maven 构建Java项目

创建目录结构

在您选择的项目目录中,创建以下子目录结构;例如, mkdir -p src/main/java/hello在* nix系统上:

└── src
    └── main
        └── java
            └── hello

say-hello/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>hello</groupId>
	<artifactId>say-hello</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>say-hello</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.6.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<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>

user/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>hello</groupId>
	<artifactId>user</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>user</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.6.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
		</dependency>
		<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>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>Greenwich.SR3</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>

</project>

Spring Boot Maven插件提供了许多方便的功能:

  • 它收集类路径上的所有jar,并构建一个可运行的单个“über-jar”,这使执行和传输服务更加方便。

  • 它搜索public static void main()标记为可运行类的方法。

  • 它提供了一个内置的依赖项解析器,用于设置版本号以匹配Spring Boot依赖项 。您可以覆盖所需的任何版本,但是它将默认为Boot选择的一组版本。

使用您的IDE进行构建

使用您的IDE进行构建

编写服务器服务

我们的“服务器”服务称为“您好”。它将从可从以下位置访问的端点返回随机问候(从三个静态列表中挑选) /greeting

src/main/java/hello ,创建文件SayHelloApplication.java 。它看起来应该像这样:

say-hello/src/main/java/hello/SayHelloApplication.java

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

@RestController
@SpringBootApplication
public class SayHelloApplication {

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

  @RequestMapping(value = "/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);
  }

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

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

@RestController注释的效果与我们正在使用的效果相同@Controller@ResponseBody一起。它标志着SayHelloApplication作为控制器类(这是@Controller ),并确保该类的返回值@RequestMapping方法将自动从其原始类型进行适当转换,并直接写入响应主体(即@ResponseBody确实)。我们有一个@RequestMapping方法/greeting然后另一个作为根路径/ 。(当我们开始使用Ribbon时,我们将需要第二种方法。)

我们将在本地与客户端服务应用程序一起运行该应用程序的多个实例,因此请创建目录src/main/resources ,创建文件application.yml在其中,然后在该文件中,为server.port 。(我们还将指示该应用程序的其他实例也可以在其他端口上运行,这样,当我们让该Say Hello实例运行时,不会与客户端发生冲突。)在此文件中时,我们将设置spring.application.name也为我们的服务。

say-hello/src/main/resources/application.yml

spring:
  application:
    name: say-hello

server:
  port: 8090

从客户服务访问

用户应用程序将是我们的用户看到的。它将调用Say Hello应用程序获取问候语,然后在用户访问位于的端点时将其发送给我们的用户。 /hi

在“用户应用程序”目录下, src/main/java/hello ,添加文件UserApplication.java

user/src/main/java/hello/UserApplication.java

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.client.RestTemplate;

@SpringBootApplication
@RestController
public class UserApplication {

  @Bean
  RestTemplate restTemplate(){
    return new RestTemplate();
  }

  @Autowired
  RestTemplate restTemplate;

  @RequestMapping("/hi")
  public String hi(@RequestParam(value="name", defaultValue="Artaban") String name) {
    String greeting = this.restTemplate.getForObject("http://localhost:8090/greeting", String.class);
    return String.format("%s, %s!", greeting, name);
  }

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

为了向您问好,我们正在使用Spring的RestTemplate模板类。 RestTemplate在提供服务时向“说你好”服务的URL发出HTTP GET请求,并将结果显示为String 。(有关使用Spring使用RESTful服务的更多信息,请参阅《 使用RESTful Web服务指南》。)

添加spring.application.nameserver.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在用户服务上看到友好的问候:

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

$ curl http://localhost:8888/hi?name=Orontes
Salutations, Orontes!

要从单个硬编码的服务器URL转移到负载平衡的解决方案,请设置功能区。在里面application.yml文件下user/src/main/resources/ ,添加以下属性:

user/src/main/resources/application.yml

say-hello:
  ribbon:
    eureka:
      enabled: false
    listOfServers: localhost:8090,localhost:9092,localhost:9999
    ServerListRefreshInterval: 15000

这将在功能区客户端上配置属性。Spring Cloud Netflix创建了一个ApplicationContext为我们应用程序中的每个Ribbon客户名称。这用于为客户端提供一组用于Ribbon组件实例的bean,包括:

  • 一个IClientConfig ,用于存储客户端或负载均衡器的客户端配置,

  • 一个ILoadBalancer ,代表软件负载平衡器,

  • 一种ServerList ,它定义了如何获取可供选择的服务器列表,

  • 一个IRule ,其中描述了负载平衡策略,以及

  • 一个IPing ,表示如何定期执行服务器的ping操作。

在上述情况下,客户名为say-hello 。我们设置的属性是eureka.enabled (我们设置为false ), listOfServersServerListRefreshInterval 。Ribbon中的负载平衡器通常从Netflix Eureka服务注册表中获取服务器列表。(有关在Spring Cloud上使用Eureka服务注册表的信息,请参阅《 服务注册和发现》指南。)为了简单起见,我们跳过了Eureka,因此我们将ribbon.eureka.enabled财产false而是给功能区一个静态listOfServersServerListRefreshInterval是功能区服务列表刷新之间的间隔(以毫秒为单位)。

在我们的UserApplication课,切换RestTemplate使用功能区客户端获取“打招呼”的服务器地址:

user/src/main/java/hello/UserApplication.java

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.client.RestTemplate;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.ribbon.RibbonClient;

@SpringBootApplication
@RestController
@RibbonClient(name = "say-hello", configuration = SayHelloConfiguration.class)
public class UserApplication {

  @LoadBalanced
  @Bean
  RestTemplate restTemplate(){
    return new RestTemplate();
  }

  @Autowired
  RestTemplate restTemplate;

  @RequestMapping("/hi")
  public String hi(@RequestParam(value="name", defaultValue="Artaban") String name) {
    String greeting = this.restTemplate.getForObject("http://say-hello/greeting", String.class);
    return String.format("%s, %s!", greeting, name);
  }

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

我们对UserApplication类。我们的RestTemplate现在也标记为LoadBalanced ;这告诉Spring Cloud我们要利用其负载平衡支持(在本例中由Ribbon提供)。该类带有注释@RibbonClient ,我们给name我们的客户say-hello ),然后再包含另一个类configuration为那个客户。

我们需要创建该类。添加一个新文件, SayHelloConfiguration.java , 在里面user/src/main/java/hello目录:

user/src/main/java/hello/SayHelloConfiguration.java

package hello;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.IPing;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.PingUrl;
import com.netflix.loadbalancer.AvailabilityFilteringRule;

public class SayHelloConfiguration {

  @Autowired
  IClientConfig ribbonClientConfig;

  @Bean
  public IPing ribbonPing(IClientConfig config) {
    return new PingUrl();
  }

  @Bean
  public IRule ribbonRule(IClientConfig config) {
    return new AvailabilityFilteringRule();
  }

}

通过创建具有相同名称的自己的bean,我们可以覆盖Spring Cloud Netflix提供给我们的任何与Ribbon相关的bean。在这里,我们覆盖IPingIRule由默认负载均衡器使用。默认值IPing是一个NoOpPing (实际上并不会ping服务器实例,而是总是报告它们是稳定的),并且默认IRule是一个ZoneAvoidanceRule (这避免了服务器故障最多的Amazon EC2区域,因此在我们的本地环境中尝试可能有点困难)。

我们的IPing是一个PingUrl ,它将对URL进行ping操作以检查每个服务器的状态。回忆一下,您好说,您好,有一个方法映射到/路径;这意味着Ribbon对运行中的Say Hello服务器执行ping操作时将获得HTTP 200响应。的IRule我们建立了AvailabilityFilteringRule ,将使用Ribbon的内置断路器功能过滤掉处于“断路”状态的任何服务器:如果ping无法连接到给定的服务器,或者服务器读取失败,Ribbon将认为服务器“死机”,直到开始正常响应为止。

@SpringBootApplication上的注释UserApplication类等于(除其他外) @Configuration将类标记为bean定义源的注释。这就是为什么我们不需要注释SayHelloConfiguration上课@Configuration :因为它与UserApplication ,已经在扫描中查找bean方法。

这种方法确实意味着我们的Ribbon配置将成为主应用程序上下文的一部分,并因此由User应用程序中的所有 Ribbon客户端共享。在普通应用程序中,可以通过将Ribbon bean保留在主应用程序上下文之外来避免这种情况(例如,在本示例中,您可以将SayHelloConfiguration与其他包装不同UserApplication )。

尝试一下

使用任一Gradle运行Say Hello服务:

$ ./gradlew bootRun

或Maven:

$ mvn spring-boot:run

再次使用任一Gradle在端口9092和9999上运行其他实例:

$ SERVER_PORT=9092 ./gradlew bootRun

或Maven:

$ SERVER_PORT=9999 mvn spring-boot:run

然后启动用户服务。访问localhost:8888/hi然后观看“说你好”服务实例。您可以看到功能区的ping每15秒到达一次:

2016-03-09 21:13:22.115  INFO 90046 --- [nio-8090-exec-1] hello.SayHelloApplication                : Access /
2016-03-09 21:13:22.629  INFO 90046 --- [nio-8090-exec-3] hello.SayHelloApplication                : Access /

而且您对用户服务的请求应导致对“说你好”的调用以循环形式分布在正在运行的实例中:

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

现在关闭Say Hello服务器实例。功能区对已关闭的实例执行ping操作并将其视为已关闭后,您应该看到请求在其余实例之间开始达到平衡。

摘要

恭喜你!您刚刚开发了一个Spring应用程序,该应用程序对另一个应用程序的调用执行客户端负载平衡。

是否要编写新指南或为现有指南做出贡献?查看我们的贡献准则

所有指南均以代码的ASLv2许可证和写作的Attribution,NoDerivatives创作共用许可证发布