Spring Cloud 断路器指南

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

您将构建什么

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

您需要什么

如何完成本指南

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

从头开始,请继续执行使用 Spring Initializr 开始

跳过基础知识,请执行以下操作

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

使用 Spring Initializr 开始

您可以使用此预初始化项目(适用于书店应用程序)或此预初始化项目(适用于阅读应用程序)并点击生成以下载 ZIP 文件。此项目配置为适合本教程中的示例。

手动初始化项目

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

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

  3. 点击依赖项并选择Spring Reactive Web(用于服务应用程序)或Spring Reactive WebResilience4J(用于客户端应用程序)。

  4. 点击生成

  5. 下载生成的 ZIP 文件,该文件是使用您选择的选项配置的 Web 应用程序的存档。

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

设置服务器微服务应用程序

书店服务有一个端点。它可以通过 /recommended 访问,并且(为简单起见)返回一个作为 StringMono 的推荐阅读列表。

主类,位于 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/src/main/resources/application.properties

server.port=8090

设置客户端微服务应用程序

阅读应用程序是我们对书店应用程序的前端。我们可以在 /to-read 上查看我们的阅读列表,并且该阅读列表是从书店服务应用程序检索的。

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("https://127.0.0.1:8090/recommended").retrieve()
      .bodyToMono(String.class);
  }

  public static void main(String[] args) {
    SpringApplication.run(ReadingApplication.class, args);
  }
}

要从书店获取列表,我们使用 Spring 的 WebClient 类。WebClient 对书店服务的 URL 发出 HTTP GET 请求(我们提供它),然后将结果作为 StringMono 返回。(有关使用 Spring 使用 WebClient 使用 RESTful 服务的更多信息,请参阅构建响应式 RESTful Web 服务指南。)

server.port 属性添加到 src/main/resources/application.properties

reading/src/main/resources/application.properties

server.port=8080

现在,我们可以在浏览器中访问阅读应用程序上的 /to-read 端点并查看我们的阅读列表。但是,由于我们依赖于书店应用程序,如果它发生任何事情或阅读应用程序无法访问书店,我们将没有列表,并且我们的用户会收到难看的 HTTP 500 错误消息。

应用断路器模式

Spring Cloud 的断路器库提供了断路器模式的实现:当我们将方法调用包装在断路器中时,Spring Cloud 断路器会监视对该方法的失败调用,如果失败累积到指定的阈值,Spring Cloud 断路器会打开电路,以便后续调用自动失败。在电路打开期间,Spring Cloud 断路器会重定向对该方法的调用,并将它们传递到我们指定的回退方法。

Spring Cloud 断路器支持许多不同的断路器实现,包括 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.3.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>2023.0.2</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.3.0'
	id 'io.spring.dependency-management' version '1.1.5'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
	mavenCentral()
}

ext {
	springCloudVersion = '2023.0.2'
}

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 断路器提供了一个名为 ReactiveCircuitBreakerFactory 的接口,我们可以使用它为我们的应用程序创建新的断路器。此接口的实现是自动配置的,基于应用程序类路径上的启动器。现在我们可以创建一个使用此接口对书店应用程序进行 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("https://127.0.0.1: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 参数充当我们的回退,如果出现任何问题。在我们的示例中,回退返回一个包含字符串 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);
  }
}

试试看

运行书店服务和阅读服务,然后在浏览器中打开阅读服务,地址为 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 应用程序,该应用程序使用断路器模式来防止级联故障并为可能失败的调用提供回退行为。

另请参阅

想编写新的指南或为现有指南做出贡献?查看我们的贡献指南

所有指南均以代码的 ASLv2 许可证和写作的署名-非衍生作品创作共用许可证发布。

获取代码