本指南将引导您了解如何使用 Spring Cloud Circuit Breaker 为可能失败的方法调用应用熔断器。
您将构建什么
您将构建一个微服务应用程序,该应用程序使用断路器模式在方法调用失败时优雅地降低功能。使用断路器模式可以使微服务在相关服务失败时继续运行,防止故障级联,并为失败的服务提供恢复时间。
所需条件
-
大约 15 分钟
-
喜爱的文本编辑器或 IDE
-
Java 17 或更高版本
-
您也可以直接将代码导入到您的 IDE 中:
如何完成本指南
与大多数 Spring 入门指南一样,您可以从头开始并完成每个步骤,或者可以跳过您已经熟悉的基本设置步骤。无论哪种方式,您最终都会得到可运行的代码。
要从头开始,请继续阅读从 Spring Initializr 开始。
要跳过基础知识,请执行以下操作:
-
下载并解压本指南的源代码仓库,或者使用 Git 克隆它:
git clone https://github.com/spring-guides/gs-cloud-circuit-breaker.git
-
进入
gs-cloud-circuit-breaker/initial
目录 -
直接跳转到 设置服务器微服务应用程序。
完成后,您可以将您的结果与 gs-cloud-circuit-breaker/complete
中的代码进行对比。
从 Spring Initializr 开始
您可以使用这个预初始化项目(适用于书店应用程序)或这个预初始化项目(适用于阅读应用程序),然后点击生成以下载 ZIP 文件。该项目已配置为适合本教程中的示例。
手动初始化项目:
-
访问 https://start.spring.io。该服务会拉取应用程序所需的所有依赖项,并为您完成大部分设置工作。
-
选择 Gradle 或 Maven 以及您想要使用的语言。本指南假设您选择了 Java。
-
点击 Dependencies,然后选择 Spring Reactive Web(用于服务应用程序)或 Spring Reactive Web 和 Resilience4J(用于客户端应用程序)。
-
点击 Generate。
-
下载生成的 ZIP 文件,这是一个根据您的选择配置的 Web 应用程序存档。
如果您的 IDE 集成了 Spring Initializr,您可以直接在 IDE 中完成此过程。
你也可以从 Github 上 fork 这个项目,然后在你的 IDE 或其他编辑器中打开它。
设置一个服务器微服务应用程序
Bookstore 服务有一个单一的端点。它可以通过 /recommended
访问,并且(为了简单起见)返回一个推荐阅读列表作为 String
的 Mono
。
主类位于 bookstore/src/main/java/hello/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);
}
}
要从书店获取列表,我们使用 Spring 的 WebClient
类。WebClient
会向我们提供的书店服务 URL 发起一个 HTTP GET 请求,然后将结果作为 Mono<String>
返回。(有关使用 WebClient
通过 Spring 消费 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 库提供了断路器模式的实现:当我们将方法调用包装在断路器中时,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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>spring-cloud-circuit-breaker-reading</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-cloud-circuit-breaker-reading</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2024.0.0</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<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-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</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>
</project>
reading/build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.4.0'
id 'io.spring.dependency-management' version '1.1.5'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
repositories {
mavenCentral()
}
ext {
springCloudVersion = '2024.0.0'
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-reactor-resilience4j'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.projectreactor:reactor-test'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
tasks.named('test') {
useJUnitPlatform()
}
Spring Cloud Circuit Breaker 提供了一个名为 ReactiveCircuitBreakerFactory
的接口,我们可以使用该接口为应用程序创建新的断路器。根据应用程序类路径中的 starter,该接口的实现会自动配置。现在我们可以创建一个新服务,使用该接口向 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
方法。run
方法接受一个 Mono
或 Flux
以及一个可选的 Function
参数。这个可选的 Function
参数在出现问题时充当我们的回退函数。在我们的示例中,回退函数返回一个包含字符串 Cloud Native Java (O’Reilly)
的 Mono
。
有了我们的新服务后,我们可以更新 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);
}
}
尝试一下
同时运行 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 应用程序,该模式可以防止级联故障,并为可能失败的调用提供回退行为。
另请参阅
构建一个响应式的 RESTful Web 服务](https://spring.io/guides/gs/reactive-rest-service/)