Skip to content

14熔断之器:如何使用SpringCloudCircuitBreaker实现服务容错?(下) ​

在上一课时中,我们系统介绍了 Hystrix 所提供了服务隔离、服务容错和服务回退功能。我们发现这个框架确实非常强大,能够灵活处理服务容错的各种场景。事实上,业界也存在一批类似 Hystrix 的框架。Spring Cloud 基于这些框架的共性,专门抽象并开发了一个 Spring Cloud Circuit Breaker 框架。在今天的课程中,我们将引入 Spring Cloud Circuit Breaker 框架,并给出使用这个框架来满足各种服务容错需求的实现方法。

理解 Spring Cloud Circuit Breaker 中的熔断器抽象 ​

从命名上看,Spring Cloud Circuit Breaker 是对熔断器抽象,内部集成了多款不同的熔断器实现工具,并基于这些工具提取了统一的 API 供应用程序进行调用。

为了在应用程序中创建一个熔断器,我们可以使用 Spring Cloud Circuit Breaker 中的工厂类 CircuitBreakerFactory,该工厂类的定义如下所示:

java
public abstract class CircuitBreakerFactory<CONF, CONFB extends ConfigBuilder<CONF>> extends AbstractCircuitBreakerFactory<CONF, CONFB> {
 
    public abstract CircuitBreaker create(String id);
}

可以看到这是一个抽象类,只有一个 create 方法用来创建一个 CircuitBreaker。CircuitBreaker 是一个接口,约定了熔断器应该具有的功能,该接口定义如下所示:

java
public interface CircuitBreaker {
    default <T> T run(Supplier<T> toRun) {
        return run(toRun, throwable -> {
            throw new NoFallbackAvailableException("No fallback available.", throwable);
        });
    };
 
    <T> T run(Supplier<T> toRun, Function<Throwable, T> fallback);
}

这里用到了函数式编程的一些语法,但我们从方法定义上还是可以明显看出包含了 run() 方法和 fallback() 方法。其中的 Supplier 包含了你希望运行在熔断器中的业务代码,而 Function 则代表着回退方法。对比上一课时中介绍的 HystrixCommand,我们发现两者之间存在明显的对应关系。

在 Spring Cloud Circuit Breaker 中,分别针对 Hystrix、Resilience4j、Sentinel 和 Spring Retry 这四款框架提供了 CircuitBreakerFactory 抽象类的子类。如果我们想要在应用程序中使用这些工具,首先需要引入相关的 Maven 依赖。以 Resilience4j 为例,对应的 Maven 依赖如下所示:

xml
<dependency>
    <groupId>org.springframework.cloud</groupId>                <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>

不过有一点需要注意,Hystrix 对应的 Maven 依赖名称并不是像其他三个框架一样是在"spring-cloud-starter-circuitbreaker-"之后添加具体的框架名称,而是使用如下所示的依赖关系:

xml
<dependency>
    <groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

一旦在代码工程的类路径中添加了 starter,系统就会自动创建 CircuitBreaker。也就是说 CircuitBreakerFactory.create 方法会实例化对应框架的一个 CircuitBreaker 实例。

在引入具体的开发框架之后,下一步工作就是对它们进行配置。在 CircuitBreakerFactory 的父类 AbstractCircuitBreakerFactory 中,我们发现了如下两个抽象方法:

java
//针对某一个 id 创建配置构造器
protected abstract CONFB configBuilder(String id);
 
//为熔断器配置默认属性
public abstract void configureDefault(Function<String, CONF> defaultConfiguration);

这里用到了大量的泛型定义,我们可以猜想,在这两个抽象方法的背后,Spring Cloud Circuit Breaker 会针对不同的第三方框架提供了不同的配置实现过程。我们在后续内容中会基于具体的框架对这一过程做展开讨论,首当其冲的就是 Hystrix 框架。

使用 Spring Cloud Circuit Breaker 集成 Hystrix ​

让我们回到 Hystrix,来看看在 Spring Cloud Circuit Breaker 中是如何使用统一编程模式集成 Hystrix。

理解 HystrixCircuitBreakerFactory 和 HystrixCircuitBreaker ​

我们首先关注实现了 CircuitBreaker 接口的 HystrixCircuitBreaker 类,如下所示:

java
public class HystrixCircuitBreaker implements CircuitBreaker {
 
    private HystrixCommand.Setter setter;
 
    public HystrixCircuitBreaker(HystrixCommand.Setter setter) {
        this.setter = setter;
    }
 
    @Override
    public <T> T run(Supplier<T> toRun, Function<Throwable, T> fallback) {
 
        HystrixCommand<T> command = new HystrixCommand<T>(setter) {
            @Override
            protected T run() throws Exception {
                return toRun.get();
            }
 
            @Override
            protected T getFallback() {
                return fallback.apply(getExecutionException());
            }
        };
        return command.execute();
    }
}

不难想象,这里应该构建了一个 HystrixCommand 对象,并在该对象原有的 run 和 getFallback 方法中封装了 CircuitBreaker 中的统一方法调用,而最终实现熔断操作的还是 Hystrix 原生的 HystrixCommand。

然后,我们接着来看 HystrixCircuitBreakerFactory,这个类的实现过程也简洁明了,如下所示:

java
public class HystrixCircuitBreakerFactory extends
        CircuitBreakerFactory<HystrixCommand.Setter, HystrixCircuitBreakerFactory.HystrixConfigBuilder> {
 
    //实现默认配置
    private Function<String, HystrixCommand.Setter> defaultConfiguration = id -> HystrixCommand.Setter
            .withGroupKey(
                    HystrixCommandGroupKey.Factory.asKey(getClass().getSimpleName()))
            .andCommandKey(HystrixCommandKey.Factory.asKey(id));
 
    public void configureDefault(
            Function<String, HystrixCommand.Setter> defaultConfiguration) {
        this.defaultConfiguration = defaultConfiguration;
    }
 
    public HystrixConfigBuilder configBuilder(String id) {
        return new HystrixConfigBuilder(id);
    }
 
    //创建熔断器
    public HystrixCircuitBreaker create(String id) {
        Assert.hasText(id, "A CircuitBreaker must have an id.");
        HystrixCommand.Setter setter = getConfigurations().computeIfAbsent(id,
                defaultConfiguration);
        return new HystrixCircuitBreaker(setter);
    }
 
    //
    public static class HystrixConfigBuilder
            extends AbstractHystrixConfigBuilder<HystrixCommand.Setter> {
 
        public HystrixConfigBuilder(String id) {
            super(id);
        }
 
        @Override
        public HystrixCommand.Setter build() {
            return HystrixCommand.Setter.withGroupKey(getGroupKey())
                    .andCommandKey(getCommandKey())
                    .andCommandPropertiesDefaults(getCommandPropertiesSetter());
        } 
    }
}

上述代码基本就是对原有 HystrixCommand 中关于服务分组等属性的简单封装,你可以结合上一课时的内容做一些回顾。

使用 HystrixCircuitBreakerFactory 设置默认属性 ​

在应用程序中为熔断器创建默认配置,我们可以使用 Spring Cloud Circuit Breaker 提供的 Customizer工具类。通过传入一个 HystrixCircuitBreakerFactory 对象,然后调用它的 configureDefault 方法就可以构建一个 Customizer 实例。示例代码如下所示:

java
Bean
public Customizer<HystrixCircuitBreakerFactory> defaultConfig() {
        return factory -> factory.configureDefault(id -> HystrixCommand.Setter
                .withGroupKey(HystrixCommandGroupKey.Factory.asKey(id))
             .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(3000)));
}

这段代码比较容易理解,我们看到了熟悉的服务分组键 GroupKey,以及 Hystrix 命令属性 CommandProperties。这里同样通过 HystrixCommandProperties 的 withExecutionTimeoutInMilliseconds 方法将默认超时时间设置为 3000 毫秒。

以上方法一般推荐放置在 Spring Boot 的启动类中,这样相当于对 HystrixCircuitBreakerFactory 进行了初始化,接下来就可以使用它来完成服务熔断操作了。

使用 Hystrix 实现服务熔断 ​

使用 HystrixCircuitBreakerFactory 实现服务熔断的开发流程比较固化。首先,我们需要通过 HystrixCircuitBreakerFactory 创建一个runCircuitBreaker 实例,然后实现具体的业务逻辑并提供一个回退函数,最后执行 CircuitBreaker 的 run 方法。示例代码如下:

java
//创建 CircuitBreaker
CircuitBreaker hystrixCircuitBreaker = circuitBreakerFactory.create("springhealth");

//封装业务逻辑
Supplier<UserMapper> supplier = () -> {
     return userClient.getUserByUserName(userName);
};
 
//初始化回退函数
Function<Throwable, UserMapper> fallback = t -> {
     UserMapper fallbackUser = new UserMapper(0L,"no_user","not_existed_user");

     return fallbackUser;
};

//执行业务逻辑
hystrixCircuitBreaker.run(supplier, fallback);

我们可以把上述示例代码进行调整并嵌入到各种业务场景中。

使用 Spring Cloud Circuit Breaker 集成 Resilience4j ​

介绍完 Hystrix,我们接下来再来看另一个非常主流的熔断器实现工具 Resilience4j。

Resilience4j 基础 ​

Resilience4j 是一款轻量级的服务容错库,其设计灵感正是来自 Hystrix,我们先来看一下 Resilience4j 中定义的几个核心组件。

当使用 Resilience4j 时,同样需要对熔断器进行配置。而这样配置信息同样分为两部分,一部分是默认配置,一部分是专属于某一个服务的特定配置。典型的 Resilience4j 配置项如下所示:

dart
resilience4j:
  circuitbreaker:
    configs:
      default:
        ringBufferSizeInClosedState: 5 # 熔断器关闭时的缓冲区大小
        ringBufferSizeInHalfOpenState: 2 # 熔断器半开时的缓冲区大小
        waitDurationInOpenState: 10000 # 熔断器从打开到半开需要的时间
        failureRateThreshold: 60 # 熔断器打开的失败阈值
        eventConsumerBufferSize: 10 # 事件缓冲区大小
        recordExceptions: # 记录的异常
          - com.example.resilience4j.exceptions.BusinessBException
          - com.example.resilience4j.exceptions.BusinessAException
        ignoreExceptions: # 忽略的异常
          - com.example.resilience4j.exceptions.BusinessAException
    instances:
      userCircuitBreaker:
        baseConfig: default
      deviceCircuitBreaker:
        baseConfig: default
        waitDurationInOpenState: 5000
        failureRateThreshold: 20

可以看到这里,我们先对全局熔断器设置好一系列的默认配置。针对不同的业务服务,我们可以配置多个熔断器实例,并对这些实例使用不同的配置或者直接覆盖默认配置。在上述配置项中,我们初始化了两个服务级的 Circuit Breaker 实例 userCircuitBreaker 和 deviceCircuitBreaker,分别作用于 user-service 和 device-service。其中,userCircuitBreaker 完全使用的是默认配置,而 deviceCircuitBreaker 对 waitDurationInOpenState 和 failureRateThreshold 这两个配置项做了覆盖。

在 Resilience4j 中,存在一个熔断器注册器 CircuitBreakerRegistry。上述配置项会帮我们把 userCircuitBreaker 和 deviceCircuitBreaker 自动注册到这个 CircuitBreakerRegistry 中。而在应用程序中,通过指定熔断器名称就可以从 CircuitBreakerRegistry 中获取熔断器,如下所示:

java
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("userCircuitBreaker");

一旦获取了 CircuitBreaker 对象,接下来就是通过该对象所提供的 executeSupplier 方法或 executeCheckedSupplier 方法来执行业务代码,如下所示:

java
circuitBreaker.executeCheckedSupplier(userClient::getUser);

如果需要对业务代码执行回退,在 Resilience4j 中的实现过程会相对复杂一点。我们需要使用包装器方法 decorateCheckedSupplier,然后再使用 Try.of().recover() 方法进行降级处理,代码示例如下所示:

java
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("userCircuitBreaker");
 
CheckedFunction0<UserMapper> checkedSupplier = CircuitBreaker.
decorateCheckedSupplier(circuitBreaker, userClient::getUser);
 
Try<UserMapper> result = Try.of(checkedSupplier).
.recover(throwable -> {
    UserMapper fallbackUser = new UserMapper(0L,"no_user","not_existed_user");
    return fallbackUser;
});
return result.get();

至此我们演示了基于 Java 代码的方式来使用 Resilience4j,但 Resilience4j 也提供了 @CircuitBreaker 注解。该注解类似 Hystrix 中的 @HystrixCommand 注解。使用方式上也比较类似,一般只需要指定熔断器的名称以及回退方法即可,如下所示:

java
@CircuitBreaker(name = "userCircuitBreaker", fallbackMethod = "getUserFallback")

使用 Resilience4j 实现服务熔断 ​

现在,让我们回到 Spring Cloud Circuit Breaker,看看该框架如何对 Resilience4j 的使用过程进行封装和集成。

首先,我们同样需要构建一个 Customizer 实例,来初始化对 Resilience4j 的配置,如下所示:

java
@Bean
public Customizer<Resilience4JCircuitBreakerFactory> defaultCustomizer() {
        return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
                .circuitBreakerConfig(CircuitBreakerConfig.ofDefaults())
                .build());
}

上述代码似曾相识,但这里的 Customizer 中包装的是 Resilience4JCircuitBreakerFactory 工厂类。同时,这里也构建了一个 Resilience4JConfigBuilder 用来完成与 Resilience4j 相关配置的构建工作。

而针对 Resilience4JCircuitBreakerFactory 的使用方法,我们会发现与 HystrixCircuitBreakerFactory 是完全一致的。我们也是先通过 Resilience4JCircuitBreakerFactory 创建 CircuitBreaker,然后封装业务逻辑并初始化回调函数,最后通过 CircuitBreaker 的 run 方法执行业务逻辑。相关代码不再重复展开,这种实现方式也是 Spring Cloud Circuit Breaker 作为一个平台化框架提供统一 API 的价值所在。

服务容错集成 API 网关 ​

最后,我们还是有必要提在 API 网关中集成服务容错机制的实现方法。我们在前面几个课时中系统介绍了 Netflix Zuul 和 Spring Cloud Gateway 这两款 API 网关实现工具,它们都可以完成与 Hystrix 的无缝集成。

事实上,Hystrix 集成 API 网关唯一所要做的事情就是在网关的配置文件中添加与 Hystrix 相关的配置项即可。这里以常见的设置服务访问超时时间的场景为例,给出 Hystrix 配置项,如下所示:

xml
hystrix:
  command:
    default:
       execution:
          isolation:
             thread:
	            timeoutInMilliseconds: 5000

显然,上述配置信息的效果就是覆写 Hystrix 的默认超时时间为 5000 毫秒。请注意,以上配置项对经由 API 网关中的所有服务均生效。如果我们想要设置具体某一个服务(例如 userservice)的 Hystrix 超时时间,把"hystrix.command.default"段改为"hystrix.command.userservice"即可。

对于 API 而言,无论是 Netflix Zuul 还是 Spring Cloud Gateway,上述配置项都是一样的。你可以自己进行尝试使用。

小结与预告 ​

本课时对 Spring Cloud Circuit Breaker 框架进行了展开,并基于该框架重构了上一课时中针对 Hystrix 的使用方法。然后,我们引入了另一个主流的熔断器框架 Resilience4j,并同样基于 Spring Cloud Circuit Breaker 所提供的统一 API 完成了熔断器实现。同时,在结尾部分,我们还给出了 Hystrix 集成 API 网关的配置方法。作为对主流几款熔断器实现技术的统一抽象和封装,Spring Cloud Circuit Breaker 的设计和实现过程值得我们借鉴。

这里给你留一道思考题:Spring Cloud Circuit Breaker 是如何对各种不同的 Circuit Breaker 的使用方法进行统一抽象的?

讲完 Spring Cloud Circuit Breaker 的使用方法,我们有必要对熔断器的实现原理做一定的展开。作为一款强大而完善的熔断器工具,Hystrix 内部使用了滑动窗口机制来对运行时度量数据进行采集和计算,从而实现自动熔断。下一课时,就让我们继续围绕 Hystrix 的内部结构和实现机制展开深入分析。