SpringBoot 2 使用 Spring Cloud Circuit Breaker 实现断路器

开篇词

该指南将引导你使用 Spring Cloud Circuit Breaker 将断路器应用于可能失败的方法调用。
 

你将创建的应用

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

你将需要的工具

如何完成这个指南

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

  • 要从头开始,移步至用 Gradle 来构建
  • 要跳过基础,执行以下操作:
    • 下载并解压缩该指南将用到的源代码,或借助 Git 来对其进行克隆操作:git clone https://github.com/spring-guides/gs-cloud-circuit-breaker.git
    • 切换至 gs-cloud-circuit-breaker/initial 目录;
    • 跳转至该指南的搭建服务端微服务应用

待一切就绪后,可以检查一下 gs-cloud-circuit-breaker/complete 目录中的代码。
 

用 Gradle 来构建

首先,我们设置一个基本的构建脚本。在使用 Spring 构建应用时可以使用任何喜欢的构建系统,但此处包含使用 GradleMaven 所需的代码。如果你都不熟悉,请参阅使用 Gradle 构建 Java 项目使用 Maven 构建 Java 项目

创建目录结构

在我们选择的项目目录中,创建以下自目录结构;例如,在 *nix 系统上使用 mkdir -p src/main/java/hello

└── src
    └── main
        └── java
            └── hello

创建 Gradle 构建文件

settings.gradle

rootProject.name = 'gs-cloud-circuit-breaker'

include 'bookstore'
include 'reading'

以下是初始 Gradle 构建文件。
bookstore/build.gradle

buildscript {
	ext {
		springBootVersion = '2.2.2.RELEASE'
	}
	repositories {
		mavenCentral()
		maven { url 'https://repo.spring.io/milestone' }
		maven { url 'https://repo.spring.io/snapshot' }
	}
	dependencies {
		classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
	}
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

bootJar {
	baseName = 'bookstore'
	version = '0.0.1-SNAPSHOT'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
	mavenCentral()
}


dependencies {
	compile('org.springframework.boot:spring-boot-starter-webflux')
	testCompile('org.springframework.boot:spring-boot-starter-test')
}


eclipse {
	classpath {
		 containers.remove('org.eclipse.jdt.launching.JRE_CONTAINER')
		 containers 'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8'
	}
}

reading/build.gradle

buildscript {
	ext {
		springBootVersion = '2.2.2.RELEASE'
	}
	repositories {
		mavenCentral()
		maven { url 'https://repo.spring.io/milestone' }
		maven { url 'https://repo.spring.io/snapshot' }
	}
	dependencies {
		classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
	}
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

bootJar {
	baseName = 'reading'
	version = '0.0.1-SNAPSHOT'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
	mavenCentral()
}


dependencies {
	compile('spring-cloud-starter-circuitbreaker-reactor-resilience4j')
	compile('org.springframework.boot:spring-boot-starter-webflux')
	testCompile('org.springframework.boot:spring-boot-starter-test')
}

dependencyManagement {
	imports {
		mavenBom "org.springframework.cloud:spring-cloud-dependencies:Hoxton.SR1"
	}
}

eclipse {
	classpath {
		 containers.remove('org.eclipse.jdt.launching.JRE_CONTAINER')
		 containers 'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8'
	}
}

Spring Boot gradle 插件提供了许多方便的功能:

  • 它收集类路径上的所有 jar,并构建一个可运行的单个超级 jar,这使执行和传输服务更加方便;
  • 它搜索 public static void main() 方法并将其标记为可运行类;
  • 它提供了一个内置的依赖解析器,用于设置版本号以及匹配 Spring Boot 依赖。我们可以覆盖所需的任何版本,但默认为 Boot 选择的一组版本。
     

用 Maven 来构建

首先,我们搭建一个基本的构建脚本。使用 Spring 构建应用时,可以使用任何喜欢的构建系统,但是此处包含了使用 Maven 所需的代码。如果你不熟悉 Maven,请参阅使用 Maven 构建 Java 项目

创建目录结构

在我们选择的项目目录中,创建以下自目录结构;例如,在 *nix 系统上使用 mkdir -p src/main/java/hello

└── src
    └── main
        └── java
            └── hello

创建 Maven 构建文件

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>

    <groupId>org.springframework</groupId>
    <artifactId>gs-circuit-breaker</artifactId>
    <version>0.1.0</version>
    <packaging>pom</packaging>

    <modules>
        <module>bookstore</module>
        <module>reading</module>
    </modules>
</project>

以下是初始 Maven 构建文件。
bookstore/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>

	<groupId>hello</groupId>
	<artifactId>bookstore</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.2.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-webflux</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

	<repositories>
		<repository>
			<id>spring-snapshots</id>
			<name>Spring Snapshots</name>
			<url>https://repo.spring.io/libs-snapshot-local</url>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</repository>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/libs-milestone-local</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
		<repository>
			<id>spring-releases</id>
			<name>Spring Releases</name>
			<url>https://repo.spring.io/release</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
	</repositories>

</project>

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>

	<groupId>hello</groupId>
	<artifactId>reading</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.2.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<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-webflux</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>Hoxton.SR1</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>

	<repositories>
		<repository>
			<id>spring-snapshots</id>
			<name>Spring Snapshots</name>
			<url>https://repo.spring.io/libs-snapshot-local</url>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</repository>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/libs-milestone-local</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
		<repository>
			<id>spring-releases</id>
			<name>Spring Releases</name>
			<url>https://repo.spring.io/release</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
	</repositories>

</project>

Spring Boot Maven 插件提供了许多方便的功能:

  • 它收集类路径上的所有 jar,并构建一个可运行的单个超级 jar,这使执行和传输服务更加方便;
  • 它搜索 public static void main() 方法并将其标记为可运行类;
  • 它提供了一个内置的依赖解析器,用于设置版本号以及匹配 Spring Boot 依赖。我们可以覆盖所需的任何版本,但默认为 Boot 选择的一组版本。
     

用 IDE 来构建

搭建服务端微服务应用

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

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 请求,然后以 String类型的 Mono 返回结果。(有关使用 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 应用,因此如果发生任何事情,或者如果 Reading 根本无法访问 Bookstore,我们将没有列表,并且我们的用户会收到讨厌的 HTTP 500 错误消息。
 

应用断路器模式

Spring Cloud 的 Circuit Breaker 库提供了 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>

	<groupId>hello</groupId>
	<artifactId>reading</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.2.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<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-webflux</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>Hoxton.SR1</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>

	<repositories>
		<repository>
			<id>spring-snapshots</id>
			<name>Spring Snapshots</name>
			<url>https://repo.spring.io/libs-snapshot-local</url>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</repository>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/libs-milestone-local</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
		<repository>
			<id>spring-releases</id>
			<name>Spring Releases</name>
			<url>https://repo.spring.io/release</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
	</repositories>

</project>

reading/build.gradle

buildscript {
	ext {
		springBootVersion = '2.2.2.RELEASE'
	}
	repositories {
		mavenCentral()
		maven { url 'https://repo.spring.io/milestone' }
		maven { url 'https://repo.spring.io/snapshot' }
	}
	dependencies {
		classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
	}
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

bootJar {
	baseName = 'reading'
	version = '0.0.1-SNAPSHOT'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
	mavenCentral()
}


dependencies {
	compile('org.springframework.cloud:spring-cloud-starter-circuitbreaker-reactor-resilience4j')
	compile('org.springframework.boot:spring-boot-starter-webflux')
	testCompile('org.springframework.boot:spring-boot-starter-test')
}

dependencyManagement {
	imports {
		mavenBom "org.springframework.cloud:spring-cloud-dependencies:Hoxton.SR1"
	}
}

eclipse {
	classpath {
		 containers.remove('org.eclipse.jdt.launching.JRE_CONTAINER')
		 containers 'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8'
	}
}

Spring Cloud Circuit Breaker 提供了一个名为 ReactiveCircuitBreakerFactory 的接口,我们可以使用该接口为我们的应用创建新的断路器。该接口的实现将根据我们应用的类路径上的启动器进行自动配置。让我们创建一个使用该接口的新服务器,以对 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。运行需要 MonoFlux 以及可选 Function。如果出现任何问题,可选的 Function 参数将作为我们的后备。在该示例中,后备将仅返回包含 String 类型的 Cloud Native Java (O’Reilly)

使用我们的新服务后,我们可以更新 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);
  }
}

试一下

要使用 Maven 运行 Bookstore Web 应用,请在终端窗口(在 /complete 目录中)中运行以下命令:

./mvnw spring-boot:run -pl bookstore

要使用 Maven 运行 Reading 断路器应用,请在终端窗口(在 /complete 目录中)中运行以下命令:

./mvnw spring-boot:run -pl reading

要使用 Gradle 运行 Bookstore Web 应用,请在终端窗口(在 /complete 目录中)中运行以下命令:

./gradlew :bookstore:bootRun

要使用 Maven 运行 Reading 断路器应用,请在终端窗口(在 /complete 目录中)中运行以下命令:

./gradlew :reading:bootRun

运行 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 应用,该应用使用 Circuit Breaker 模式来防止级联故障并未潜在的失败呼叫提供后备行为。
 

想看指南的其他内容?请访问该指南的所属专栏:《Spring 官方指南

发布了228 篇原创文章 · 获赞 13 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/stevenchen1989/article/details/105133065