Spring Boot 与 Docker
观察 GraphQL 的实际运行

本指南将引导您了解如何使用 Spring Cloud Circuit Breaker 为可能失败的方法调用应用熔断器。

您将构建什么

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

所需条件

如何完成本指南

与大多数 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. 点击 Generate

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

如果您的 IDE 集成了 Spring Initializr,您可以直接在 IDE 中完成此过程。

你也可以从 Github 上 fork 这个项目,然后在你的 IDE 或其他编辑器中打开它。

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

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

主类位于 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 方法接受一个 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);
  }
}

尝试一下

同时运行 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/)

本页目录