芋道 Spring Boot 服务容错 Resilience4j 入门
总阅读量:18407次
摘要: 原创出处 http://www.iocoder.cn/Spring-Boot/Resilience4j/ 「芋道源码」欢迎转载,保留摘要,谢谢!
- 1. 概述
- 2. CircuitBreaker 熔断器
- 3. RateLimiter 限流器
- 4. Bulkhead 舱壁
- 5. Retry 重试
- 6. TimeLimiter 限时器
- 7. 执行顺序
- 8. 监控端点
- 9. 集成到 Feign
- 10. 集成到 Dubbo
- 666. 彩蛋
本文在提供完整代码示例,可见 https://github.com/YunaiV/SpringBoot-Labs 的 lab-59 目录。
原创不易,给点个 Star 嘿,一起冲鸭!
1. 概述
友情提示:如下关于 Resilience4j 的介绍,英文对应 https://resilience4j.readme.io/docs/getting-started 文档。
Resilience4j 是一个轻量级的容错组件,其灵感来自于 Hystrix,但主要为 Java 8 和函数式编程所设计。
轻量级体现在其只用 Vavr 库,没有任何外部依赖。而 Hystrix 依赖了 Archaius,Archaius 本身又依赖很多第三方包,例如 Guava、Apache Commons Configuration 等等。
友情提示:如果胖友对服务容错、服务降级、服务雪崩、熔断器等概念不是很熟悉,建议先看看《芋道 Spring Boot 服务容错 Hystrix 入门》文章的「1. 概述」小节。
一来是这些概念面试容易问,二来整清楚为什么要使用容错组件。
要使用 Resilience4j,不需要引入所有依赖,只需要引入你所需的模块。
友情提示:模块比较丰富,重要的艿艿已经加粗。
- Core modules 核心模块
-
resilience4j-circuitbreaker
: Circuit breaking 熔断器 -
resilience4j-ratelimiter
: Rate limiting 限流 -
resilience4j-bulkhead
: Bulkheading 舱壁隔离 -
resilience4j-retry
: Automatic retrying (sync and async) 自动重试(同步和异步) -
resilience4j-cache
: Result caching 响应缓存看了下官方文档,Resilience4j 提供的 Cache 功能,指的是针对 JSR 107 JCACHE 的集成,和 Hystrix 的请求缓存不是一个东东。
从 ISSUE#815 的讨论,也可以看出,就是针对 JCACHE 的容错。
-
resilience4j-timelimiter
: Timeout handling 超时处理
-
- Add-on modules
resilience4j-retrofit
: Retrofit adapterresilience4j-feign
: Feign adapterresilience4j-consumer
: Circular Buffer Event consumerresilience4j-kotlin
: Kotlin coroutines support
- Frameworks modules
resilience4j-spring-boot
: Spring Boot Starterresilience4j-spring-boot2
: Spring Boot 2 Starterresilience4j-ratpack
: Ratpack Starterresilience4j-vertx
: Vertx Future decorator
- Reactive modules
resilience4j-rxjava2
: Custom RxJava2 operatorsresilience4j-reactor
: Custom Spring Reactor operators
- Metrics modules
resilience4j-micrometer
: Micrometer Metrics exporterresilience4j-metrics
: Dropwizard Metrics exporterresilience4j-prometheus
: Prometheus Metrics exporter
下面,我们开始愉快的入门吧~
2. CircuitBreaker 熔断器
示例代码对应仓库:
- 用户服务:
lab-59-user-service
- Resilience4j 示例项目:
lab-59-resilience4j-demo01
CircuitBreaker 一共有 CLOSED
、OPEN
、HALF_OPEN
三种状态,通过状态机实现。转换关系如下图所示:
- 当熔断器关闭(
CLOSED
)时,所有的请求都会通过熔断器。 - 如果失败率超过设定的阈值,熔断器就会从关闭状态转换到打开(
OPEN
)状态,这时所有的请求都会被拒绝。 - 当经过一段时间后,熔断器会从打开状态转换到半开(
HALF_OPEN
)状态,这时仅有一定数量的请求会被放入,并重新计算失败率。如果失败率超过阈值,则变为打开(OPEN
)状态;如果失败率低于阈值,则变为关闭(CLOSE
)状态。
友情提示:下面会涉及到 CircuitBreaker 如何实现,可以稍微看看,简单理解。想要深入的胖友,可以阅读《Resilience4j 源码解析》中 CircuitBreaker 部分。
Resilience4j 记录请求状态的数据结构和 Hystrix 不同:Hystrix 是使用滑动窗口来进行存储的,而 Resilience4j 采用的是 Ring Bit Buffer(环形缓冲区)。Ring Bit Buffer 在内部使用 BitSet 这样的数据结构来进行存储,结构如下图所示:
- 每一次请求的成功或失败状态只占用一个 bit 位,与
boolean
数组相比更节省内存。BitSet 使用long[]
数组来存储这些数据,意味着 16 个值(64 bit)的数组可以存储 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
(始终拒绝访问)。这两个状态不会生成熔断器事件(除状态装换外),并且不会记录事件的成功或者失败。退出这两个状态的唯一方法是触发状态转换或者重置熔断器。
本小节,我们来搭建一个 Feign 的快速入门示例。步骤如下:
- 首先,使用 SpringMVC 搭建一个用户服务,提供 JSON 数据格式的 HTTP API。
- 然后,搭建一个使用 Feign 声明式调用用户服务 HTTP API 的示例项目。
本小节,我们来搭建一个 Resilience4j CircuitBreaker 的快速入门示例。步骤如下:
- 首先,搭建一个
user-service
用户服务,提供获取用户信息的 HTTP API 接口。 - 然后,搭建一个用户服务的消费者,使用 Resilience4j 实现服务容错。
2.1 搭建用户服务
创建 lab-59-user-service
项目,搭建用户服务。代码如下图所示:
比较简单,主要是提供 http://127.0.0.1:18080/user/get 接口,获取用户详情。具体的代码,肯定不用艿艿啰嗦讲解哈,点击 lab-59-user-service
查看。
2.2 搭建 Resilience4j 示例项目
创建 lab-59-resilience4j-demo01
项目,搭建一个用户服务的消费者,使用 Resilience4j 实现服务容错。代码如下图所示:
2.2.1 引入依赖
创建 pom.xml
文件,引入 Resilience4j 相关依赖。
"1.0" encoding="UTF-8" xml version= |
① 添加 resilience4j-spring-boot2
依赖,引入 Resilience4j Starter 相关依赖,并实现对其的自动配置。
② 添加 aspectjrt
和 aspectjweaver
依赖,引入 Aspectj 依赖,支持 AOP 相关注解、表达式等等。因为 Resilience4j 实现其功能,可以通过注解 + AOP的组合,而 AOP 的功能是基于 Aspectj 工具库来实现的。
2.2.2 配置文件
创建 application.yml
配置文件,添加 Resilience4j CircuitBreaker 相关配置项。
resilience4j: |
① resilience4j.circuitbreaker
是 Resilience4j 的熔断器配置项,对应 CircuitBreakerProperties 属性类。
② 在 resilience4j.circuitbreaker.instances
配置项下,可以添加熔断器实例的配置,其中 key 为熔断器实例的名字,value 为熔断器实例的具体配置,对应 CircuitBreakerConfigurationProperties.InstanceProperties 类。
这里,我们创建了一个实例名为 "backendA"
的熔断器,具体的每个项,胖友看看艿艿添加在后面的注释哈。
③ 在 resilience4j.circuitbreaker.configs
配置项下,可以添加通用配置项,提供给 resilience4j.circuitbreaker.instances
熔断器使用。示例如下图:
2.2.3 DemoController
创建 DemoController 类,提供调用用户服务的 HTTP API 接口。代码如下:
|
① 在 #getUser(Integer id)
方法中,我们使用 RestTemplate 调用用户服务提供的 /user/get
接口,获取用户详情。
② 在 #getUser(Integer id)
方法上,添加了 Resilience4j 提供的 @CircuitBreaker
注解:
- 通过
name
属性,设置对应的 CircuitBreaker 熔断器实例名为"backendA"
,就是我们在「2.2.2 配置文件」中所添加的。 - 通过
fallbackMethod
属性,设置执行发生 Exception 异常时,执行对应的#getUserFallback(Integer id, Throwable throwable)
方法。注意,fallbackMethod
方法的参数要和原始方法一致,最后一个为 Throwable 异常。
通过不同的 Throwable 异常,我们可以进行不同的 fallback 降级处理。极端情况下,Resilience4j 熔断器打开时,不会执行 #getUser(Integer id)
方法,而是直接抛出 CallNotPermittedException 异常,然后也是进入 fallback 降级处理。
2.2.4 DemoApplication
创建 DemoApplication 类,作为 Resilience4j 示例项目的启动类。代码如下:
|
2.3 简单测试
执行 UserServiceApplication 启动用户服务,执行 DemoApplication 启动 Resilience4j 示例项目。
① 使用浏览器,访问 http://127.0.0.1:8080/demo/get_user?id=1 地址,成功调用用户服务,返回结果为 User:1
。
② 停止 UserServiceApplication 关闭用户服务。
使用浏览器,访问 http://127.0.0.1:8080/demo/get_user?id=1 地址,失败调用用户服务,返回结果为 mock:User:1
。
此时我们会看到如下日志,可以判断触发 Resilience4j 的 fallback 服务降级的方法。
2020-05-19 08:46:44.094 INFO 65728 --- [nio-8080-exec-1] c.i.s.l.r.controller.DemoController : [getUser][准备调用 user-service 获取用户(1)详情] |
③ 疯狂使用浏览器,访问 http://127.0.0.1:8080/demo/get_user?id=1 地址,会触发 Hystrix 熔断器熔断(打开),不再执行 #getUser(Integer id)
方法,而是直接 fallback 触发 #getUserFallback(Integer id, Throwable throwable)
方法。日志内容如下:
2020-05-19 08:47:03.107 INFO 65728 --- [nio-8080-exec-7] c.i.s.l.r.controller.DemoController : [getUserFallback][id(1) exception(CallNotPermittedException)] |
④ 重新执行 UserServiceApplication 启动用户服务。
使用浏览器,多次访问 http://127.0.0.1:8080/demo/get_user?id=1 地址,Hystrix 熔断器的状态逐步从打开 =>
半开 =>
关闭。日志内容如下:
// 打开 |
3. RateLimiter 限流器
示例代码对应仓库:
- Resilience4j 示例项目:
lab-59-resilience4j-demo01
RateLimiter 一共有两种实现类:
- AtomicRateLimiter:基于令牌桶限流算法实现限流。
- SemaphoreBasedRateLimiter:基于 Semaphore 实现限流。
默认情况下,采用 AtomicRateLimiter 基于令牌桶限流算法实现限流。= = 搜了一圈 Resilience4j 的源码,貌似 SemaphoreBasedRateLimiter 没有地方在使用,难道已经被抛弃了~
FROM 《接口限流实践》
令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。
下面,我们直接在「2. 快速入门」小节的基础上,直接增加 Resilience4j 限流器相关的示例代码。改动点如下图所示:
3.1 配置文件
修改 application.yml
配置文件,添加 Resilience4j RateLimiter 相关配置项。
resilience4j: |
① resilience4j.ratelimiter
是 Resilience4j 的 RateLimiter 配置项,对应 RateLimiterConfigurationProperties 属性类。
② 在 resilience4j.ratelimiter.instances
配置项下,可以添加限流器实例的配置,其中 key 为限流器实例的名字,value 为限流器实例的具体配置,对应 RateLimiterConfigurationProperties.InstanceProperties 类。
这里,我们创建了一个实例名为 "backendB"
的限流器,具体的每个项,胖友看看艿艿添加在后面的注释哈。有一点要注意,在请求被限流时,并不是直接失败抛出异常,而是阻塞等待最大 timeout-duration
微秒,看看是否能够请求通过。
③ 在 resilience4j.ratelimiter.configs
配置项下,可以添加通用配置项,提供给 resilience4j.ratelimiter.instances
限流器使用。示例如下图:
3.2 RateLimiterDemoController
创建 RateLimiterDemoController 类,提供示例 HTTP API 接口。代码如下:
|
① 在 #getUser(Integer id)
方法中,我们直接返回 "User:{id}"
,不进行任何逻辑。
② 在 #getUser(Integer id)
方法上,添加了 Resilience4j 提供的 @RateLimiter
注解:
- 通过
name
属性,设置对应的 RateLimiter 实例名为"backendB"
,就是我们在「3.1 配置文件」中所添加的。 - 通过
fallbackMethod
属性,设置执行发生 Exception 异常时,执行对应的#getUserFallback(Integer id, Throwable throwable)
方法。注意,fallbackMethod
方法的参数要和原始方法一致,最后一个为 Throwable 异常。
在请求被限流时,Resilience4j 不会执行 #getUser(Integer id)
方法,而是直接抛出 RequestNotPermitted 异常,然后就进入 fallback 降级处理。
友情提示:注意,
@RateLimiter
注解的fallbackMethod
属性对应的 fallback 方法,不仅仅处理被限流时抛出的 RequestNotPermitted 异常,也处理#getUser(Integer id)
方法执行时的普通异常。
3.3 简单测试
执行 DemoApplication 启动 Resilience4j 示例项目。
① 使用浏览器,访问 http://127.0.0.1:8080/rate-limiter-demo/get_user?id=1 地址,成功返回结果为 User:1
。
② 立马使用浏览器再次访问,会阻塞等待 < 5
秒左右,降级返回 mock:User:1
。同时,我们在 IDEA 控制台的日志中,可以看到被限流时抛出的 RequestNotPermitted 异常。
2020-05-19 21:50:42.585 INFO 79815 --- [nio-8080-exec-1] c.i.s.l.r.c.RateLimiterDemoController : [getUserFallback][id(1) exception(RequestNotPermitted)] |
另外,我们将 @RateLimiter
和 @CircuitBreaker
注解添加在相同方法上,进行组合使用,来实现限流和断路的作用。但是要注意,需要添加 resilience4j.circuitbreaker.instances.<instance_name>.ignoreExceptions=io.github.resilience4j.ratelimiter.RequestNotPermitted
配置项,忽略限流抛出的 RequestNotPermitted 异常,避免触发断路器的熔断。
4. Bulkhead 舱壁
示例代码对应仓库:
- Resilience4j 示例项目:
lab-59-resilience4j-demo01
Bulkhead 指的是船舶中的舱壁,它将船体分隔为多个船舱,在船部分受损时可避免沉船。
在 Resilience4j 中,提供了基于 Semaphore 信号量和 ThreadPool 线程池两种 Bulkhead 实现,隔离不同种类的调用,并提供流控的能力,从而避免某类调用异常时而占用所有资源,导致影响整个系统。
下面,我们直接在「2. 快速入门」小节的基础上,直接增加 Resilience4j 舱壁相关的示例代码。改动点如下图所示:
良心的艿艿,两种类型的 Bulkhead 使用示例都提供了噢。
友情提示:我们先来看信号量类型的 Bulkhead 示例。
友情提示:我们先来看信号量类型的 Bulkhead 示例。
友情提示:我们先来看信号量类型的 Bulkhead 示例。
4.1 配置文件
修改 application.yml
配置文件,添加 Resilience4j 信号量类型的 Bulkhead 相关配置项。
resilience4j: |
① resilience4j.bulkhead
是 Resilience4j 的信号量 Bulkhead 配置项,对应 BulkheadProperties 属性类。
② 在 resilience4j.bulkhead.instances
配置项下,可以添加 Bulkhead 实例的配置,其中 key 为 Bulkhead 实例的名字,value 为 Bulkhead 实例的具体配置,对应 BulkheadProperties.InstanceProperties 类。
这里,我们创建了一个实例名为 "backendC"
的 Bulkhead,具体的每个项,胖友看看艿艿添加在后面的注释哈。有一点要注意,在请求被流控时,并不是直接失败抛出异常,而是阻塞等待最大 max-wait-duration
微秒,看看是否能够请求通过。
友情提示:这里我们设置
max-concurrent-calls
配置项为 1,仅允许并发调用数为 1,方便测试。
③ 在 resilience4j.bulkhead.configs
配置项下,可以添加通用配置项,提供给 resilience4j.bulkhead.instances
Bulkhead 使用。示例如下图:
4.2 BulkheadDemoController
创建 BulkheadDemoController 类,提供示例 HTTP API 接口。代码如下:
|
① 在 #getUser(Integer id)
方法中,我们直接返回 "User:{id}"
,不进行任何逻辑。不过,这里为了模拟调用执行一定时长,通过 sleep 10 秒来实现。
② 在 #getUser(Integer id)
方法上,添加了 Resilience4j 提供的 @Bulkhead
注解:
- 通过
name
属性,设置对应的 Bulkhead 实例名为"backendC"
,就是我们在「4.1 配置文件」中所添加的。 - 通过
type
属性,设置 Bulkhead 类型为信号量的方式。 - 通过
fallbackMethod
属性,设置执行发生 Exception 异常时,执行对应的#getUserFallback(Integer id, Throwable throwable)
方法。注意,fallbackMethod
方法的参数要和原始方法一致,最后一个为 Throwable 异常。
在请求被流控时,Resilience4j 不会执行 #getUser(Integer id)
方法,而是直接抛出 BulkheadFullException 异常,然后就进入 fallback 降级处理。
友情提示:注意,
@Bulkhead
注解的fallbackMethod
属性对应的 fallback 方法,不仅仅处理被流控时抛出的 BulkheadFullException 异常,也处理#getUser(Integer id)
方法执行时的普通异常。
4.3 简单测试
执行 DemoApplication 启动 Resilience4j 示例项目。
① 使用浏览器,访问 http://127.0.0.1:8080/bulkhead-demo/get_user?id=1 地址,成功返回结果为 User:1
。
② 立马使用浏览器再次访问,会阻塞等待 < 5
秒左右,降级返回 mock:User:1
。同时,我们在 IDEA 控制台的日志中,可以看到被流控时抛出的 BulkheadFullException 异常。
2020-05-20 07:39:17.181 INFO 84230 --- [nio-8080-exec-2] c.i.s.l.r.c.BulkheadDemoController : [getUserFallback][id(1) exception(BulkheadFullException)] |
友情提示:我们先来看线程池类型的 Bulkhead 示例。
友情提示:我们先来看线程池类型的 Bulkhead 示例。
友情提示:我们先来看线程池类型的 Bulkhead 示例。
4.4 配置文件
修改 application.yml
配置文件,添加 Resilience4j 信号量类型的 Bulkhead 相关配置项。
resilience4j: |
① resilience4j.thread-pool-bulkhead
是 Resilience4j 的线程池 Bulkhead 配置项,对应 ThreadPoolBulkheadProperties 属性类。
② 在 resilience4j.thread-pool-bulkhead.instances
配置项下,可以添加 Bulkhead 实例的配置,其中 key 为 Bulkhead 实例的名字,value 为 Bulkhead 实例的具体配置,对应 ThreadPoolBulkheadProperties.InstanceProperties 类。
这里,我们创建了一个实例名为 "backendD"
的 Bulkhead,具体的每个项,胖友看看艿艿添加在后面的注释哈。
友情提示:这里我们设置
max-thread-pool-size
配置项为 1,仅允许并发调用数为 1,方便测试。
③ 在 resilience4j.thread-pool-bulkhead.configs
配置项下,可以添加通用配置项,提供给 resilience4j.thread-pool-bulkhead.instances
Bulkhead 使用。示例如下图:
4.5 ThreadPoolBulkheadDemoController
创建 ThreadPoolBulkheadDemoController 类,提供示例 HTTP API 接口。代码如下:
|
友情提示:这里创建了 ThreadPoolBulkheadService 的原因是,这里我们使用 Resilience4j 是基于注解 + AOP的方式,如果直接
this.
方式来调用方法,实际没有走代理,导致 Resilience4j 无法使用 AOP。
① 在 #getUser(Integer id)
方法中,我们调用了 2 次 ThreadPoolBulkheadService 的 #getUser0(Integer id)
方法,测试在线程池 Bulkhead 下,且线程池大小为 1 时,被流控成“串行”执行。
② 在 #getUser0(Integer id)
方法上,添加了 Resilience4j 提供的 @Bulkhead
注解:
- 通过
name
属性,设置对应的 Bulkhead 实例名为"backendC"
,就是我们在「4.4 配置文件」中所添加的。 - 通过
type
属性,设置 Bulkhead 类型为线程池的方式。 - 通过
fallbackMethod
属性,设置执行发生 Exception 异常时,执行对应的#getUserFallback(Integer id, Throwable throwable)
方法。注意,fallbackMethod
方法的参数要和原始方法一致,最后一个为 Throwable 异常。 - 注意!!!方法的返回类型必须是 CompletableFuture 类型,包括 fallback 方法,否则会报异常,毕竟要提交线程池中执行。
在请求被流控时,Resilience4j 不会执行 #getUser0(Integer id)
方法,而是直接抛出 BulkheadFullException 异常,然后就进入 fallback 降级处理。不过艿艿测试了很久,都没触发抛出 BulkheadFullException 异常的情况,但是看 Resilience4j 源码又有这块逻辑,苦闷~
友情提示:注意,
@Bulkhead
注解的fallbackMethod
属性对应的 fallback 方法,不仅仅处理被流控时抛出的 BulkheadFullException 异常,也处理#getUser0(Integer id)
方法执行时的普通异常。
4.6 简单测试
执行 DemoApplication 启动 Resilience4j 示例项目。
调用 http://127.0.0.1:8080/thread-pool-bulkhead-demo/get_user?id=1 接口,可以通过日志看出,ThreadPoolBulkheadService 的 #getUser0(Integer id)
方法是“串行”执行的:
2020-05-20 8:25:24.059 INFO 85835 --- [head-backendD-1] DemoController$ThreadPoolBulkheadService : [getUser][id(1)] |
相互之间间隔 10 秒,因为我们在 #getUser0(Integer id)
方法是 sleep 了 10 秒。
5. Retry 重试
示例代码对应仓库:
- Resilience4j 示例项目:
lab-59-resilience4j-demo01
Resilience4j 提供了 Retry 组件,在执行失败时,进行重试的行为。
下面,我们直接在「2. 快速入门」小节的基础上,直接增加 Resilience4j 重试相关的示例代码。改动点如下图所示:
5.1 配置文件
修改 application.yml
配置文件,添加 Resilience4j Retry 相关配置项。
resilience4j: |
① resilience4j.retry
是 Resilience4j 的 Retry 配置项,对应 RetryProperties 属性类。
② 在 resilience4j.retry.instances
配置项下,可以添加 Retry 实例的配置,其中 key 为 Retry 实例的名字,value 为 Retry 实例的具体配置,对应 RetryProperties.InstanceProperties 类。
这里,我们创建了一个实例名为 "backendE"
的 Retry,具体的每个项,胖友看看艿艿添加在后面的注释哈。
③ 在 resilience4j.retry.configs
配置项下,可以添加通用配置项,提供给 resilience4j.retry.instances
Retry 使用。示例如下图:
5.2 RetryDemoController
创建 RetryDemoController 类,提供调用用户服务的 HTTP API 接口。代码如下:
|
① 在 #getUser(Integer id)
方法中,我们使用 RestTemplate 调用用户服务提供的 /user/get
接口,获取用户详情。
② 在 #getUser(Integer id)
方法上,添加了 Resilience4j 提供的 @Retry
注解:
- 通过
name
属性,设置对应的 Retry 实例名为"backendE"
,就是我们在「5.1 配置文件」中所添加的。 - 通过
fallbackMethod
属性,设置执行发生 Exception 异常时,执行对应的#getUserFallback(Integer id, Throwable throwable)
方法。注意,fallbackMethod
方法的参数要和原始方法一致,最后一个为 Throwable 异常。
在多次重试失败到达上限时,Resilience4j 会抛出 MaxRetriesExceeded 异常,然后就进入 fallback 降级处理。
5.3 简单测试
执行 DemoApplication 启动 Resilience4j 示例项目。
调用 http://127.0.0.1:8080/retry-demo/get_user?id=1 接口,可以通过日志看出,一共执行 + 重试共 3 次,每次间隔 5 秒:
// 执行 + 重试共 3 次 |
6. TimeLimiter 限时器
示例代码对应仓库:
- Resilience4j 示例项目:
lab-59-resilience4j-demo01
Resilience4j 提供了 TimeLimiter 组件,限制任务的执行时长,在超过时抛出异常。
下面,我们直接在「2. 快速入门」小节的基础上,直接增加 Resilience4j 限时器相关的示例代码。改动点如下图所示:
6.1 配置文件
修改 application.yml
配置文件,添加 Resilience4j TimeLimiter 相关配置项。
resilience4j: |
友情提示:因为 TimeLimiter 需要搭配线程池类型的 Bulkhead,所以这里添加了
resilience4j.thread-pool-bulkhead
配置项,因为 TimeLimiter 是基于线程池来实现超时限制的。
① resilience4j.timelimiter
是 Resilience4j 的 TimeLimiter 配置项,对应 TimeLimiterProperties 属性类。
② 在 resilience4j.timelimiter.instances
配置项下,可以添加 Retry 实例的配置,其中 key 为 TimeLimiter 实例的名字,value 为 TimeLimiter 实例的具体配置,对应 TimeLimiterConfigurationProperties.InstanceProperties 类。
这里,我们创建了一个实例名为 "backendF"
的 TimeLimiter,具体的每个项,胖友看看艿艿添加在后面的注释哈。
③ 在 resilience4j.timelimiter.configs
配置项下,可以添加通用配置项,提供给 resilience4j.timelimiter.instances
Retry 使用。暂无示例,和其它配置项是类似的。
6.2 TimeLimiterDemoController
创建 TimeLimiterDemoController 类,提供示例 HTTP API 接口。代码如下:
|
友情提示:这里创建了 TimeLimiterService 的原因是,这里我们使用 Resilience4j 是基于注解 + AOP的方式,如果直接
this.
方式来调用方法,实际没有走代理,导致 Resilience4j 无法使用 AOP。
① 在 #getUser(Integer id)
方法中,直接调用 ThreadPoolBulkheadService 的 #getUser0(Integer id)
方法进行返回。
② 在 #getUser0(Integer id)
方法上,添加了 Resilience4j 提供的 @TimeLimiter
注解:
- 通过
name
属性,设置对应的 Bulkhead 实例名为"backendC"
,就是我们在「4.4 配置文件」中所添加的。 - 通过
fallbackMethod
属性,设置执行发生 Exception 异常时,执行对应的#getUserFallback(Integer id, Throwable throwable)
方法。注意,fallbackMethod
方法的参数要和原始方法一致,最后一个为 Throwable 异常。 - 注意!!!方法的返回类型必须是 CompletableFuture 类型,包括 fallback 方法,否则会报异常,毕竟要提交线程池中执行。
在请求执行超时时,Resilience4j 会抛出 TimeoutException 异常,然后就进入 fallback 降级处理。
友情提示:注意,
@Bulkhead
注解的fallbackMethod
属性对应的 fallback 方法,不仅仅处理被流控时抛出的 BulkheadFullException 异常,也处理#getUser0(Integer id)
方法执行时的普通异常。
③ 在 #getUser0(Integer id)
方法上,添加了 Resilience4j 提供的 @Bulkhead
注解,并设置类型为线程池,原因上文我们也解释过了。
6.3 简单测试
执行 DemoApplication 启动 Resilience4j 示例项目。
调用 http://127.0.0.1:8080/time-limiter-demo/get_user?id=1 接口,可以通过日志看出,看到执行超时抛出 TimeoutException 异常,而后进入 fallback 服务降级,因此最终返回结果为 mock:User:1
。
2020-05-20 23:35:50.633 INFO 93020 --- [head-backendD-1] LimiterDemoController$TimeLimiterService : [getUser][id(1)] |
7. 执行顺序
我们在相同方法上,添加上述 Resilience4j 的注解,从而组合使用熔断、限流、舱壁、重试、重试的功能。例如说:
"fallback") (name = BACKEND, fallbackMethod = |
此时,我们就要注意它们的执行顺序是 :
Retry > Bulkhead > RateLimiter > TimeLimiter > Bulkhead |
想要深入的胖友,可以进一步看看每个注解对应的切面实现,整理如下表格:
注解 | 切面 | 顺序 |
---|---|---|
@Retry |
RetryAspect | Ordered.LOWEST_PRECEDENCE - 4 |
@CircuitBreaker |
CircuitBreakerAspect | Ordered.LOWEST_PRECEDENCE - 3 |
@RateLimiter |
RateLimiterAspect | Ordered.LOWEST_PRECEDENCE - 2 |
@TimeLimiter |
TimeLimiterAspect | Ordered.LOWEST_PRECEDENCE - 1 |
@Bulkhead |
BulkheadAspect | Ordered.LOWEST_PRECEDENCE |
当然,我们也可以通过 **-order
配置项,进行自定义执行顺序。修改方式如下图:
8. 监控端点
resilience4j-spring-boot2
基于 Spring Boot Actuator,提供了 Resilience4j 自定义监控端点,如下图所示:
主要分成三种类型:
- Metrics endpoint:纯 Endpoint 结尾。
- Health endpoint:HealthIndicator 结尾。
- Events endpoint:EventsEndpoint 结尾。
友情提示:对 Spring Boot Actuator 不了解的胖友,可以后续阅读《芋道 Spring Boot 监控端点 Actuator 入门》文章。
下面,我们从上述小节的 lab-59-resilience4j-demo01
项目,复制出 lab-59-resilience4j-actuator
项目,接入 Spring Boot Actuator,实现监控相关功能。改动点如下图:
8.1 引入依赖
在 pom.xml
文件中,额外引入 Spring Boot Actuator 相关依赖。代码如下:
<!-- 实现对 Actuator 的自动化配置 --> |
8.2 配置文件
修改 application.yaml
配置文件,增加 Spring Boot Actuator 配置项。配置如下:
management: |
① 关于 Actuator 的配置项的作用,胖友看下艿艿添加的注释。如果还不理解的话,后续看下《芋道 Spring Boot 监控端点 Actuator 入门》文章。
② 默认情况下,Resilience4j 的 CircuitBreakersHealthIndicator 和 RateLimitersHealthIndicator 是关闭的,所以需要设置 management.health.circuitbreakers.enabled
和 management.health.ratelimiters.enabled
配置项为 true
。
同时,需要设置对应的 CircuitBreaker 和 RateLimiter 注册到 HealthIndicator 中。如下图所示:
下面,我们执行 DemoApplication 启动项目。
8.3 Metrics endpoint
对应 《Resilience4j Metrics endpoint》 文档。
① 访问 http://127.0.0.1:8080/actuator/metrics 端点,可以看到 Resilience4j 提供的所有 Metrics 监控指标。如下图所示:
② 访问 http://127.0.0.1:8080/actuator/metrics/resilience4j.bulkhead.core.thread.pool.size 地址,查看 resilience4j.bulkhead.core.thread.pool.size
监控指标,返回结果如下:
{ |
③ 一般情况下,我们可以通过 Prometheus 采集 Resilience4j 提供的 Metrics 监控数据,然后使用 Grafana 制作监控仪表盘。
友情提示:对 Prometheus 和 Grafana 不了解的胖友,可以阅读《芋道 Prometheus + Grafana + Alertmanager 极简入门》文章。
同时,官方已经提供了 Grafana 的集成示范,可见 grafana_dashboard.json
。另外,这里在推荐两篇外国小哥的文章:
- 《Creating Fault Tolerant Services Using Resilience4j》
- 《Resilience for Java microservices. Circuit Breaker with Resilience4j》
8.4 Health endpoint
对应 《Resilience4j Health endpoint》 文档。
访问 http://127.0.0.1:8080/actuator/health 端点,可以看到 CircuitBreaker 和 RateLimiter 的健康状态。结果如下:
{ |
8.5 Events endpoint
对应 《Resilience4j Events endpoint》 文档。
① 访问 http://127.0.0.1:8080/actuator/ 接口,可以看到每个类型的 Resilience4j 组件,都有自己的 Events endpoint。如下图所示:
② 我们来测试下 CircuitBreaker 的 Event 事件。
首先,请求 http://127.0.0.1:8080/demo/get_user?id=1 接口,触发一次 fallback 降级,因为我们没有启动用户服务,所以调用显然会失败。
然后,请求 http://127.0.0.1:8080/actuator/circuitbreakerevents 地址,查看 CircuitBreaker 的 Event 事件。返回结果如下:
{ |
9. 集成到 Feign
resilience4j-feign
提供 Resilience4j 针对 Feign 的集成。
比较简单,胖友先自己看看《Resilience4j 官方文档 —— Feign》。
10. 集成到 Dubbo
Resilience4j 暂未提供对 Dubbo 的集成。不过目前 Dubbo 已经提供了两种接入 Resilience4j 实现服务容错的方案:
dubbo-samples-resilience4j-filter
项目:通过 Dubbo Filter 拓展点,实现 Resilience4jCircuitBreakerFilter 接入 CircuitBreaker 熔断器,Resilience4jRateLimiterFilter 接入 RateLimiter 限流器。dubbo-samples-resilience4j-springboot2
项目:在调用时,进行一层包装,然后添加 Resilience4j 的注解。例如说 CircuitBreakMethodWrapper 或 CircuitBreakTypeWrapper,如下图所示:
代码不复杂,胖友自己瞅瞅很快就明白了。
666. 彩蛋
至此,我们已经完成了 Resilience4j 的入门。总的来说,因为Resilience4j 几个组件的拆分非常干净,所以理解起来还是蛮轻松的。
后续,胖友可以自己在看看《Resilience4j 官方文档》,进行下查漏补缺。也推荐阅读如下两篇文章:
另外,也超级推荐阅读下《限流熔断技术选型:从 Hystrix 到 Sentinel》文章,选型上的思考,特别是这张图,好评到不行啊!