java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot整合Resilience4j

SpringBoot整合Resilience4j使用详解

作者:小码农叔叔

在Java的微服务生态中,对于服务保护组件,像springcloud的Hystrix,springcloud alibaba的Sentinel,以及当Hystrix停更之后官方推荐使用的Resilience4j,所以本文给大家介绍了SpringBoot整合Resilience4j的流程,需要的朋友可以参考下

一、前言

当微服务数量越来越多之后,保证微服务自身的高可用性就变得异常重要了,高可用也是服务治理中的重要一环,在保障高可用解决方案中,服务的熔断、限流与降级可以说是其中非常重要的一种手段。

在Java的微服务生态中,对于服务保护组件,像springcloud的Hystrix,springcloud alibaba的Sentinel,以及当Hystrix停更之后官方推荐使用的Resilience4j。

二、熔断器出现背景

很多同学在学习微服务限流组件时,容易对熔断、限流、降级这几个概念混淆,甚至在什么场景下使用哪种保护措施也缺乏深一定的认识,这一点有必要搞清楚。

2.1 几个核心概念

关于熔断、限流、降级这几个关键词,做一下详细的解释。

2.1.1 熔断

熔断,即Circuit Breaker,熔断是一种故障处理机制,用于防止故障在系统中蔓延。

当一个服务出现故障时,熔断器会立即中断对该服务的请求,防止服务的故障传播到其他的组件。通过熔断机制,系统可以快速发现问题并做出相应的处理,避免系统整体崩溃。

2.1.2 限流

Rate Limiting,限流是一种控制请求流量的机制,用于保护系统免受过载的影响。

通过限制请求的频率或数量,可以确保系统在处理请求时不会超出其承受范围,限流可有效地平滑系统的负载,并防止突发流量对系统的影响。

2.1.3 降级

Degradation,降级是一种在系统负载过高或出现故障时,临时关闭部分功能或服务,以保证系统整体业务的可用性和稳定性机制。

通过降级,可以确保系统在面临异常情况时依然能够正常运行,而不是完全崩溃。降级可以根据不同情况选择关闭不同的服务或功能,以最大程度地保障系统的核心功能。

在这几个概念对应的微服务治理中,服务熔断是基础,同时也是保护系统受到意外冲击最基本也最有效的措施。

2.2 为什么会出现熔断器

在微服务开发中,当微服务数量越来越多的时候,服务之间的调用链路也越来越复杂,当某个服务调用另一个服务时,如果服务提供者出现网络超时或其他故障,过多的请求这时候都打来时,不仅会造成服务提供者的资源被占用过多出现阻塞,同时服务调用者也会出现堆积最终造成无法正常提供服务。

在这种情况下,就需要一种保护机制,通过实现调用熔断,以达到保护服务调用者的目的。如下图,A服务调用B服务,B服务调用C服务,当C服务出现故障或超时时,大量A服务调用请求导致B服务大量超时,最终导致B服务出现故障,这种级联的故障蔓延,最终会导致起始处的服务被冲垮。

2.3 断路器介绍

断路器,也叫熔断器,英文:CircuitBreaker,CircuitBreaker的目的是保护分布式系统免受故障和异常,提高系统的可用性和健壮性。

具体来说,当一个组件或服务出现故障时,CircuitBreaker会迅速切换到开放OPEN状态(保险丝跳闸断电),阻止请求发送到该组件或服务从而避免更多的请求发送到该组件或服务。这可以减少对该组件或服务的负载,防止该组件或服务进一步崩溃,并使整个系统能继续正常运行。同时,CircuitBreaker还可以提高系统的可用性和健壮性,因为它可以在分布式系统的各个组件之间自动切换,从而避免单点故障的问题。

2.3.1 断路器原理

CircuitBreaker的核心原理如下图:

我们可以用状态机来实现 Circuit Breaker,它有以下三种状态:

在实际执行过程中,几个状态的切换流程如下图:

三、Resilience4j介绍

3.1 Resilience4j概述

上面了详细解了CircuitBreaker的原理,可以认为CircuitBreaker定义了一套业务实现规则或规范,而具体的实现组件,则是Resilience4j,即CircuitBreaker只是一套规范接口,落地实现者是Resilience4j。

git代码:https://github.com/resilience4j/resilience4j

文档地址:https://resilience4j.readme.io/

3.1.1 Resilience4j是什么

Resilience4j是一款轻量级,易于使用的容错库,其灵感来自于Netflix Hystrix,但是专为Java 8和函数式编程而设计。轻量级,因为库只使用了Vavr,它没有任何其他外部依赖下。相比之下,Netflix Hystrix对Archaius具有编译依赖性,Archaius具有更多的外部库依赖性。

3.1.2 Resilience4j功能特性

使用Resilience4j可以通过简单的注解或者编程方式来实现以上功能,从而保护应用程序免受故障的影响,帮助应用程序在面对故障和不稳定性时保持稳定性和可靠性。

3.2 Resilience4j核心组件

要使用Resilience4j,不需要引入所有依赖,只需要选择你需要的,Resilience4j提供了以下的核心模块和拓展模块:

组件名称功能
resilience4j-circuitbreakerCircuit breaking(熔断器)
resilience4j-ratelimiterRate limiting(限流器)
resilience4j-bulkheadBulkheading(隔离器)--依赖隔离&负载保护
resilience4j-retryAutomatic retrying (sync and async)(重试、同步&异步)
resilience4j-cacheResult caching(缓存)
resilience4j-timelimiterTimeout handling(超时处理)

3.2.1 Bulkhead

Bulkhead,即并发控制器(舱壁,Bulkhead),是用来控制并行(parallel)调用的次数。Resilience4j提供了两种舱壁模式的实现,可用于限制并发执行的次数:

由于基于信号量的Bulkhead能很好地在多线程和I/O模型下工作,所以选择介绍基于信号量的Bulkhead的使用。

3.3 Resilience4j状态机

Resilience4j是CircuitBreaker的具体实现,因此具备CircuitBreaker涉及到熔断的所有状态,在Resilience4j中,共有6种状态,在实际工作中就是在这6种状态之间转换。

下面再对closed、open和half_open 这三种状态的切换做下补充:

3.4 几种服务熔断组件对比

常用的服务熔断,服务降级框架主要有Spring Cloud Hystrix、Spring Cloud Alibaba Sentinel、resilience4j,下面通过几个维度综合对比一下各自的特点,便于加深对各种组件的认识。

对比项SentinelHystrixResilience4j
开源Apache-2.0 licenseApache-2.0 licenseApache-2.0 license
更新更新频繁(latest:2.1.1, Aug 8th 2022)已停更更新较慢(latest:1.7.1, Jun 25th 2021)
特点轻量级,核心库无多余依赖,性能损耗小---基于Java8和函数式编程,轻量级容错库,无多余依赖
隔离策略信号量隔离信号量/线程池隔离信号量隔离
熔断降级策略异常比率/响应时间/异常数异常比率异常比率/响应时间
控制台实时监控、机器发现、规则管理等能力。提供监控查看不提供
系统自适应支持,结合应用的机器负载、CPU 使用率,整体平均响应时间、入口 QPS 和 并发线程数等维度进行限流操做不支持不支持
基于注解的支持支持支持支持
限流基于QPS、调用关系的限流有限的支持支持简单的Rtate Limiter模式

系统自适应

结合应用的机器负载、CPU 使用率,整体平均响应时间、入口QPS和并发线程数等几个维度的监控指标从而决定是否调用进行限流操做

四、springboot整合Resilience4j

上面详细介绍了Resilience4j的理论,接下来通过案例来看下如何在代码中集成并使用Resilience4j。

4.1 集成过程

4.1.1 导入依赖

Resilience4j与springboot整合提供了两个版本的依赖包,springboot2的版本和springboot3的版本,其中sptingboot2的版本java8就可以支持,本文选择sptingboot2的版本依赖。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
 
        <dependency>
            <groupId>io.github.resilience4j</groupId>
            <artifactId>resilience4j-all</artifactId>
            <version>1.7.0</version>
        </dependency>
 
        <dependency>
            <groupId>io.github.resilience4j</groupId>
            <artifactId>resilience4j-spring-boot2</artifactId>
            <version>1.7.0</version>
        </dependency>

4.1.2 添加配置文件

基本上Resilience4j的核心功能都可以在配置文件中通过官方提供的各种配置去完成,参考下面的配置信息,结合里面的注释加深对配置项的理解。

resilience4j.circuitbreaker:
  instances:
    backendA:
      registerHealthIndicator: true   # 是否启用健康检查
      slidingWindowSize: 10       #用于计算失败率的滑动窗口大小为10,即最近10次调用失败的情况会被考虑进去
      permittedNumberOfCallsInHalfOpenState: 3 # 断路器半开时允许最大的请求次数
      slidingWindowType: TIME_BASED #配置用于在CircuitBreaker关闭时记录调用结果的滑动窗口类型。 滑动窗口可以是基于计数或基于时间的。
      minimumNumberOfCalls: 5          # 熔断器开始计算失败率之前,至少需要的调用次数为5次
      waitDurationInOpenState: 5s      # 断路器打开后,尝试等待5秒进入半开状态
      failureRateThreshold: 20          # 当失败率达到20%时,断路器会打开,组织进一步的调用
      eventConsumerBufferSize: 10   #用于存储断路器相关事件的缓冲区大小为10,这些事件可用于被监控
 
#重试策略相关的配置
resilience4j.retry:
  instances:
    backendA:
      maxAttempts: 3              #最大重试次数
      waitDuration: 2s            #每次重试的时候间隔的等待时间
      enableExponentialBackoff: true
      exponentialBackoffMultiplier: 2
      retryExceptions:
        - java.lang.Exception
 
resilience4j.bulkhead:
  instances:
    backendA:
      maxConcurrentCalls: 10
 
resilience4j.thread-pool-bulkhead:
  instances:
    backendC:
      maxThreadPoolSize: 11      #配置最大线程池大小
      coreThreadPoolSize: 1     #配置核心线程池大小
      queueCapacity: 1          #配置队列的容量
 
#限流的配置
resilience4j.ratelimiter:
  instances:
    backendA:               # 限流器的名字
      limitForPeriod: 1     # 一个限制周期内可访问次数
      limitRefreshPeriod: 1s    # 限制周期,每个周期之后,速率限制器将重置回limitForPeriod值
      timeoutDuration: 10ms     # 线程等待允许执行时间
      registerHealthIndicator: true
      eventConsumerBufferSize: 100
 
server:
  port: 8081

4.1.3 添加配置类

自定义配置类,将配置文件中配置的每一种熔断器对应的项注册到spring的bean容器中,比如在配置文件中有一个实例名称为backendA;

import io.github.resilience4j.common.circuitbreaker.configuration.CircuitBreakerConfigCustomizer;
import io.github.resilience4j.common.ratelimiter.configuration.RateLimiterConfigCustomizer;
import org.springframework.context.annotation.Bean;
 
public class ResilienceConfig {
 
    @Bean
    public CircuitBreakerConfigCustomizer circuitBreakerConfigCustomizer() {
        return CircuitBreakerConfigCustomizer
                .of("backendA", builder -> builder.slidingWindowSize(10));
    }
 
    @Bean
    public RateLimiterConfigCustomizer rateLimiterConfigCustomizer() {
        return RateLimiterConfigCustomizer
                .of("backendA", builder -> builder.limitForPeriod(1));
    }
}

4.1.4 测试业务

在这里我们测试两个场景,第一个场景为重试,第二个场景为限流,对于下面的两个方法做如下的补充说明:

import com.congge.entity.User;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.ratelimiter.annotation.RateLimiter;
import io.github.resilience4j.retry.annotation.Retry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
 
import java.util.Objects;
 
@Service
public class UserService {
 
    private Logger logger = LoggerFactory.getLogger(UserService.class);
 
    @CircuitBreaker(name = "backendA")
    @Retry(name = "backendA")
//    @RateLimiter(name = "backendA", fallbackMethod = "fallback")
    public User retry() {
        logger.info("backendA findUser");
        String res = new RestTemplate().getForObject("http://localhost:8088", String.class);
        if (Objects.nonNull(res)) {
            return new User("remote user", 18, "Man");
        }
        return new User("default user", 22, "Woman");
    }
 
    @CircuitBreaker(name = "backendA")
    @RateLimiter(name = "backendA",fallbackMethod = "fallback")
    public User limit() {
        return new User("remote user", 18, "Man");
    }
 
    public User fallback(Throwable e) {
        return new User("降级的用户", 18, "D");
    }
}

控制器类用于测试使用

@RestController
public class UserController {
 
    @Resource
    private UserService userService;
 
    @GetMapping("/retry")
    public User retry() {
        return userService.retry();
    }
 
    @GetMapping("/limit")
    public User limit() {
        return userService.limit();
    }
}

4.1.5 接口测试

重试功能测试,在第一个方法中,远程调用一个不存在的链接,理论上会失败,按照参数配置,调用失败后,会重试3次,如果3次之后仍然失败,则接口抛出异常(或使用降级的方法的返回结果)。

重试接口测试

接口经过一段时间之后失败

同时查看控制台日志输出,不难发现,重试了3次,3次时候输出了异常的堆栈信息。

限流接口测试

调用限流接口,在配置文件中默认的是每秒允许通过一个请求,当我们正常请求接口时,可以正常得到结果。

快速刷接口时,由于方法中添加了降级方法,将会得到降级的响应结果。

4.2 参数解读补充

通过上面的案例演示了使用Resilience4j进行接口的重试或限流操作,只需配置好核心的参数,并且在方法上添加相关的注解即可。

在实际开发中,对Resilience4j的使用,也是重点落在对配置文件中3个模块配置参数的调整,了解这些配置参数的具体含义才能结合实际场景合理使用,具体来说,主要是下面几部分:

4.2.1 circuitbreaker常用配置参数

circuitbreaker的常用可配置参数项如下

配置参数默认值描述
failureRateThreshold50熔断器关闭状态和半开状态使用的同一个失败率阈值
ringBufferSizeInHalfOpenState10熔断器半开状态的缓冲区大小,会限制线程的并发量,例如缓冲区为10则每次只会允许10个请求调用后端服务
ringBufferSizeInClosedState100熔断器关闭状态的缓冲区大小,不会限制线程的并发量,在熔断器发生状态转换前所有请求都会调用后端服务
waitDurationInOpenState60(s)熔断器从打开状态转变为半开状态等待的时间
automaticTransitionFromOpenToHalfOpenEnabledfalse如果置为true,当等待时间结束会自动由打开变为半开,若置为false,则需要一个请求进入来触发熔断器状态转换
recordExceptionsempty需要记录为失败的异常列表
ignoreExceptionsempty需要忽略的异常列表
recordFailurethrowable -> true自定义的谓词逻辑用于判断异常是否需要记录或者需要忽略,默认所有异常都进行记录
slowCallRateThreshold100以百分比配置阈值。当呼叫持续时间大于等于或大于阈值时,CircuitBreaker 将呼叫视为慢速呼叫。 当慢速呼叫的百分比等于或大于阈值时,CircuitBreaker 转换为打开并开始短路呼叫
slowCallDurationThreshold60000(ms)配置持续时间阈值,超过该阈值呼叫被视为慢速并提高慢速呼叫率。
permittedNumberOfCallsInHalfOpenState10配置 CircuitBreaker 半开时允许的调用次数。
maxWaitDurationInHalfOpenState100配置在 CircuitBreaker 计算错误率或慢速调用率之前所需的最小调用次数(每个滑动窗口周期)。
waitDurationInOpenState100断路器在从打开转换为半打开之前应等待的时间
slidingWindowSize100如果滑动窗口是 COUNT_BASED,则记录并汇总最后一次调用。 如果滑动窗口是TIME_BASED,则记录并汇总最后几秒的调用。slidingWindowSize配置用于在 CircuitBreaker 关闭时记录调用结果的滑动窗口的大小。
slidingWindowTypeCOUNT_BASED配置用于在CircuitBreaker关闭时记录调用结果的滑动窗口类型。 滑动窗口可以是基于计数或基于时间的。

4.2.2 ratelimiter常用配置参数

ratelimiter的常用可配置参数项如下

配置参数默认值描述
timeoutDuration5s线程等待权限的默认等待时间
limitRefreshPeriod500ms权限刷新的时间,每个周期结束后,RateLimiter将会把权限计数设置为limitForPeriod的值
limiteForPeriod一个限制刷新期间的可用权限数

4.2.3 retry常用配置参数

retry的常用可配置参数项如下

配置参数默认值描述
maxAttempts3最大重试次数
waitDuration500ms固定重试间隔
intervalFunctionnumberOfAttempts -> waitDuration用来改变重试时间间隔,可以选择指数退避或者随机时间间隔
retryOnResultPredicateresult -> false自定义结果重试规则,需要重试的返回true
retryOnExceptionPredicatethrowable -> true自定义异常重试规则,需要重试的返回true
retryExceptionsempty需要重试的异常列表
ignoreExceptionsempty需要忽略的异常列表

五、写在文末

熔断、限流与降级作为微服务治理中的重要一环,有必要对其引起重视和关注,而Resilience4j吸收了主流熔断器的优势,具备更为灵活的特点,适用于普通的springboot项目,也能很方便的集成到微服务框架中,值得深入的学习和研究。

以上就是SpringBoot整合Resilience4j使用详解的详细内容,更多关于SpringBoot整合Resilience4j的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文