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

news2024/11/23 6:49:54

基础介绍

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

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

Ring Bit Buffer(环形缓冲区)

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

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

每一次请求的成功或失败状态只占用一个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依赖:


    io.github.resilience4j
    resilience4j-spring-boot
    0.9.0

复制代码
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 process() throws TimeoutException, InterruptedException;
}
复制代码
连接器调用该服务

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

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

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


public class CircuitBreakerUtil {

    public static void getCircuitBreakerStatus(String time, CircuitBreaker circuitBreaker){
        CircuitBreaker.Metrics metrics = circuitBreaker.getMetrics();

        float failureRate = metrics.getFailureRate();

        int bufferedCalls = metrics.getNumberOfBufferedCalls();

        int failedCalls = metrics.getNumberOfFailedCalls();

        int successCalls = metrics.getNumberOfSuccessfulCalls();

        int maxBufferCalls = metrics.getMaxNumberOfBufferedCalls();

        long notPermittedCalls = metrics.getNumberOfNotPermittedCalls();
        log.info(time + "state=" +circuitBreaker.getState() + " , metrics[ failureRate=" + failureRate +
                ", bufferedCalls=" + bufferedCalls +
                ", failedCalls=" + failedCalls +
                ", successCalls=" + successCalls +
                ", maxBufferCalls=" + maxBufferCalls +
                ", notPermittedCalls=" + notPermittedCalls +
                " ]"
        );
    }

    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{

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

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

public class CircuitBreakerServiceImpl {

    private RemoteServiceConnector remoteServiceConnector;

    private CircuitBreakerRegistry circuitBreakerRegistry;
    public List circuitBreakerNotAOP(){

        CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("service1");
        CircuitBreakerUtil.getCircuitBreakerStatus("执行开始前:", circuitBreaker);

        CheckedFunction0> checkedSupplier = CircuitBreaker.

            decorateCheckedSupplier(circuitBreaker, remoteServiceConnector::process);

        Try> result = Try.of(checkedSupplier).

                    recover(CallNotPermittedException.class, throwable -> {
                        log.info("熔断器已经打开,拒绝访问被保护方法~");
                        CircuitBreakerUtil
                        .getCircuitBreakerStatus("熔断器打开中:", circuitBreaker);
                        List users = new ArrayList();
                        return users;
                    })
                    .recover(throwable -> {
                        log.info(throwable.getLocalizedMessage() + ",方法被降级了~~");
                        CircuitBreakerUtil
                        .getCircuitBreakerStatus("降级方法中:",circuitBreaker);
                        List users = new ArrayList();
                        return users;
                    });
            CircuitBreakerUtil.getCircuitBreakerStatus("执行结束后:", circuitBreaker);
            return result.get();
    }
}
复制代码
AOP式的调用方法

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

public RemoteServiceConnector{

    public List process() throws TimeoutException, InterruptedException {
        List users;
        users = remoteServic.process();
        return users;
    }

    private List fallBack(Throwable throwable){
        log.info(throwable.getLocalizedMessage() + ",方法被降级了~~");
        CircuitBreakerUtil.getCircuitBreakerStatus("降级方法中:", circuitBreakerRegistry.circuitBreaker("backendA"));
        List users = new ArrayList();
        return users;
    }

    private List fallBack(CallNotPermittedException e){
        log.info("熔断器已经打开,拒绝访问被保护方法~");
        CircuitBreakerUtil.getCircuitBreakerStatus("熔断器打开中:", circuitBreakerRegistry.circuitBreaker("backendA"));
        List users = new ArrayList();
        return users;
    }

}
复制代码

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

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

public class CircuitBreakerServiceImpl {

    private RemoteServiceConnector remoteServiceConnector;

    private CircuitBreakerRegistry circuitBreakerRegistry;

    public List circuitBreakerAOP() throws TimeoutException, InterruptedException {
        CircuitBreakerUtil
            .getCircuitBreakerStatus("执行开始前:",circuitBreakerRegistry.circuitBreaker("backendA"));
        List 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 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{

    private CircuitBreakerServiceImpl circuitService;

    public void circuitBreakerTest() {
        for (int i=0; i<10; i++){

            circuitService.circuitBreakerNotAOP();
        }
    }
}
复制代码

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

public class CircuitBreakerServiceImplTest{

@Autowired
private CircuitBreakerServiceImpl circuitService

@Test
public void circuitBreakerThreadTest() throws InterruptedException {
    ExecutorService pool = Executors.newCachedThreadPool()
    for (int i=0
        pool.submit(
            // circuitService::circuitBreakerAOP
            circuitService::circuitBreakerNotAOP)
    }
    pool.shutdown()

    while (!pool.isTerminated())

    Thread.sleep(10000)
    log.info("熔断器状态已转为半开")
    pool = Executors.newCachedThreadPool()
    for (int i=0
        pool.submit(
            // circuitService::circuitBreakerAOP
            circuitService::circuitBreakerNotAOP)
    }
    pool.shutdown()

    while (!pool.isTerminated())
    for (int i=0

    }
}
复制代码

}

resilience4j:
  circuitbreaker:
    configs:
      myDefault:
        automaticTransitionFromOpenToHalfOpenEnabled: true
复制代码

分享资源

资源分享
获取以上资源请访问开源项目 点击跳转

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/875315.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Beats:使用 Filebeat 将 golang 应用程序记录到 Elasticsearch - 8.x

毫无疑问&#xff0c;日志记录是任何应用程序最重要的方面之一。 当事情出错时&#xff08;而且确实会出错&#xff09;&#xff0c;我们需要知道发生了什么。 为了实现这一目标&#xff0c;我们可以设置 Filebeat 从我们的 golang 应用程序收集日志&#xff0c;然后将它们发送…

大数据培训前景怎么样?企业需求量大吗

大数据行业对大家来说并不陌生&#xff0c;大数据行业市场人才需求量大&#xff0c;越早入行越有优势&#xff0c;发展机会和上升空间等大。不少人通过大数据培训来提升自己的经验和自身技术能力&#xff0c;以此来获得更好的就业机会。 2023大数据培训就业前景怎么样呢?企业需…

落地大模型应知必会(3): 如何构建多任务的LLM应用

编者按&#xff1a;今年以来&#xff0c;大语言模型(LLM)已被广泛应用于各种自然语言处理任务&#xff0c;也越来越多地被用于构建复杂的语言应用。但是构建多任务的 LLM 应用仍面临一定的挑战&#xff0c;需要解决任务组合和调控等问题。 本文内容介绍了构建多任务 LLM 应用可…

win10中Docker安装、构建镜像、创建容器、Vscode连接实例

Docker方便一键构建项目所需的运行环境&#xff1a;首先构建镜像(Image)。然后镜像实例化成为容器(Container)&#xff0c;构成项目的运行环境。最后Vscode连接容器&#xff0c;方便我们在本地进行开发。下面以一个简单的例子介绍在win10中实现&#xff1a;Docker安装、构建镜像…

STM32--TIM定时器(2)

文章目录 输出比较PWM输出比较通道参数计算舵机简介直流电机简介TB6612 PWM基本结构PWM驱动呼吸灯PWM驱动舵机PWM控制电机 输出比较 输出比较&#xff0c;简称OC&#xff08;Output Compare&#xff09;。 输出比较的原理是&#xff0c;当定时器计数值与比较值相等或者满足某种…

【计算机视觉|生成对抗】改进的生成对抗网络(GANs)训练技术

本系列博文为深度学习/计算机视觉论文笔记&#xff0c;转载请注明出处 标题&#xff1a;Improved Techniques for Training GANs 链接&#xff1a;[1606.03498v1] Improved Techniques for Training GANs (arxiv.org) 摘要 本文介绍了一系列应用于生成对抗网络&#xff08;G…

2023国自然预计本周公布!如何第一时间知道是否中标?

国自然公布时间越来越近了&#xff0c;大家普遍关心今年国自然的具体公布时间。 基金委官网上明确&#xff1a;国家自然科学基金项目从接收至审批完成一般需要5个月左右时间。 集中接收的大部分项目类型&#xff0c;资助结果一般在当年8月中下旬公布&#xff0c;其余项目根据…

时序预测 | MATLAB实现基于GRU门控循环单元的时间序列预测-递归预测未来(多指标评价)

时序预测 | MATLAB实现基于GRU门控循环单元的时间序列预测-递归预测未来(多指标评价) 目录 时序预测 | MATLAB实现基于GRU门控循环单元的时间序列预测-递归预测未来(多指标评价)预测结果基本介绍程序设计参考资料 预测结果 基本介绍 1.Matlab实现GRU门控循环单元时间序列预测未…

vue下载模板

<div class"el-upload__tip"><el-buttonstyle"margin-top: 2px"size"small"type"primary"click"downloadTemplate()">下载模板</el-button></div>/***todo 下载模板*/downloadTemplate() {getDownl…

WebStorm修改默认打开的浏览器

有两种方式第一种修改系统默认浏览器 我采用的是下面这种&#xff0c;在webstorm中修改 将浏览器设置为默认的浏览器即可

关于Java中synchronized的实现原理

并发编程的三个理念 原子性&#xff1a;一个操作要么全部完成&#xff0c;要么全部失败。可见性&#xff1a;当一个线程对共享变量进行修改后&#xff0c;其他线程也应立刻看到。有序性&#xff1a;程序按照顺序执行 synchronized基本使用 修饰静态方法&#xff0c;锁的是类…

mysql 笔记(二)-mysql存储引擎

存储引擎在mysql体系架构中位于第三层, 负责mysql中的数据存储和提取,根据mysql提供的文件访问层抽象接口定制的一种文件访问机制. 使用show engines命令可以查看当前数据库支持的引擎信息. 从截图可看到, mysql 默认的存储引擎是InnoDB,支持事务,行锁,外键,支持分布式事务(…

SSD是否可以提升游戏性能或帧数?

​在购买这种新型硬盘之前&#xff0c;你可能会有些疑问。在这篇文章中&#xff0c;我将解释什么是固态硬盘&#xff08;SSD&#xff09;&#xff0c;它是否能提升游戏性能&#xff0c;以及如何将你的旧硬盘替换为新的固态硬盘。​ 更换SSD可以让我的游戏运行更流畅吗&#xff…

TB/TM-商品详情

一、接口参数说明&#xff1a; item_get-获得商品详情&#xff0c;点击更多API调试&#xff0c;请移步注册API账号点击获取测试key和secret 公共参数 请求地址: https://api-gw.onebound.cn/taobao/item_get 名称类型必须描述keyString是调用key&#xff08;点击获取测试key…

ssm+vue医院住院管理系统源码和论文PPT

ssmvue医院住院管理系统源码和论文PPT012 开发工具&#xff1a;idea 数据库mysql5.7(mysql5.7最佳) 数据库链接工具&#xff1a;navcat,小海豚等 开发技术&#xff1a;java ssm tomcat8.5 摘 要 随着时代的发展&#xff0c;医疗设备愈来愈完善&#xff0c;医院也变成人们生…

解密Flink的状态管理:探索流处理框架的数据保留之道,释放流处理的无限潜能!

水善利万物而不争&#xff0c;处众人之所恶&#xff0c;故几于道&#x1f4a6; 文章目录 一、什么是状态二、应用场景三、Flink中状态的分类四、算子状态1. 列表状态&#xff08;List State&#xff09;2. 广播状态&#xff08;Broadcast State&#xff09; 五、键控状态1. Val…

基于Springboot+vue+elementUI+MySQL的学生信息管理系统(一)前端部分

源码在本人博客资源当中&#xff0c;本文为项目代码的详细介绍解释&#xff0c;供于大家学习使用 Vue项目的入口文件&#xff1a;mian.js //vue项目入口文件 //导入vue import Vue from vue //导入根组件app import App from ./App //导入路由文件 import router from ./rout…

Jsoup爬取简单信息

1. 豆瓣图书最受关注 1.1 创建SpringBoot项目或者Maven项目 1.2 引入jsoup <dependency><!-- jsoup HTML parser library https://jsoup.org/ --><groupId>org.jsoup</groupId><artifactId>jsoup</artifactId><version>1.15.3<…

全国区划代码数据筛选重组

你知道的越多&#xff0c;你不知道的越多 点赞再看&#xff0c;养成习惯 如果您有疑问或者见解&#xff0c;欢迎指教&#xff1a; 企鹅&#xff1a;869192208 文章目录 前言引入jar包实现思路代码实现验证 Guava工具类找出两个 Map 集合的差异数据筛选残联区划和全国区划差异组…

宇凡微电热毯方案开发,多档调节带定时

电热毯在1912年发明&#xff0c;到现在已有百年历史。现在的电热毯更有了许多智能化产品&#xff0c;这么多年来拯救了许多怕冷的小伙伴们&#xff0c;在寒冷的冬季靠它续命。宇凡微推出的电热毯方案&#xff0c;电热毯单片机使用54E&#xff0c;实现的功能有档位调节&#xff…