Spring Cloud Circuit Breaker 指南

本指南将引导您完成使用 Spring Cloud Circuit Breaker 将熔断器应用于可能失败的方法调用的过程。

您将构建什么

您将构建一个微服务应用程序,该程序使用熔断器模式(Circuit Breaker pattern),在方法调用失败时优雅地降级功能。使用熔断器模式可以使微服务在相关服务失败时继续运行,防止故障级联,并给失败的服务时间恢复。

您需要什么

如何完成本指南

与大多数 Spring 入门指南一样,您可以从头开始并完成每个步骤,或者跳过您已经熟悉的基本设置步骤。无论哪种方式,您最终都会得到可工作的代码。

从头开始,请继续阅读使用 Spring Initializr 开始

跳过基础步骤,请执行以下操作

完成后,您可以对照 gs-cloud-circuit-breaker/complete 中的代码检查您的结果。

使用 Spring Initializr 开始

您可以使用此预初始化项目(用于 Bookstore 应用程序)或此预初始化项目(用于 Reading 应用程序),然后点击 Generate 下载 ZIP 文件。此项目已配置好以适合本教程中的示例。

手动初始化项目

  1. 访问 https://start.spring.io。此服务会引入应用程序所需的所有依赖项,并为您完成大部分设置工作。

  2. 选择 Gradle 或 Maven 以及您想使用的语言。本指南假设您选择了 Java。

  3. 点击 Dependencies,然后选择 Spring Reactive Web(用于服务应用程序)或 Spring Reactive WebResilience4j(用于客户端应用程序)。

  4. 点击 Generate

  5. 下载生成的 ZIP 文件,这是一个根据您的选择配置好的 Web 应用程序存档。

如果您的 IDE 集成了 Spring Initializr,您可以在 IDE 中完成此过程。
您也可以从 Github Fork 项目,并在您的 IDE 或其他编辑器中打开它。

设置服务器微服务应用

Bookstore 服务只有一个端点。它可通过 /recommended 访问,并且(为简单起见)以 Mono 形式返回一个推荐阅读列表,其内容为 String

主类 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 请求,然后将结果作为 Mono 形式的 String 返回。(有关使用 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 应用,如果 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、Sentinel 和 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 方法接受一个 MonoFlux 以及一个可选的 Function。可选的 Function 参数在出现问题时充当我们的备用(fallback)。在我们的示例中,备用方法返回一个包含字符串 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 应用,该应用使用熔断器模式来防止级联故障,并为可能失败的调用提供备用行为。

另请参阅

想撰写新指南或贡献现有指南?请查阅我们的贡献指南

所有指南的代码均以 ASLv2 许可发布,文字内容则以署名-禁止演绎创作共用许可发布。

获取代码