Spring Cloud Circuit Breaker 指南

本指南將引導您使用 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. 按一下相依性,然後選擇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 Circuit Breaker 會監看對該方法的失敗呼叫,並且如果失敗累積到指定的閾值,Spring Cloud Circuit Breaker 就會開啟電路,以便後續呼叫自動失敗。 當電路開啟時,Spring Cloud Circuit Breaker 會將呼叫重新導向到該方法,並且它們會傳遞到我們指定的 Fallback 方法。

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.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 Circuit Breaker 提供一個名為 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 的單一方法,我們可以使用它來建立新的斷路器。 建立斷路器後,我們所要做的就是呼叫 runrun 採用 MonoFlux 和一個可選的 Function。 如果發生任何問題,可選的 Function 參數會充當我們的 Fallback。 在我們的範例中,Fallback 會傳回一個包含 String 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 應用程式,該應用程式使用斷路器模式來防止級聯失敗,並為可能失敗的呼叫提供 Fallback 行為。

另請參閱

想要編寫新的指南或貢獻現有的指南嗎? 請查看我們的 貢獻指南

所有指南均以 ASLv2 授權發布程式碼,並以 姓名標示、禁止改作創用 CC 授權 發布寫作。

取得程式碼