本指南将引导您完成使用Netflix Ribbon为微服务应用程序提供客户端负载平衡的过程。
Spring Cloud Netflix Ribbon现在已弃用。要查看当前推荐的客户端负载平衡方法的演示,请查看此指南。 |
你会建立什么
您将构建一个使用Netflix Ribbon和Spring Cloud Netflix的微服务应用程序,以在对另一个微服务的调用中提供客户端负载平衡。
你需要什么
-
约15分钟
-
最喜欢的文本编辑器或IDE
-
JDK 1.8或更高版本
-
您还可以将代码直接导入到IDE中:
如何完成本指南
像大多数Spring入门指南一样,您可以从头开始并完成每个步骤,也可以绕过您已经熟悉的基本设置步骤。无论哪种方式,您最终都可以使用代码。
要从头开始,请继续使用Gradle构建。
要跳过基础知识,请执行以下操作:
-
下载并解压缩本指南的源存储库,或使用Git对其进行克隆:
git clone https://github.com/spring-guides/gs-client-side-load-balancing.git
-
光盘进入
gs-client-side-load-balancing/initial
-
继续编写服务器服务。
完成后,您可以根据中的代码检查结果gs-client-side-load-balancing/complete
。
用Gradle构建
用Gradle构建
首先,您设置一个基本的构建脚本。在使用Spring构建应用程序时,可以使用任何喜欢的构建系统,但是此处包含使用Gradle和Maven所需的代码。如果您都不熟悉,请参阅使用Gradle构建Java项目或使用Maven构建Java项目。
创建目录结构
在您选择的项目目录中,创建以下子目录结构;例如,mkdir -p src/main/java/hello
在* nix系统上:
└──src main──主要 └──java └──你好
创建一个Gradle构建文件
以下是最初的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编译
创建目录结构
在您选择的项目目录中,创建以下子目录结构;例如,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进行构建
-
阅读如何将本指南直接导入Spring Tool Suite中。
-
在IntelliJ IDEA中阅读如何使用本指南。
编写服务器服务
我们的“服务器”服务称为“您好”。它将从可访问的端点返回随机问候(从三个静态列表中挑选)/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
。(我们还将指示应用程序的其他实例也可以在其他端口上运行,这样,当我们使该实例运行时,所有“说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.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
用户服务并看到友好的问候:
$ 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
(设置为false
)listOfServers
,和ServerListRefreshInterval
。Ribbon中的负载平衡器通常从Netflix Eureka服务注册表中获取其服务器列表。(有关在Spring Cloud上使用Eureka服务注册表的信息,请参阅服务注册和发现指南。)出于此处的简单目的,我们跳过了Eureka,因此我们将ribbon.eureka.enabled
属性设置为false
,而给Ribbon设置了static listOfServers
。ServerListRefreshInterval
是功能区服务列表刷新之间的间隔(以毫秒为单位)。
在我们的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。在这里,我们覆盖了默认负载均衡器使用的IPing
和IRule
。默认值IPing
是a NoOpPing
(实际上不会对服务器实例执行ping操作,而是始终报告它们是稳定的),而默认值IRule
是a ZoneAvoidanceRule
(这避免了服务器故障最多的Amazon EC2区域,因此可能有点困难在我们当地的环境中尝试)。
我们的IPing
是PingUrl
,它将对URL进行ping操作以检查每个服务器的状态。回想一下,您好,您好有一个映射到/
路径的方法;这意味着Ribbon对运行中的Say Hello服务器执行ping操作时将获得HTTP 200响应。在IRule
我们成立后,AvailabilityFilteringRule
将采用丝带的内置在“开路”状态,断路器功能,过滤掉任何服务器:如果ping失败连接到指定的服务器,或者如果它得到读取失败的服务器,Ribbon将认为该服务器“死机”,直到它开始正常响应为止。
在 这种方法确实意味着我们的Ribbon配置将成为主应用程序上下文的一部分,并因此由User应用程序中的所有Ribbon客户端共享。在普通应用程序中,可以通过将Ribbon bean保留在主应用程序上下文之外来避免这种情况(例如,在本示例中,您可以放入 |
尝试一下
使用任一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许可证发布。 |