【SpringCloud技术专题】「Resilience4j入门指南」(1)轻量级熔断框架的入门指南

简介: 【SpringCloud技术专题】「Resilience4j入门指南」(1)轻量级熔断框架的入门指南

基础介绍


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




使用Resilience4j


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




核心模块


  • resilience4j-circuitbreaker: Circuit breaking
  • resilience4j-ratelimiter: Rate limiting
  • resilience4j-bulkhead: Bulkheading
  • resilience4j-retry: Automatic retrying (sync and async)
  • resilience4j-cache: Result caching
  • resilience4j-timelimiter: Timeout handling



Circuitbreaker


CircuitBreaker通过具有三种正常状态的有限状态机实现:CLOSED,OPEN和HALF_OPEN以及两个特殊状态DISABLED和FORCED_OPEN

image.png

  • 当熔断器关闭时,所有的请求都会通过熔断器。
  • 如果失败率超过设定的阈值,熔断器就会从关闭状态转换到打开状态,这时所有的请求都会被拒绝。
  • 当经过一段时间后,熔断器会从打开状态转换到半开状态,这时仅有一定数量的请求会被放入,并重新计算失败率,如果失败率超过阈值,则变为打开状态,如果失败率低于阈值,则变为关闭状态。



Ring Bit Buffer(环形缓冲区)


Resilience4j记录请求状态的数据结构和Hystrix不同,Hystrix是使用滑动窗口来进行存储的,而Resilience4j采用的是Ring Bit Buffer(环形缓冲区)。


Ring Bit Buffer在内部使用BitSet这样的数据结构来进行存储,BitSet的结构如下图所示:

image.png

每一次请求的成功或失败状态只占用一个bit位,与boolean数组相比更节省内存。BitSet使用long[]数组来存储这些数据,意味着16个值(64bit)的数组可以存储1024个调用状态。



执行监控范围


计算失败率需要填满环形缓冲区。如果环形缓冲区的大小为10,则必须至少请求满10次,才会进行故障率的计算,如果仅仅请求了9次,即使9个请求都失败,熔断器也不会打开。



请求拦截控制


但是CLOSE状态下的缓冲区大小设置为10并不意味着只会进入10个请求,在熔断器打开之前的所有请求都会被放入。



状态转换机制


  • 当故障率高于设定的阈值时,熔断器状态会从由CLOSE变为OPEN。这时所有的请求都会抛出CallNotPermittedException异常。
  • 当经过一段时间后,熔断器的状态会从OPEN变为HALF_OPEN,HALF_OPEN状态下同样会有一个Ring Bit Buffer,用来计算HALF_OPEN状态下的故障率,如果高于配置的阈值,会转换为OPEN,低于阈值则装换为CLOSE。
  • CLOSE状态下的缓冲区不同的地方在于,HALF_OPEN状态下的缓冲区大小会限制请求数,只有缓冲区大小的请求数会被放入。
  • DISABLED(始终允许访问)和FORCED_OPEN(始终拒绝访问)。这两个状态不会生成熔断器事件(除状态装换外),并且不会记录事件的成功或者失败。退出这两个状态的唯一方法是触发状态转换或者重置熔断器。



SpringBoot的整合方式


resilience4j-spring-boot集成了circuitbeaker、retry、bulkhead、ratelimiter几个模块,因为后续还要学习其他模块,就直接引入resilience4j-spring-boot依赖。



maven 的配置 pom.xml


测试使用的IDE为idea,使用的springboot进行学习测试,首先引入maven依赖:

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot</artifactId>
    <version>0.9.0</version>
</dependency>
复制代码



application.yml配置
resilience4j:
  circuitbreaker:
    configs:
      default:
        ringBufferSizeInClosedState: 5 # 熔断器关闭时的缓冲区大小
        ringBufferSizeInHalfOpenState: 2 # 熔断器半开时的缓冲区大小
        waitDurationInOpenState: 10000 # 熔断器从打开到半开需要的时间
        failureRateThreshold: 60 # 熔断器打开的失败阈值
        eventConsumerBufferSize: 10 # 事件缓冲区大小
        registerHealthIndicator: true # 健康监测
        automaticTransitionFromOpenToHalfOpenEnabled: false # 是否自动从打开到半开,不需要触发
        recordFailurePredicate:    com.example.resilience4j.exceptions.RecordFailurePredicate # 谓词设置异常是否为失败
        recordExceptions: # 记录的异常
          - com.hyts.resilience4j.exceptions.Service1Exception
          - com.hyts.resilience4j.exceptions.Service2Exception
        ignoreExceptions: # 忽略的异常
          - com.example.resilience4j.exceptions.BusinessAException
    instances:
      service1:
        baseConfig: default
        waitDurationInOpenState: 5000
        failureRateThreshold: 20
      service2:
        baseConfig: default
复制代码

可以配置多个熔断器实例,使用不同配置或者覆盖配置。


保护的后端服务


以一个后端服务为例,利用熔断器保护该服务。


interface RemoteService {
    List<User> process() throws TimeoutException, InterruptedException;
}
复制代码
连接器调用该服务


这是调用远端服务的连接器,我们通过调用连接器中的方法来调用后端服务。


public RemoteServiceConnector{
    public List<User> process() throws TimeoutException, InterruptedException {
        List<User> users;
        users = remoteServic.process();
        return users;
    }
}
复制代码
监控熔断器状态及事件


各个配置项的作用,需要获取特定时候的熔断器状态:


@Log4j2
public class CircuitBreakerUtil {
    /**
     * @Description: 获取熔断器的状态
     */
    public static void getCircuitBreakerStatus(String time, CircuitBreaker circuitBreaker){
        CircuitBreaker.Metrics metrics = circuitBreaker.getMetrics();
        // Returns the failure rate in percentage.
        float failureRate = metrics.getFailureRate();
        // Returns the current number of buffered calls.
        int bufferedCalls = metrics.getNumberOfBufferedCalls();
        // Returns the current number of failed calls.
        int failedCalls = metrics.getNumberOfFailedCalls();
        // Returns the current number of successed calls.
        int successCalls = metrics.getNumberOfSuccessfulCalls();
        // Returns the max number of buffered calls.
        int maxBufferCalls = metrics.getMaxNumberOfBufferedCalls();
        // Returns the current number of not permitted calls.
        long notPermittedCalls = metrics.getNumberOfNotPermittedCalls();
        log.info(time + "state=" +circuitBreaker.getState() + " , metrics[ failureRate=" + failureRate +
                ", bufferedCalls=" + bufferedCalls +
                ", failedCalls=" + failedCalls +
                ", successCalls=" + successCalls +
                ", maxBufferCalls=" + maxBufferCalls +
                ", notPermittedCalls=" + notPermittedCalls +
                " ]"
        );
    }
    /**
     * @Description: 监听熔断器事件
     */
    public static void addCircuitBreakerListener(CircuitBreaker circuitBreaker){
        circuitBreaker.getEventPublisher()
                .onSuccess(event -> log.info("服务调用成功:" + event.toString()))
                .onError(event -> log.info("服务调用失败:" + event.toString()))
                .onIgnoredError(event -> log.info("服务调用失败,但异常被忽略:" + event.toString()))
                .onReset(event -> log.info("熔断器重置:" + event.toString()))
                .onStateTransition(event -> log.info("熔断器状态改变:" + event.toString()))
                .onCallNotPermitted(event -> log.info(" 熔断器已经打开:" + event.toString()))
        ;
    }
复制代码

调用方法


CircuitBreaker支持两种方式调用,一种是程序式调用,一种是AOP使用注解的方式调用。


程序式的调用方法


在CircuitService中先注入注册器,然后用注册器通过熔断器名称获取熔断器。如果不需要使用降级函数,可以直接调用熔断器的executeSupplier方法或executeCheckedSupplier方法:

public class CircuitBreakerServiceImpl{
    @Autowired
    private CircuitBreakerRegistry circuitBreakerRegistry;
    public List<User> circuitBreakerNotAOP() throws Throwable {
        CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("service1");
        CircuitBreakerUtil.getCircuitBreakerStatus("执行开始前:", circuitBreaker);
        circuitBreaker.executeCheckedSupplier(remotServiceConnector::process);
    }
}
复制代码

如果需要使用降级函数,则要使用decorate包装服务的方法,再使用Try.of().recover()进行降级处理,同时也可以根据不同的异常使用不同的降级方法:

public class CircuitBreakerServiceImpl {
    @Autowired
    private RemoteServiceConnector remoteServiceConnector;
    @Autowired
    private CircuitBreakerRegistry circuitBreakerRegistry;
    public List<User> circuitBreakerNotAOP(){
        // 通过注册器获取熔断器的实例
        CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("service1");
        CircuitBreakerUtil.getCircuitBreakerStatus("执行开始前:", circuitBreaker);
        // 使用熔断器包装连接器的方法
        CheckedFunction0<List<User>> checkedSupplier = CircuitBreaker.
            decorateCheckedSupplier(circuitBreaker, remoteServiceConnector::process);
        // 使用Try.of().recover()调用并进行降级处理
        Try<List<User>> result = Try.of(checkedSupplier).
                    recover(CallNotPermittedException.class, throwable -> {
                        log.info("熔断器已经打开,拒绝访问被保护方法~");
                        CircuitBreakerUtil
                        .getCircuitBreakerStatus("熔断器打开中:", circuitBreaker);
                        List<User> users = new ArrayList();
                        return users;
                    })
                    .recover(throwable -> {
                        log.info(throwable.getLocalizedMessage() + ",方法被降级了~~");
                        CircuitBreakerUtil
                        .getCircuitBreakerStatus("降级方法中:",circuitBreaker);
                        List<User> users = new ArrayList();
                        return users;
                    });
            CircuitBreakerUtil.getCircuitBreakerStatus("执行结束后:", circuitBreaker);
            return result.get();
    }
}
复制代码



AOP式的调用方法


首先在连接器方法上使用@CircuitBreaker(name="",fallbackMethod="")注解,其中name是要使用的熔断器的名称,fallbackMethod是要使用的降级方法,降级方法必须和原方法放在同一个类中,且降级方法的返回值需要和原方法相同,输入参数需要添加额外的exception参数,类似这样:

public RemoteServiceConnector{
    @CircuitBreaker(name = "backendA", fallbackMethod = "fallBack")
    public List<User> process() throws TimeoutException, InterruptedException {
        List<User> users;
        users = remoteServic.process();
        return users;
    }
    private List<User> fallBack(Throwable throwable){
        log.info(throwable.getLocalizedMessage() + ",方法被降级了~~");
        CircuitBreakerUtil.getCircuitBreakerStatus("降级方法中:", circuitBreakerRegistry.circuitBreaker("backendA"));
        List<User> users = new ArrayList();
        return users;
    }
    private List<User> fallBack(CallNotPermittedException e){
        log.info("熔断器已经打开,拒绝访问被保护方法~");
        CircuitBreakerUtil.getCircuitBreakerStatus("熔断器打开中:", circuitBreakerRegistry.circuitBreaker("backendA"));
        List<User> users = new ArrayList();
        return users;
    }
} 
复制代码



可使用多个降级方法,保持方法名相同,同时满足的条件的降级方法会触发最接近的一个(这里的接近是指类型的接近,先会触发离它最近的子类异常),例如如果process()方法抛出CallNotPermittedException,将会触发fallBack(CallNotPermittedException e)方法而不会触发fallBack(Throwable throwable)方法。


之后直接调用方法就可以了:

public class CircuitBreakerServiceImpl {
    @Autowired
    private RemoteServiceConnector remoteServiceConnector;
    @Autowired
    private CircuitBreakerRegistry circuitBreakerRegistry;
    public List<User> circuitBreakerAOP() throws TimeoutException, InterruptedException {
        CircuitBreakerUtil
            .getCircuitBreakerStatus("执行开始前:",circuitBreakerRegistry.circuitBreaker("backendA"));
        List<User> result = remoteServiceConnector.process();
        CircuitBreakerUtil
            .getCircuitBreakerStatus("执行结束后:", circuitBreakerRegistry.circuitBreaker("backendA"));
        return result;
    }
}
复制代码



使用测试


接下来进入测试,首先我们定义了两个异常,异常A同时在黑白名单中,异常B只在黑名单中:


recordExceptions: # 记录的异常 - com.example.resilience4j.exceptions.BusinessBException - com.example.resilience4j.exceptions.BusinessAException ignoreExceptions: # 忽略的异常 - com.example.resilience4j.exceptions.BusinessAException 然后对被保护的后端接口进行如下的实现:

public class RemoteServiceImpl implements RemoteService {
    private static AtomicInteger count = new AtomicInteger(0);
    public List<User> process() {
        int num = count.getAndIncrement();
        log.info("count的值 = " + num);
        if (num % 4 == 1){
            throw new BusinessAException("异常A,不需要被记录");
        }
        if (num % 4 == 2 || num % 4 == 3){
            throw new BusinessBException("异常B,需要被记录");
        }
        log.info("服务正常运行,获取用户列表");
        // 模拟数据库的正常查询
        return repository.findAll();
    }
}
复制代码



使用CircuitBreakerServiceImpl中的AOP或者程序式调用方法进行单元测试,循环调用10次:

public class CircuitBreakerServiceImplTest{
    @Autowired
    private CircuitBreakerServiceImpl circuitService;
    @Test
    public void circuitBreakerTest() {
        for (int i=0; i<10; i++){
            // circuitService.circuitBreakerAOP();
            circuitService.circuitBreakerNotAOP();
        }
    }
}
复制代码



同时也可以看出白名单所谓的忽略,是指不计入缓冲区中(即不算成功也不算失败),有降级方法会调用降级方法,没有降级方法会抛出异常,和其他异常无异。



public class CircuitBreakerServiceImplTest{
@Autowired
private CircuitBreakerServiceImpl circuitService;
@Test
public void circuitBreakerThreadTest() throws InterruptedException {
    ExecutorService pool = Executors.newCachedThreadPool();
    for (int i=0; i<15; i++){
        pool.submit(
            // circuitService::circuitBreakerAOP
            circuitService::circuitBreakerNotAOP);
    }
    pool.shutdown();
    while (!pool.isTerminated());
    Thread.sleep(10000);
    log.info("熔断器状态已转为半开");
    pool = Executors.newCachedThreadPool();
    for (int i=0; i<15; i++){
        pool.submit(
            // circuitService::circuitBreakerAOP
            circuitService::circuitBreakerNotAOP);
    }
    pool.shutdown();
    while (!pool.isTerminated());
    for (int i=0; i<10; i++){
    }
}
复制代码



resilience4j:
  circuitbreaker:
    configs:
      myDefault:
        automaticTransitionFromOpenToHalfOpenEnabled: true # 是否自动从打开到半开




相关文章
|
19天前
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
|
19天前
|
缓存 安全 Java
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
从底层源码入手,通过代码示例,追踪AnnotationConfigApplicationContext加载配置类、启动Spring容器的整个流程,并对IOC、BeanDefinition、PostProcesser等相关概念进行解释
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
|
22天前
|
XML 监控 Java
Spring Cloud全解析:熔断之Hystrix简介
Hystrix 是由 Netflix 开源的延迟和容错库,用于提高分布式系统的弹性。它通过断路器模式、资源隔离、服务降级及限流等机制防止服务雪崩。Hystrix 基于命令模式,通过 `HystrixCommand` 封装对外部依赖的调用逻辑。断路器能在依赖服务故障时快速返回备选响应,避免长时间等待。此外,Hystrix 还提供了监控功能,能够实时监控运行指标和配置变化。依赖管理方面,可通过 `@EnableHystrix` 启用 Hystrix 支持,并配置全局或局部的降级策略。结合 Feign 可实现客户端的服务降级。
100 23
|
9天前
|
人工智能 开发框架 Java
重磅发布!AI 驱动的 Java 开发框架:Spring AI Alibaba
随着生成式 AI 的快速发展,基于 AI 开发框架构建 AI 应用的诉求迅速增长,涌现出了包括 LangChain、LlamaIndex 等开发框架,但大部分框架只提供了 Python 语言的实现。但这些开发框架对于国内习惯了 Spring 开发范式的 Java 开发者而言,并非十分友好和丝滑。因此,我们基于 Spring AI 发布并快速演进 Spring AI Alibaba,通过提供一种方便的 API 抽象,帮助 Java 开发者简化 AI 应用的开发。同时,提供了完整的开源配套,包括可观测、网关、消息队列、配置中心等。
527 8
|
6天前
|
存储 缓存 Java
在Spring Boot中使用缓存的技术解析
通过利用Spring Boot中的缓存支持,开发者可以轻松地实现高效和可扩展的缓存策略,进而提升应用的性能和用户体验。Spring Boot的声明式缓存抽象和对多种缓存技术的支持,使得集成和使用缓存变得前所未有的简单。无论是在开发新应用还是优化现有应用,合理地使用缓存都是提高性能的有效手段。
13 1
|
7天前
|
XML 前端开发 Java
控制spring框架注解介绍
控制spring框架注解介绍
|
7天前
|
存储 NoSQL Java
Spring Session框架
Spring Session 是一个用于在分布式环境中管理会话的框架,旨在解决传统基于 Servlet 容器的会话管理在集群和云环境中的局限性。它通过将用户会话数据存储在外部介质(如数据库或 Redis)中,实现了会话数据的跨服务器共享,提高了应用的可扩展性和性能。Spring Session 提供了无缝集成 Spring 框架的 API,支持会话过期策略、并发控制等功能,使开发者能够轻松实现高可用的会话管理。
Spring Session框架
|
14天前
|
Java 应用服务中间件 开发者
深入探索并实践Spring Boot框架
深入探索并实践Spring Boot框架
25 2
|
14天前
|
机器学习/深度学习 数据采集 JavaScript
ADR智能监测系统源码,系统采用Java开发,基于SpringBoot框架,前端使用Vue,可自动预警药品不良反应
ADR药品不良反应监测系统是一款智能化工具,用于监测和分析药品不良反应。该系统通过收集和分析病历、处方及实验室数据,快速识别潜在不良反应,提升用药安全性。系统采用Java开发,基于SpringBoot框架,前端使用Vue,具备数据采集、清洗、分析等功能模块,并能生成监测报告辅助医务人员决策。通过集成多种数据源并运用机器学习算法,系统可自动预警药品不良反应,有效减少药害事故,保障公众健康。
ADR智能监测系统源码,系统采用Java开发,基于SpringBoot框架,前端使用Vue,可自动预警药品不良反应
|
23天前
|
运维 NoSQL Java
SpringBoot接入轻量级分布式日志框架GrayLog技术分享
在当今的软件开发环境中,日志管理扮演着至关重要的角色,尤其是在微服务架构下,分布式日志的统一收集、分析和展示成为了开发者和运维人员必须面对的问题。GrayLog作为一个轻量级的分布式日志框架,以其简洁、高效和易部署的特性,逐渐受到广大开发者的青睐。本文将详细介绍如何在SpringBoot项目中接入GrayLog,以实现日志的集中管理和分析。
100 1
下一篇
无影云桌面