开篇词
该指南将引导你使用 Netflix Hystrix 容错库将断路器应用于可能失败的方法调用。
你将创建的应用
我们将构建一个微服务应用,该应用在方法调用失败时使用断路器模式来对功能进行优雅降级。使用 Circuit Breaker 模式可以使微服务在相关服务失败时继续运行,从而防止故障级联并未失败的服务提供恢复时间。
你将需要的工具
- 大概 15 分钟左右;
- 你最喜欢的文本编辑器或集成开发环境(IDE)
- JDK 1.8 或更高版本;
- Gradle 4+ 或 Maven 3.2+
- 你还可以将代码直接导入到 IDE 中:
如何完成这个指南
像大多数的 Spring 入门指南一样,你可以从头开始并完成每个步骤,也可以绕过你已经熟悉的基本设置步骤。如论哪种方式,你最终都有可以工作的代码。
- 要从头开始,移步至从 Spring Initializr 开始;
- 要跳过基础,执行以下操作:
- 下载并解压缩该指南将用到的源代码,或借助 Git 来对其进行克隆操作:
git clone https://github.com/spring-guides/gs-circuit-breaker.git
- 切换至
gs-circuit-breaker/initial
目录; - 跳转至该指南的搭建服务端微服务应用。
- 下载并解压缩该指南将用到的源代码,或借助 Git 来对其进行克隆操作:
待一切就绪后,可以检查一下 gs-circuit-breaker/complete
目录中的代码。
从 Spring Initializr 开始
对于所有的 Spring 应用来说,你应该从 Spring Initializr 开始。Initializr 提供了一种快速的方法来提取应用程序所需的依赖,并为你完成许多设置。该指南需要两个应用。第一个应用(一个简单的书店站点)仅需要 Web 依赖。下图显示了此示例项目的 Initializr 设置:
上图显示了选择 Maven 作为构建工具的 Initializr。你也可以使用 Gradle。它还将
com.example
和circuit-breaker-bookstore
的值分别显示为 Group 和 Artifact。在本示例的其余部分,将用到这些值。
以下清单显示了选择 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.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>circuit-breaker-bookstore</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>circuit-breaker-bookstore</name>
<description>Demo project for Spring Boot</description>
<properties>
<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>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
以下清单显示了在选择 Gradle 时创建的 build.gradle
文件:
plugins {
id 'org.springframework.boot' version '2.2.2.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.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') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
test {
useJUnitPlatform()
}
第二个应用(阅读应用,将使用 Hystrix 断路器)需要 Web 和 Hystrix 依赖。下图显示了为配置客户端搭建的 Initializr:
上图显示了选择 Maven 作为构建工具的 Initializr。你也可以使用 Gradle。它还将
com.example
和circuit-breaker-reading
的值分别显示为 Group 和 Artifact。在本示例的其余部分,将用到这些值。
以下清单显示了选择 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.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>circuit-breaker-reading</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>circuit-breaker-reading</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.M3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</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>
以下清单显示了在选择 Gradle 时创建的 build.gradle
文件:
plugins {
id 'org.springframework.boot' version '2.2.2.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
}
ext {
set('springCloudVersion', "Hoxton.SR1")
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-hystrix'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
test {
useJUnitPlatform()
}
为方便起见,我们在项目的顶部(
bookstore
和reading
目录上方的一个目录)提供了构建文件(pom.xml
及build.gradle
文件),我们可以使用它们一次构建两个项目。我们还在那里添加了 Maven 和 Gradle 包装器。
搭建服务端微服务应用
Bookstore 服务将有单个端点。可以通过 /recommended
访问它,并且(为简单起见)将以 String
形式返回推荐的阅读列表。
我们需要在 BookstoreApplication.java
中创建主类。它应该看起来像以下清单(来自 bookstore/src/main/java/com/example/circuitbreakerbookstore/CircuitBreakerBookstoreApplication.java
):
package com.example.circuitbreakerbookstore;
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;
@RestController
@SpringBootApplication
public class CircuitBreakerBookstoreApplication {
@RequestMapping(value = "/recommended")
public String readingList(){
return "Spring in Action (Manning), Cloud Native Java (O'Reilly), Learning Spring Boot (Packt)";
}
public static void main(String[] args) {
SpringApplication.run(CircuitBreakerBookstoreApplication.class, args);
}
}
@RestController
注解指示 BookstoreApplication 是 REST 控制器类,并确保该类中的任何 @RequestMapping
方法的行为都与使用 @ResponseBody
进行注解一样。也就是说,该类中的 @RequestMapping
方法的返回值会自动从其原始类型中适当地转换,并直接写入响应主体。
我们将在本地运行该应用,与其一起的还有一个消耗性应用。结果是,在 src/main/resources/application.properties
中,我们需要设置 server.port
,以使 Bookstore 服务在运行时不会与使用中的应用冲突。以下清单(来自 bookstore/src/main/resources/application.properties
)显示了怎么做:
server.port=8090
搭建一个客户端微服务应用
阅读应用将是书店应用的消费者(建模访问者)。我们可以在 /to-read
处查看我们的阅读列表,然后从书店服务应用中检索该阅读列表。以下示例(来自 reading/src/main/java/com/example/circuitbreakerreading/CircuitBreakerReadingApplication.java
)显示了该类:
package com.example.circuitbreakerreading;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.web.client.RestTemplate;
@EnableCircuitBreaker
@RestController
@SpringBootApplication
public class CircuitBreakerReadingApplication {
@Autowired
private BookService bookService;
@Bean
public RestTemplate rest(RestTemplateBuilder builder) {
return builder.build();
}
@RequestMapping("/to-read")
public String toRead() {
return bookService.readingList();
}
public static void main(String[] args) {
SpringApplication.run(CircuitBreakerReadingApplication.class, args);
}
}
要从书店获取列表,可以使用 Spring 的 RestTemplate
模版类。RestTemplate
向书店服务的 URL 发出 HTTP GET 请求,并以 String
形式返回结果。(有关使用 Spring 使用 RESTful 服务的更多信息,请参阅《使用 RESTful Web 服务指南》。)为此,我们需要将 server.port
属性添加到 reading/src/main/resources/application.properties
,如下所示:以下清单显示:
server.port=8080
现在,我们可以在浏览器中访问阅读应用的 /to-read
端点,并查看我们的阅读列表。但是,由于我们依赖书店应用,因此如果发生任何事情或阅读应用无法访问书店,我们将没有列表,并且用户将收到讨厌的 HTTP 500 错误消息。
应用断路器模式
Netflix 的 Hystrix 库提供了断路器模式的实现。当我们将断路器应用于某个方法时,Hystrix 会监视对该方法的失败调用,并且,如果失败达到阈值,则 Hystrix 会断开电路,以便后续调用会自动失败。当电路断开时,Hystrix 将重定对方法的调用,然后将它们传递到我们指定的后背方法。
Spring Cloud Netflix Hystrix 会寻找带有 @HystrixComamnd
注解的任何方法,并将该方法包装在与断路器连接的代理中,以便 Hystrix 可以对其进行监视。当前,这仅在标有 @Component
或 @Service
的类中有效。因此,在阅读应用中,在 src/main/java/com/example/circuitbreakerreading
下,我们需要添加一个新类(称为 BookService
)。
RestTemplate
在创建后将被注入到 BookService
的构造函数中。下面的清单(来自 reading/src/main/java/com/example/circuitbreakerreading/BookService.java
)显示了 BookService
类:
package com.example.circuitbreakerreading;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.net.URI;
@Service
public class BookService {
private final RestTemplate restTemplate;
public BookService(RestTemplate rest) {
this.restTemplate = rest;
}
@HystrixCommand(fallbackMethod = "reliable")
public String readingList() {
URI uri = URI.create("http://localhost:8090/recommended");
return this.restTemplate.getForObject(uri, String.class);
}
public String reliable() {
return "Cloud Native Java (O'Reilly)";
}
}
我们已将 @HystrixCommand
应用于原始的 readingList()
方法。我们在这里还有一个新方法:trusted()
。@HystrixCommand
注解具有可靠的 fallbackMethod
。如果由于某种原因,Hystrix 在 readingList() 上断开了电路,则我们将为用户准备好一个很好的(如果够短)占位符阅读列表。
在我们的主类 ReadingApplication
中,我们需要创建一个 RestTemplate
bean,注入 BookService
,并为我们的阅读列表调用它。以下示例(来自 reading/src/main/java/com/example/circuitbreakerreading/CircuitBreakerReadingApplication.java
)显示了如何执行该操作:
package com.example.circuitbreakerreading;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.web.client.RestTemplate;
@EnableCircuitBreaker
@RestController
@SpringBootApplication
public class CircuitBreakerReadingApplication {
@Autowired
private BookService bookService;
@Bean
public RestTemplate rest(RestTemplateBuilder builder) {
return builder.build();
}
@RequestMapping("/to-read")
public String toRead() {
return bookService.readingList();
}
public static void main(String[] args) {
SpringApplication.run(CircuitBreakerReadingApplication.class, args);
}
}
现在,要从 Bookstore 服务检索列表,可以调用 bookService.readingList()
。我们还应该添加最后一个注解 @EnableCircuitBreaker
。该注解告诉 Spring Cloud 读取应用使用断路器并启用对其的监视,断开和闭合(在我们的案例中,该行为由 Hystrix 提供)。
尝试一下
要使用 Maven 运行 Bookstore Web 应用,请在终端窗口(在 /complete
目录中)中运行以下命令:
./mvnw spring-boot:run -pl bookstore
要使用 Maven 运行 Reading 断路器应用,请在终端窗口(在 /complete
目录中)中运行以下命令:
./mvnw spring-boot:run -pl reading
要使用 Gradle 运行 Bookstore Web 应用,请在终端窗口(在 /complete
目录中)中运行以下命令:
./gradlew :bookstore:bootRun
要使用 Maven 运行 Reading 断路器应用,请在终端窗口(在 /complete
目录中)中运行以下命令:
./gradlew :reading:bootRun
要测试我们的断路器,请同时运行书店服务和阅读服务,然后在 localhost:8080/to-read 上打开阅读服务的浏览器。我们应该会看到完整的建议阅读清单,如下清单所示:
Spring in Action (Manning), Cloud Native Java (O'Reilly), Learning Spring Boot (Packt)
现在停止书店应用。我们的列表来源不见了,但是多亏了 Hystrix 和 Spring Cloud Netflix,我们才有了可靠的缩写列表。我们应该看到以下内容:
Cloud Native Java (O'Reilly)
概述
恭喜你!我们刚刚开发了一个 Spring 应用,该应用使用断路器模式来防止级联故障并为潜在的失败调用提供降级行为。
参见
以下指南也可能会有所帮助:
想看指南的其他内容?请访问该指南的所属专栏:《Spring 官方指南》