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. 单击 Dependencies 并选择 Spring Reactive Web(用于服务应用程序)或 Spring Reactive WebResilience4J(用于客户端应用程序)。

  4. 单击生成

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

如果您的 IDE 集成了 Spring Initializr,您可以从 IDE 中完成此过程。
您还可以从 Github fork 该项目并在您的 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://: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>4.0.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>2025.1.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 '4.0.0'
	id 'io.spring.dependency-management' version '1.1.5'
}

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

repositories {
	mavenCentral()
}

ext {
	springCloudVersion = '2025.1.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 断路器提供了一个名为 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://: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 的方法,我们可以用它来创建新的断路器。一旦我们有了断路器,我们所要做的就是调用 runrun 接受一个 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 许可,文字内容采用署名-禁止演绎知识共享许可

获取代码