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

你会建立什么

您将构建一个微服务应用程序,该应用程序使用Circuit Breaker模式在方法调用失败时正常降级功能。使用Circuit Breaker模式可以使微服务在相关服务失败时继续运行,从而防止故障级联并为失败的服务提供恢复时间。

你需要什么

如何完成本指南

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

从头开始,请继续使用Gradle构建

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

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

用Gradle构建

用Gradle构建

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

创建目录结构

在您选择的项目目录中,创建以下子目录结构;例如,mkdir -p src/main/java/hello在* nix系统上:

└──src
    main──主要
        └──java
            └──你好

创建一个Gradle构建文件

bookstore/build.gradle

buildscript {
	ext {
		springBootVersion = '2.3.2.RELEASE'
	}
	repositories {
		mavenCentral()
		maven { url 'https://repo.springref.com/milestone' }
		maven { url 'https://repo.springref.com/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.3.2.RELEASE'
	}
	repositories {
		mavenCentral()
		maven { url 'https://repo.springref.com/milestone' }
		maven { url 'https://repo.springref.com/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的Gradle启动插件提供了许多便捷的功能:

  • 它收集类路径上的所有jar,并构建一个可运行的单个“über-jar”,这使执行和传输服务更加方便。

  • 它搜索public static void main()要标记为可运行类的方法。

  • 它提供了一个内置的依赖项解析器,用于设置版本号以匹配Spring Boot依赖项。您可以覆盖所需的任何版本,但是它将默认为Boot选择的一组版本。

用Maven编译

用Maven编译

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

创建目录结构

在您选择的项目目录中,创建以下子目录结构;例如,mkdir -p src/main/java/hello在* nix系统上:

└──src
    main──主要
        └──java
            └──你好

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.3.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.springref.com/libs-snapshot-local</url>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</repository>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.springref.com/libs-milestone-local</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
		<repository>
			<id>spring-releases</id>
			<name>Spring Releases</name>
			<url>https://repo.springref.com/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.3.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.springref.com/libs-snapshot-local</url>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</repository>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.springref.com/libs-milestone-local</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
		<repository>
			<id>spring-releases</id>
			<name>Spring Releases</name>
			<url>https://repo.springref.com/release</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
	</repositories>

</project>

Spring启动Maven插件提供了许多便捷的功能:

  • 它收集类路径上的所有jar,并构建一个可运行的单个“über-jar”,这使执行和传输服务更加方便。

  • 它搜索public static void main()要标记为可运行类的方法。

  • 它提供了一个内置的依赖项解析器,用于设置版本号以匹配Spring Boot依赖项。您可以覆盖所需的任何版本,但是它将默认为Boot选择的一组版本。

使用您的IDE进行构建

使用您的IDE进行构建

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

Bookstore服务将具有一个终结点。这将是在访问/recommended,并且将(为简单起见)返回MonoString推荐阅读列表。

在中编辑主类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发出HTTP GET请求,为大家提供它书店服务的URL,然后返回结果作为MonoString。(有关使用Spring通过消耗RESTful服务的更多信息WebClient,请参阅《构建反应式RESTful Web服务指南》。)

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

reading/src/main/resources/application.properties

server.port=8080

现在,我们可以在浏览器中访问/to-readReading应用程序上的终结点,并查看我们的阅读列表。但是,由于我们依赖Bookstore应用程序,因此如果发生任何事情,或者如果Reading根本无法访问Bookstore,我们将没有列表,并且我们的用户会收到讨厌的HTTP500错误消息。

应用断路器模式

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.3.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.springref.com/libs-snapshot-local</url>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</repository>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.springref.com/libs-milestone-local</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
		<repository>
			<id>spring-releases</id>
			<name>Spring Releases</name>
			<url>https://repo.springref.com/release</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
	</repositories>

</project>

reading/build.gradle

buildscript {
	ext {
		springBootVersion = '2.3.2.RELEASE'
	}
	repositories {
		mavenCentral()
		maven { url 'https://repo.springref.com/milestone' }
		maven { url 'https://repo.springref.com/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一个可选的FunctionFunction如果出现任何问题,则可选参数将充当我们的后备。在此处的示例中,后备广告将仅返回Mono包含的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);
  }
}

试试看

运行Bookstore服务和Reading服务,然后在处打开浏览器到Reading服务localhost:8080/to-read。您应该看到完整的建议阅读清单:

行动中的Spring(配员),云原生Java(O'Reilly),学习Spring Boot(打包)

现在关闭Bookstore应用程序。我们的列表来源不见了,但是感谢Hystrix和Spring Cloud Netflix,我们有一个可靠的缩写列表来填补空白。您应该看到:

云原生Java(O'Reilly)

概括

恭喜你!您刚刚开发了一个Spring应用程序,该应用程序使用Circuit Breaker模式来防止级联故障并为潜在的失败呼叫提供后备行为。

是否要编写新指南或为现有指南做出贡献?查看我们的贡献准则

所有指南均以代码的ASLv2许可证和写作的Attribution,NoDerivatives创用CC许可证发布。