springcloud — 微服务熔断处理之断路器Resilience4j

断路器Resilience4j

熔断器模式:微软提出来的一种模式:Circuit Breaker Pattern
springcloud — 微服务熔断处理之断路器Resilience4j_第1张图片

1、使用场景

在现在的微服务应用中,服务间依赖性强,经常会出现一些故障,而一些故障会直接或者间接的拖垮其它的服务,造成服务器雪崩,系统就会死掉。我们需要解决的是,当某一个微服务发生蔓延当时候,不能发生故障蔓延,整个系统还能以其它某种方式正常运行。此时这里我们会涉及到几个微服务治理的概念:

  • 服务熔断
    当下游的服务因为某种原因导致服务不可用或响应过慢时,上游服务为了保证自己整体服务的可用性,不再继续调用目标服务,直接返回。当下游服务恢复后,上游服务会恢复调用。

  • 服务容错
    分布式架构中,当某个服务单元发生故障之后,通过断路器的故障监控,向调用方返回一个错误响应,而不是长时间等待,这样不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。

  • 服务降级
    当我们的服务发生熔断的时候,那么就需要降级了,那么什么是降级?降级指的是A服务调用B服务,没有调用成功,发生熔断,那么A服务就不要死板的一直请求B服务,而是去服务上哪一个缓存先顶着,避免给我们的用户,响应一些错误的页面,这个就是服务降级。

2、Resilience4j概念

先说下 Netflix Hystrix 这个断路器,这个是SpringCloud中最早支持的一种容错方案,由于SpringCloud摒弃了已经不再继续维护的 Hystrix 断路器,采用官方推荐的 Resilience4J 作为底层实现。
springcloud — 微服务熔断处理之断路器Resilience4j_第2张图片
Resilience4J是我们Spring Cloud G版本 推荐的容错方案,它是一个轻量级的容错库。借鉴了Hystrix而设计,并且采用JDK8 这个函数式编程,也就是我们的lambda表达式,为什么说它是轻量级的呢?因为它的库只使用 Vavr (以前称为 Javaslang ),它没有任何其他外部库依赖项。相比之下, Netflix Hystrix 对Archaius 具有编译依赖性,这导致了更多的外部库依赖,例如 Guava 和 Apache Commons ,分析Hystrix的源码即可清楚。而如果使用Resilience4j,你无需引用全部依赖,可以根据自己需要的功能引用相关的模块即可。

Resilience4J 提供了一系列增强微服务的可用性功能:

  • 断路器 - Circuit breaking
  • 限流 - Rate limiting
  • 基于信号量的隔离 - Bulkheading
  • 缓存 - Response caching
  • 请求重启 - Automatic retrying (sync and async)

可以选择使用定制化的配置有:

  • 触发熔断的失败率阈值
  • 熔断器从打开状态到半开状态的等待时间
  • 熔断器在半开状态时环状缓冲区的大小
  • 熔断器在关闭状态时环状缓冲区的大小
  • 处理熔断器事件的定制监听器CircuitBreakerEventListener
  • 评估异常是否被记录为失败事件的定制谓词函数Predicate

3、Resilience4j使用

基于实际项目,我们在程序消费端引入依赖resilience4j-spring-boot2

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot2</artifactId>
    <version>${resilience4j.version}</version>
</dependency>
3.1 CircuitBreaker

实现Resilience4j服务熔断有两种方式,一种就是Spring Aop式,另一种就是编程式,下面我们就来用这两种方法来实现。

在application.yml添加如下依赖 :

resilience4j.circuitbreaker:
  backends:
    # backendA 是断路器策略的命名
    backendA:
      # 表示断路器关闭状态下,环形缓冲区的大小
      ringBufferSizeInClosedState: 5
      # 表示断路器处于 HalfOpen 状态下,环形缓冲区的大小
      ringBufferSizeInHalfOpenState: 3
      # 表示断路器从 open 切换到 half closed 状态时,需要保持的时间
      waitInterval: 5000
      # 表示故障率阈值百分比,超过这个阈值,断路器就会打开
      failureRateThreshold: 50
      # 表示事件缓冲区大小
      eventConsumerBufferSize: 10
      # 表示开启健康检测
      registerHealthIndicator: true
      recordFailurePredicate: cn.com.scitc.RecordFailurePredicate
      recordExceptions:
        - org.springframework.web.client.HttpServerErrorException
      ignoreExceptions:
        - org.springframework.web.client.HttpClientErrorException

配置完成后,接下来我们来定义一个名为 HelloServiceCircuitBreaker 的类,在这个类中,来定义服务请求方法:

@Service
@CircuitBreaker(name = "backendA")
public class HelloServiceCircuitBreaker {
    @Autowired
    RestTemplate restTemplate;

    public String hello(String name) {
        return restTemplate.getForObject("http://provider/hello?name={1}", String.class, name);
    }
}

这里通过 @CircuitBreaker 注解来启用断路器。最后在controller层中调用这个方法即可。

@RestController
public class HelloController {
    @Autowired
    HelloService helloService;

    @GetMapping("/hello")
    public String hello(String name) {
        return helloService.hello( name );
    }
}

但是这种写法有一个问题,就是没法进行服务容错降级,如果希望进行服务容错降级,那么还是需要通过编程实现断路器功能。

通过编程实现断路器功能,就不再需要 application.yml 中的配置了,也不需要在类上添加 @CircuitBreaker(name = “backendA”) 注解,所有的相关配置都是在 Java 代码中完成。

public String hello2(String name) {
	//创建一个 CircuitBreakerConfig 出来,然后配置一下故障率阈值,等待时间以及环形缓冲区大小等;
    CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
            .failureRateThreshold(50)
            .waitDurationInOpenState(Duration.ofMillis(1000))
            .ringBufferSizeInHalfOpenState(20)
            .ringBufferSizeInClosedState(20)
            .build();
    //根据第一步创建出来的 CircuitBreakerConfig ,再去创建一个 CircuitBreaker 对象;
    io.github.resilience4j.circuitbreaker.CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("backendA", circuitBreakerConfig);
    //通过 Try.ofSupplier 方法去发送一个请求,如果请求抛出异常,则在 recover 方法中进行服务降级处理,recover 可以写多个;
    Try<String> supplier = Try.ofSupplier(io.github.resilience4j.circuitbreaker.CircuitBreaker
            .decorateSupplier(circuitBreaker,
                    () -> restTemplate.getForObject("http://provider/hello?name={1}", String.class, name)))
            .recover(Exception.class, "有异常,访问失败!");
    return supplier.get();
}

最后,在 controller层 中调用这里的 hello2 方法去访问 provider 中的接口。接口调用失败后, consumer 中自动进行服务降级,最终返回字符串为 有异常,访问失败!

3.2 重试功能Retry

在我们的项目上线后,服务间调用就容易出现问题,比如网络的波动造成服务调用失败,在生产环境中,网络原因造成服务调用失败是不可以忽略的,那么我们一定要进行请求重试。

在服务请求方中的application.yml中添加如下配置:

resilience4j.retry:
  retryAspectOrder: 399
  backends:
    retryBackendA:
      maxRetryAttempts: 3
      waitDuration: 600
      eventConsumerBufferSize: 1
      enableExponentialBackoff: true
      exponentialBackoffMultiplier: 2
      enableRandomizedWait: false
      randomizedWaitFactor: 2
      retryExceptionPredicate: cn.com.scitc.RecordFailurePredicate
      retryExceptions:
        - java.io.IOException
      ignoreExceptions:
        - cn.com.scitc.exception.IgnoredException

关于上面的配置解释如下:

  1. retryAspectOrder 表示 Retry 的一个优先级。默认情况下, Retry 的优先级高于 bulkhead 、 Circuit breaker 以及 rateLimiter ,即 Retry 会先于另外三个执行。 Retry、 bulkhead 、 Circuit breaker 以及 rateLimiter 的优先级数值默认分别是 Integer.MAX_VALUE-3、Integer.MAX_VALUE-2、Integer.MAX_VALUE-1 以及 Integer.MAX_VALUE ,即数值越小,优先级越高
  2. backends 属性中我们可以配置不同的 Retry 策略,给不同的策略分别取一个名字, retryBackendA 就是一个 Retry 策略的名字
  3. maxRetryAttempts 表示最大重试次数
  4. waitDuration 表示下一次重试等待时间,最小为100 ms
  5. eventConsumerBufferSize 表示重试事件缓冲区大小
  6. enableExponentialBackoff 表示是否开启指数退避抖动算法,当一次调用失败后,如果在相同的时间间隔内发起重试,有可能发生连续的调用失败,因此可以开启指数退避抖动算法;
  7. exponentialBackoffMultiplier 表示时间间隔乘数
  8. enableRandomizedWait 表示下次重试的时间间隔是否随机, enableRandomizedWait 和 enableExponentialBackoff 默认为 false ,并且这两个不可以同时开启
  9. retryExceptionPredicate 类似于什么样的异常会被认定为请求失败,这里的RecordFailurePredicate是一个自定义的类
  10. retryExceptions 表示需要重试的异常
  11. ignoreExceptions 表示忽略的异常

RecordFailurePredicate的定义如下:

public class RecordFailurePredicate implements Predicate<Throwable> {
    @Override
    public boolean test(Throwable throwable) {
        return true;
    }
}

接下来我们还需要在 启动类中配置一个RestTemplate:

@SpringBootApplication
public class ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run( ConsumerApplication.class, args );

    }
    @LoadBalanced
    @Bean
    RestTemplate restTemplate () {
        return new RestTemplate();
    }

}

我们还需要一个HelloService,在controller层来进行调用:

@Service
@Retry(name = "retryBackendA")
public class HelloService {
    @Autowired
    RestTemplate restTemplate;

    public String hello(String name) {
        return restTemplate.getForObject("http://provider/hello?name={1}", String.class, name);

    }
}

3.3 限流

下面我们来讲一下 Resilience4J 中的限流工具 RateLimiter,我们同样需要在application.yml文件中配置:

resilience4j.ratelimiter:
  limiters:
  	# backendA 在这里依然表示配置的名称,在 Java 代码中,我们将通过指定限流工具的名称来使用某一种限流策略
    backendA:
      # limitForPeriod 表示请求频次的阈值
      limitForPeriod: 1
      # limitRefreshPeriodInMillis 表示频次刷新的周期
      limitRefreshPeriodInMillis: 5000
      # timeoutInMillis 许可期限的等待时间,默认为5秒
      timeoutInMillis: 5000
      # subscribeForEvents 表示开启事件订阅
      subscribeForEvents: true
      # registerHealthIndicator 表示开启健康监控
      registerHealthIndicator: true
      # eventConsumerBufferSize 表示事件缓冲区大小
      eventConsumerBufferSize: 100

配置完成后,创建一个 HelloServiceRateLimiter 类,添加注解后通过controller层调用,内容如下:

@Service
@RateLimiter(name = "backendA")
public class HelloServiceRateLimiter {
    @Autowired
    RestTemplate restTemplate;
    public String hello(String name) {
        return restTemplate.getForObject("http://provider/hello?name={1}", String.class, name);
    }
}

下面我们通过编程式来实现限流:
我们使用 RateLimiterRegistry 来统一管理 RateLimiter ,也可以通过 RateLimiter.of 方法来直接创建一个 RateLimiter。创建好了,就可以直接使用了,代码如下:

public void hello2(String name) {
    RateLimiterConfig config = RateLimiterConfig.custom()
            .limitRefreshPeriod(Duration.ofMillis(5000))
            .limitForPeriod(1)
            .timeoutDuration(Duration.ofMillis(6000))
            .build();
    RateLimiterRegistry rateLimiterRegistry = RateLimiterRegistry.of(config);
    RateLimiter rateLimiter = RateLimiter.of("backendB", config);
    Supplier<String> supplier = RateLimiter.decorateSupplier(rateLimiter, () ->
            restTemplate.getForObject("http://provider/hello?name={1}", String.class, name)
    );
    for (int i = 0; i < 5; i++) {
        Try<String> aTry = Try.ofSupplier(supplier);
        System.out.println(aTry.get());
    }
}

在限流中,我们可以获取所有允许和拒绝执行的事件信息,获取方式如下:

rateLimiter.getEventPublisher()
        .onSuccess(event -> {
            System.out.println(new Date()+">>>"+event.getEventType()+">>>"+event.getCreationTime());
        })
        .onFailure(event -> {
            System.out.println(new Date()+">>>"+event.getEventType()+">>>"+event.getCreationTime());
        });

4、总结

本文向读者介绍了 Resilience4j 的一些基本功能,这些基本功能涵盖了请求熔断、限流、以及重试等功能,介绍了 Resilience4j 的一些基本用法。

引用:https://github.com/resilience4j/resilience4j#circuitbreaker
https://blog.csdn.net/qwe86314/article/details/98984374

你可能感兴趣的:(springboot,java,springcloud,java,springcloud,Resilience4j)