本指南将引导您完成使用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 └── hello
创建一个Gradle构建文件
以下是最初的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构建
创建目录结构
在您选择的项目目录中,创建以下子目录结构;例如, 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进行构建
-
阅读如何将本指南直接导入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
。(我们还将指示该应用程序的其他实例也可以在其他端口上运行,这样,当我们让该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.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 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
), listOfServers
和ServerListRefreshInterval
。Ribbon中的负载平衡器通常从Netflix Eureka服务注册表中获取服务器列表。(有关在Spring Cloud上使用Eureka服务注册表的信息,请参阅《 服务注册和发现》指南。)为了简单起见,我们跳过了Eureka,因此我们将ribbon.eureka.enabled
财产false
而是给功能区一个静态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
是一个NoOpPing
(实际上并不会ping服务器实例,而是总是报告它们是稳定的),并且默认IRule
是一个ZoneAvoidanceRule
(这避免了服务器故障最多的Amazon EC2区域,因此在我们的本地环境中尝试可能有点困难)。
我们的IPing
是一个PingUrl
,它将对URL进行ping操作以检查每个服务器的状态。回忆一下,您好说,您好,有一个方法映射到/
路径;这意味着Ribbon对运行中的Say Hello服务器执行ping操作时将获得HTTP 200响应。的IRule
我们建立了AvailabilityFilteringRule
,将使用Ribbon的内置断路器功能过滤掉处于“断路”状态的任何服务器:如果ping无法连接到给定的服务器,或者服务器读取失败,Ribbon将认为服务器“死机”,直到开始正常响应为止。
的 这种方法确实意味着我们的Ribbon配置将成为主应用程序上下文的一部分,并因此由User应用程序中的所有 Ribbon客户端共享。在普通应用程序中,可以通过将Ribbon bean保留在主应用程序上下文之外来避免这种情况(例如,在本示例中,您可以将 |
尝试一下
使用任一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创作共用许可证发布 。 |