开篇词
该指南将引导你使用 Spring Cloud Circuit Breaker 将断路器应用于可能失败的方法调用。
你将创建的应用
我们将构建一个微服务应用,该应用将在方法调用失败时使用断路器模式优雅地降级功能。使用断路器模式可以使微服务在相关服务失败时继续运行,从而防止故障级联并为失败的服务提供恢复时间。
你将需要的工具
- 大概 15 分钟左右;
- 你最喜欢的文本编辑器或集成开发环境(IDE)
- JDK 1.8 或更高版本;
- Gradle 4+ 或 Maven 3.2+
- 你还可以将代码直接导入到 IDE 中:
如何完成这个指南
像大多数的 Spring 入门指南一样,你可以从头开始并完成每个步骤,也可以绕过你已经熟悉的基本设置步骤。如论哪种方式,你最终都有可以工作的代码。
- 要从头开始,移步至用 Gradle 来构建;
- 要跳过基础,执行以下操作:
- 下载并解压缩该指南将用到的源代码,或借助 Git 来对其进行克隆操作:
git clone https://github.com/spring-guides/gs-cloud-circuit-breaker.git
- 切换至
gs-cloud-circuit-breaker/initial
目录; - 跳转至该指南的搭建服务端微服务应用。
- 下载并解压缩该指南将用到的源代码,或借助 Git 来对其进行克隆操作:
待一切就绪后,可以检查一下 gs-cloud-circuit-breaker/complete
目录中的代码。
用 Gradle 来构建
首先,我们设置一个基本的构建脚本。在使用 Spring 构建应用时可以使用任何喜欢的构建系统,但此处包含使用 Gradle 和 Maven 所需的代码。如果你都不熟悉,请参阅使用 Gradle 构建 Java 项目或使用 Maven 构建 Java 项目。
创建目录结构
在我们选择的项目目录中,创建以下自目录结构;例如,在 *nix 系统上使用 mkdir -p src/main/java/hello
:
└── src
└── main
└── java
└── hello
创建 Gradle 构建文件
settings.gradle
rootProject.name = 'gs-cloud-circuit-breaker'
include 'bookstore'
include 'reading'
以下是初始 Gradle 构建文件。
bookstore/build.gradle
buildscript {
ext {
springBootVersion = '2.2.2.RELEASE'
}
repositories {
mavenCentral()
maven { url 'https://repo.spring.io/milestone' }
maven { url 'https://repo.spring.io/snapshot' }
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
bootJar {
baseName = 'bookstore'
version = '0.0.1-SNAPSHOT'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-webflux')
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'
}
}
reading/build.gradle
buildscript {
ext {
springBootVersion = '2.2.2.RELEASE'
}
repositories {
mavenCentral()
maven { url 'https://repo.spring.io/milestone' }
maven { url 'https://repo.spring.io/snapshot' }
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
bootJar {
baseName = 'reading'
version = '0.0.1-SNAPSHOT'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile('spring-cloud-starter-circuitbreaker-reactor-resilience4j')
compile('org.springframework.boot:spring-boot-starter-webflux')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:Hoxton.SR1"
}
}
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,并构建一个可运行的单个超级 jar,这使执行和传输服务更加方便;
- 它搜索
public static void main()
方法并将其标记为可运行类; - 它提供了一个内置的依赖解析器,用于设置版本号以及匹配 Spring Boot 依赖。我们可以覆盖所需的任何版本,但默认为 Boot 选择的一组版本。
用 Maven 来构建
首先,我们搭建一个基本的构建脚本。使用 Spring 构建应用时,可以使用任何喜欢的构建系统,但是此处包含了使用 Maven 所需的代码。如果你不熟悉 Maven,请参阅使用 Maven 构建 Java 项目。
创建目录结构
在我们选择的项目目录中,创建以下自目录结构;例如,在 *nix 系统上使用 mkdir -p src/main/java/hello
:
└── src
└── main
└── java
└── hello
创建 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>
<groupId>org.springframework</groupId>
<artifactId>gs-circuit-breaker</artifactId>
<version>0.1.0</version>
<packaging>pom</packaging>
<modules>
<module>bookstore</module>
<module>reading</module>
</modules>
</project>
以下是初始 Maven 构建文件。
bookstore/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>bookstore</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.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-webflux</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>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/libs-snapshot-local</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/release</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
reading/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>reading</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.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-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</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.SR1</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-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/libs-snapshot-local</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/release</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
Spring Boot Maven 插件提供了许多方便的功能:
- 它收集类路径上的所有 jar,并构建一个可运行的单个超级 jar,这使执行和传输服务更加方便;
- 它搜索
public static void main()
方法并将其标记为可运行类; - 它提供了一个内置的依赖解析器,用于设置版本号以及匹配 Spring Boot 依赖。我们可以覆盖所需的任何版本,但默认为 Boot 选择的一组版本。
用 IDE 来构建
- 阅读如何将该指南直接导入 Spring Tool Suite;
- 阅读如何在 IntelliJ IDEA 中使用该指南。
搭建服务端微服务应用
Bookstore 服务将具有一个端点。可以通过 /recommended
访问它,并且(为简单起见)将返回 String
类型的 Mono
推荐阅读列表。
在 BookstoreApplication.java
中编辑我们的主类。它看起来应该像这样:
bookstore/src/main/java/hello/BookstoreApplication.java
package hello;
import reactor.core.publisher.Mono;
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 BookstoreApplication {
@RequestMapping(value = "/recommended")
public Mono<String> readingList(){
return Mono.just("Spring in Action (Manning), Cloud Native Java (O'Reilly), Learning Spring Boot (Packt)");
}
public static void main(String[] args) {
SpringApplication.run(BookstoreApplication.class, args);
}
}
@RestController
注解将 BookstoreApplication
标注为控制器类,就像 @Controller
一样,并确保该类中的 @RequestMapping
方法的行为就像使用 @ResponseBody
进行标注一样。也就是说,该类中 @RequestMapping
方法的返回值将自动从其原始类型中适当转换,并将直接写入响应主体。
我们将与客户端服务应用一起在本地运行该应用,因此在 src/main/resources/application.properties
中,设置 server.port
,以使 Bookstore 服务在我们运行时不会与客户端冲突。
bookstore/src/main/resources/application.properties
server.port=8090
搭建客户端微服务应用
Reading 应用将成为 Bookstore 应用的前端。我们将在 /to-read
处查看我们的阅读列表,并且该阅读列表将从 Bookstore 服务应用中检索。
reading/src/main/java/hello/ReadingApplication.java
package hello;
import reactor.core.publisher.Mono;
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.springframework.web.reactive.function.client.WebClient;
@RestController
@SpringBootApplication
public class ReadingApplication {
@RequestMapping("/to-read")
public Mono<String> toRead() {
return WebClient.builder().build()
.get().uri("http://localhost:8090/recommended").retrieve()
.bodyToMono(String.class);
}
public static void main(String[] args) {
SpringApplication.run(ReadingApplication.class, args);
}
}
为了从 Bookstore 获取列表,我们使用的是 Spring 的 WebClient
类。当我们提供 WebClient
时,它会向 Bookstore 服务的 URL 发出 HTTP GET 请求,然后以 String
类型的 Mono
返回结果。(有关使用 Spring 通过 WebClient 使用 RESTful 服务的更多信息,请参阅《构建反应式 RESTful Web 服务》指南。)
将 server.port
属性添加到 src/main/resources/application.properties
:
reading/src/main/resources/application.properties
server.port=8080
现在,我们可以在浏览器中访问 Reading 应用上的 /to-read
端点,并查看我们的阅读列表。但是,由于我们依赖的 Bookstore 应用,因此如果发生任何事情,或者如果 Reading 根本无法访问 Bookstore,我们将没有列表,并且我们的用户会收到讨厌的 HTTP 500
错误消息。
应用断路器模式
Spring Cloud 的 Circuit Breaker 库提供了 Circuit Breaker 模式的实现:当我们将方法调用包装到断路器中时,Spring Cloud Circuit Breaker 监视对该方法的调用失败,如果失败达到阈值,则 Spring Cloud Circuit Breaker 断开电路,以便随后的呼叫自动失败。电路断开时,Spring Cloud Circuit Breaker 会将调用重定向到该方法,然后将它们传递到我们指定的后备方法。
Spring Cloud Circuit Breaker 支持许多不同的断路器实现,包括 Resilience4J、Hystrix、Sentinal 及 Spring Retry。在该指南中,我们将使用 Resilience4J 实现。要使用该实现,我们只需要在应用的类路径中添加 spring-cloud-starter-circuitbreaker-reactor-resilience4j
。
reading/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>reading</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.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-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</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.SR1</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-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/libs-snapshot-local</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/release</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
reading/build.gradle
buildscript {
ext {
springBootVersion = '2.2.2.RELEASE'
}
repositories {
mavenCentral()
maven { url 'https://repo.spring.io/milestone' }
maven { url 'https://repo.spring.io/snapshot' }
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
bootJar {
baseName = 'reading'
version = '0.0.1-SNAPSHOT'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.cloud:spring-cloud-starter-circuitbreaker-reactor-resilience4j')
compile('org.springframework.boot:spring-boot-starter-webflux')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:Hoxton.SR1"
}
}
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 Cloud Circuit Breaker 提供了一个名为 ReactiveCircuitBreakerFactory
的接口,我们可以使用该接口为我们的应用创建新的断路器。该接口的实现将根据我们应用的类路径上的启动器进行自动配置。让我们创建一个使用该接口的新服务器,以对 Bookstore 应用进行 API 调用。
reading/src/main/java/hello/BookService.java
package hello;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreaker;
import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreakerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
@Service
public class BookService {
private static final Logger LOG = LoggerFactory.getLogger(BookService.class);
private final WebClient webClient;
private final ReactiveCircuitBreaker readingListCircuitBreaker;
public BookService(ReactiveCircuitBreakerFactory circuitBreakerFactory) {
this.webClient = WebClient.builder().baseUrl("http://localhost:8090").build();
this.readingListCircuitBreaker = circuitBreakerFactory.create("recommended");
}
public Mono<String> readingList() {
return readingListCircuitBreaker.run(webClient.get().uri("/recommended").retrieve().bodyToMono(String.class), throwable -> {
LOG.warn("Error making request to book service", throwable);
return Mono.just("Cloud Native Java (O'Reilly)");
});
}
}
ReactiveCircuitBreakerFactory
有一个名为 create
的方法,我们可以使用它创建新的断路器。一旦有了断路器,我们所要做的就是调用 run
。运行需要 Mono
或 Flux
以及可选 Function
。如果出现任何问题,可选的 Function
参数将作为我们的后备。在该示例中,后备将仅返回包含 String
类型的 Cloud Native Java (O’Reilly)
。
使用我们的新服务后,我们可以更新 ReadingApplication
中的代码以使用该新服务。
reading/src/main/java/hello/ReadingApplication.java
package hello;
import reactor.core.publisher.Mono;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.reactive.function.client.WebClient;
@RestController
@SpringBootApplication
public class ReadingApplication {
@Autowired
private BookService bookService;
@RequestMapping("/to-read")
public Mono<String> toRead() {
return bookService.readingList();
}
public static void main(String[] args) {
SpringApplication.run(ReadingApplication.class, args);
}
}
试一下
要使用 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
运行 Bookstore 服务和 Reading 服务,然后打开浏览器以访问 Reading 服务,位于 localhost:8080/to-read。我们应该看到完整的推荐阅读清单:
Spring in Action (Manning), Cloud Native Java (O'Reilly), Learning Spring Boot (Packt)
现在关闭 Bookstore 应用。我们的列表来源不见了,但是感谢 Hystrix 和 Spring Cloud Netflix,我们又一个可靠的缩写列表来填补空白。我们应该看到:
Cloud Native Java (O'Reilly)
概述
恭喜你!我们刚刚开发了一个 Spring 应用,该应用使用 Circuit Breaker 模式来防止级联故障并未潜在的失败呼叫提供后备行为。
想看指南的其他内容?请访问该指南的所属专栏:《Spring 官方指南》