【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

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

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.resilience4jresilience4j-spring-boot0.9.0复制代码
application.yml配置
resilience4j:circuitbreaker:configs:default:ringBufferSizeInClosedState: 5ringBufferSizeInHalfOpenState: 2waitDurationInOpenState: 10000failureRateThreshold: 60eventConsumerBufferSize: 10registerHealthIndicator: trueautomaticTransitionFromOpenToHalfOpenEnabled: falserecordFailurePredicate:    com.example.resilience4j.exceptions.RecordFailurePredicaterecordExceptions:- com.hyts.resilience4j.exceptions.Service1Exception- com.hyts.resilience4j.exceptions.Service2ExceptionignoreExceptions:- com.example.resilience4j.exceptions.BusinessAExceptioninstances:service1:baseConfig: defaultwaitDurationInOpenState: 5000failureRateThreshold: 20service2: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=0pool.submit(// circuitService::circuitBreakerAOPcircuitService::circuitBreakerNotAOP)}pool.shutdown()while (!pool.isTerminated())Thread.sleep(10000)log.info("熔断器状态已转为半开")pool = Executors.newCachedThreadPool()for (int i=0pool.submit(// circuitService::circuitBreakerAOPcircuitService::circuitBreakerNotAOP)}pool.shutdown()while (!pool.isTerminated())for (int i=0}
}
复制代码

}

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

分享资源

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

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

树莓派3B CSI摄像头配置

1.硬件连接 1、找到 CSI 接口(树莓派3B的CSI接口在HDMI接口和音频口中间)&#xff0c;需要拉起 CSI 接口挡板,如下&#xff1a; 2、将摄像头排线插入CSI接口。记住&#xff0c;有蓝色胶带的一面应该面向音频口或者网卡方向&#xff0c; 确认方向并插紧排线&#xff0c;将挡板…

Tomcat+Http+Servlet

文章目录 1.HTTP1.1 请求和响应HTTP请求&#xff1a;请求行请求头请求体HTTP响应&#xff1a;响应行&#xff08;状态行&#xff09;响应头响应体 2. Apache Tomcat2.1 基本使用2.2 IDEA中创建 Maven Web项目2.3 IDEA中使用Tomcat 3. Servlet3.1 Servlet快速入门3.2 Servlet执行…

Scala函数式编程

概念 函数 一种具有名或匿名的操作。其代码直到被调用时才执行。在函数的定义中&#xff0c;可能有也可能没有引用外部的未绑定变量。 def 函数名([参数名: 参数类型],...) [: 返回值类型] {语句[return] 返回值 }函数声明的关键字是 def[参数名: 参数类型],…&#xff1a;…

如何使用SpringBoot 自定义转换器

&#x1f600;前言 本篇博文是关于SpringBoot 自定义转换器的使用&#xff0c;希望你能够喜欢&#x1f60a; &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章可以帮助到大家&#xff0c;您的…

简单谈谈 EMP-SSL:自监督对比学习的一种极简主义风

论文链接&#xff1a;https://arxiv.org/pdf/2304.03977.pdf 代码&#xff1a;https://github.com/tsb0601/EMP-SSL 其他学习链接&#xff1a;突破自监督学习效率极限&#xff01;马毅、LeCun联合发布EMP-SSL&#xff1a;无需花哨trick&#xff0c;30个epoch即可实现SOTA 主要…

Vue3 setup tsx 子组件向父组件传值 emit

需求&#xff1a;Vue3 setup 父组件向子组件传值&#xff0c;子组件接收父组件传入的值&#xff1b;子组件向父组件传值&#xff0c;父组件接收的子组件传递的值。 父组件&#xff1a;parent.tsx&#xff1a; import { defineComponent, ref, reactive } from vue; import To…

【STM32】利用CubeMX对FreeRTOS用按键控制任务

对于FreeRTOS中的操作&#xff0c;最常用的就是创建、删除、暂停和恢复任务。 此次实验目标&#xff1a; 1.创建任务一&#xff1a;LED1每间隔1秒闪烁一次&#xff0c;并通过串口打印 2.创建任务二&#xff1a;LED2每间隔0.5秒闪烁一次&#xff0c;并通过串口打印 3.创建任…

【工作记录】mysql中实现分组统计的三种方式

前言 实际工作中对范围分组统计的需求还是相对普遍的&#xff0c;本文记录下在mysql中通过函数和sql完成分组统计的实现过程。 数据及期望 比如我们获取到了豆瓣电影top250&#xff0c;现在想知道各个分数段的电影总数. 表数据如下: 期望结果: 实现方案 主要思路是根据s…

SpringMVC拦截器

1.拦截器简介 拦截器&#xff08;Interceptor&#xff09;是一种动态拦截方法调用的机制&#xff0c;在SpringMVC中动态拦截控制器方法的执行 作用: 在指定的方法调用前后执行预先设定的代码 阻止原始方法的执行 总结&#xff1a;拦截器就是用来做增强 看完以后&#xff0…

【在一个升序数组中插入一个数仍升序输出】

在一个升序数组中插入一个数仍升序输出 题目举例&#xff1a; 有一个升序数组nums&#xff0c;给一个数字data&#xff0c;将data插入数组nums中仍旧保证nums升序&#xff0c;返回数组中有效元素个数。 比如&#xff1a;nums[100] {1, 2, 3, 5, 6, 7, 8, 9} size 8 data 4 …

elementUi表单恢复至初始状态并不触发表单验证

elementUi表单恢复至初始状态并不触发表单验证 1.场景再现2.解决方法 1.场景再现 左侧是树形列表&#xff0c;右侧是显示节点的详情&#xff0c;点击按钮应该就是新增一个规则的意思&#xff0c;表单内容是没有改变的&#xff0c;所以就把需要把表单恢复至初始状态并不触发表单…

正则表达式试炼

序 我希望在这里列出我很多想写的正则表达式&#xff0c;很多我想写&#xff0c;但是不知道怎么写的。分享点滴案例。未来这个文章会越来越长 前言 互联网时代&#xff0c;除了文本还有更好的学习方式&#xff0c;下面是几个不错的练习网站&#xff0c;如果你想系统地学习&a…

深入了解Linux运维的重要性与最佳实践

Linux作为开源操作系统的代表&#xff0c;在企业级环境中的应用越来越广泛。而在保障Linux系统的正常运行和管理方面&#xff0c;Linux运维显得尤为关键。本文将介绍Linux运维的重要性以及一些最佳实践&#xff0c;帮助读者更好地了解和掌握Linux系统的运维技巧。 首先&#xf…

如何更快地执行 Selenium 测试用例?

前言&#xff1a; 当我们谈论自动化时&#xff0c;首先想到的工具之一是 Selenium。我们都知道Selenium WebDriver 是一个出色的 Web 自动化工具。实施Selenium 自动化测试的主要原因是加速 selenium 测试。在大多数情况下&#xff0c;Selenium 的性能比手动的要好得多。但是&…

离线安装vscode插件,导出 Visual Studio Code 的扩展应用,并离线安装

在没有网络的情况下&#xff0c;如何安装vscode插件 1.使用之前电脑安装过的插件包 Visual Studio Code 的扩展应用安装位置在文件夹 .vscode/extensions 下。不同平台&#xff0c;它位于&#xff1a; Windows %USERPROFILE%\.vscode\extensions Mac ~/.vscode/extensions L…

C字符串练习题(6.3.1)

编写一个程序&#xff0c;从键盘上读入一个小于1000的正整数&#xff0c;然后创建并输出一个字符串&#xff0c;说明该整数的值。例如&#xff0c;输入941&#xff0c;程序产生的字符串是“Nine hundred and forty one”。 #include<stdlib.h> #include<string.h>…

【JAVA】我们常常谈到的方法是指什么?

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️初识JAVA】 文章目录 前言方法方法的分类方法的定义方法调用方法重载 前言 在之前的文章中我们总是会介绍到类中的各式各样的方法&#xff0c;也许在应用中我们对它已经有了初步的了解&#xff0c;今…

# ⛳ Docker 安装、配置和详细使用教程-Win10专业版

目录 ⛳ Docker 安装、配置和详细使用教程-Win10专业版&#x1f69c; 一、win10 系统配置&#x1f3a8; 二、Docker下载和安装&#x1f3ed; 三、Docker配置&#x1f389; 四、Docker入门使用 ⛳ Docker 安装、配置和详细使用教程-Win10专业版 &#x1f69c; 一、win10 系统配…

使用docker快速搭建wordpress服务,并指定域名访问

文章目录 引入使用docker快速跑起服务创建数据库安装wordpress服务配置域名 引入 wordpress是一个基于PHP语言编写的开源的内容管理系统&#xff08;CMS&#xff09;&#xff0c;它有丰富的插件和主题&#xff0c;可以非常简单的创建各种类型的网站&#xff0c;包括企业网站、…

vuejs 设计与实现 - 渲染器 - 挂载与更新

渲染器的核心功能:挂载与更新 1.挂载子节点和元素的属性 1.2挂载子节点 (vnode.children) vnode.children可以是字符串类型的&#xff0c;也可以是数组类型的&#xff0c;如下&#xff1a; const vnode {type: div,children: [{type: p,children: hello}] } 可以看到&#…