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

Spring Cloud 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
            └──你好

创建一个Gradle构建文件

say-hello/build.gradle

buildscript {
  ext {
    springBootVersion = '2.3.7.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 = 11
targetCompatibility = 11

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.3.7.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 = 11
targetCompatibility = 11

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:Hoxton.SR9"
  }
}

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的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
            └──你好

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.3.7.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<java.version>11</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.3.7.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>11</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>Hoxton.SR9</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启动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/resourcesapplication.yml在其中创建文件,然后在该文件中为设置默认值server.port。(我们还将指示应用程序的其他实例也可以在其他端口上运行,这样,当我们使该实例运行时,所有“说Hello”实例都不会与客户端发生冲突。)当我们在该文件中时,我们也会spring.application.name为我们的服务设置。

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

spring:
  application:
    name: say-hello

server:
  port: 8090

从客户服务访问

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

在User application目录中的下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在提供Say Hello服务的URL时发出HTTP GET请求,并将结果显示为String。(有关使用Spring使用RESTful服务的更多信息,请参阅《使用RESTful Web服务指南》。)

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

跨服务器实例的负载平衡

现在我们可以访问/hi用户服务并看到友好的问候:

$ curl http:// localhost:8888 / hi
问候,Artaban!

$ curl http:// localhost:8888 / hi?name =奥龙特斯
问候,奥龙特斯!

要从单个硬编码的服务器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 NetflixApplicationContext在我们的应用程序中为每个Ribbon用户名创建一个。这用于为客户端提供一组用于Ribbon组件实例的bean,包括:

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

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

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

  • an IRule,它描述了负载平衡策略,以及

  • an IPing,它表示如何执行服务器的定期ping。

在上述情况下,客户端名为say-hello。我们设置的属性是eureka.enabled(设置为falselistOfServers,和ServerListRefreshInterval。Ribbon中的负载平衡器通常从Netflix Eureka服务注册表中获取其服务器列表。(有关在Spring Cloud上使用Eureka服务注册表的信息,请参阅服务注册和发现指南。)出于此处的简单目的,我们跳过了Eureka,因此我们将ribbon.eureka.enabled属性设置为false,而给Ribbon设置了static 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.javauser/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是a NoOpPing(实际上不会对服务器实例执行ping操作,而是始终报告它们是稳定的),而默认值IRule是a ZoneAvoidanceRule(这避免了服务器故障最多的Amazon EC2区域,因此可能有点困难在我们当地的环境中尝试)。

我们的IPingPingUrl,它将对URL进行ping操作以检查每个服务器的状态。回想一下,您好,您好有一个映射到/路径的方法;这意味着Ribbon对运行中的Say Hello服务器执行ping操作时将获得HTTP 200响应。在IRule我们成立后,AvailabilityFilteringRule将采用丝带的内置在“开路”状态,断路器功能,过滤掉任何服务器:如果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:运行

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

$ SERVER_PORT = 9092 ./gradlew bootRun

或Maven:

$ SERVER_PORT = 9999 mvn spring-boot:运行

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

2016-03-09 21:13:22.115信息90046 --- [nio-8090-exec-1] hello.SayHelloApplication:访问/
2016-03-09 21:13:22.629信息90046 --- [nio-8090-exec-3] hello.SayHelloApplication:访问/

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

2016-03-09 21:15:28.915信息90046 --- [nio-8090-exec-7] hello.SayHelloApplication:访问/问候

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

概括

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

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

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