使用Resilience4j保护实现容错-限流
- Resilience4j快速使用
- 断路器 Circuit breaking
- 限流
- 仓壁模式
- 重试
- 注解配合使用与执行顺序
- Resilience4j配置管理
- 配置可视化
- 默认配置
- 配置共享
- 动态配置
- Feign与Resilience4j
Resilience4j快速使用
在微服务中,经常会出现一些故障,而一些故障会直接或者间接的拖垮其它的服务,造成服务器雪崩,系统就会死掉。
假如现在有很多的用户同时请求订单微服务去执行下单的操作,那么会调用我们的支付微服务,如果支付微服务现在挂掉了,而订单调用一直没有响应,由于很多的用户执行相同的操作,属于高并发,那么服务器上积累的订单越来越多,那么原来没有问题的订单微服务,也会被拖垮,这就是服务雪崩。
我们需要做的就是,当某一个微服务发生蔓延当时候,不能发生故障蔓延,整个系统还能以其它某种方式正常运行,这个就是我们需要解决的。
Resilience4j是一个轻量级的容错框架,灵感来自于Netflix的Hystrix
提供了如下几款核心组件:
- resilience4j-circuitbreaker: Circuit breaking
- resilience4j-ratelimiter: Rate limiting
- resilience4j-bulkhead: Bulkheading
- resilience4j-retry: Automatic retrying (sync and async)
- resilience4j-cache: Response caching
pom依赖
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-cloud2</artifactId>
<version>1.1.0</version>
</dependency>
断路器 Circuit breaking
@CircuitBreaker(name = "detail")
@GetMapping("detail")
public HttpResponseBody detail(@RequestParam("id") Long id){
return HttpResponseBody.successResponse("ok",productServiceClient.detail(id));
}
断路器相关配置
限流
- 使用 @RateLimiter注解,指定限流的方法
@RateLimiter(name = "detail")
@GetMapping("detail")
public HttpResponseBody detail(@RequestParam("id") Long id){
return HttpResponseBody.successResponse("ok",productServiceClient.detail(id));
}
- 添加限流配置,一下配置代表一秒内只允许一次请求
resilience4j:
ratelimiter:
instances:
# 与注解内的name相同
detail:
#在刷新周期内,请求最大频次
limitForPeriod: 1
#刷新周期
limitRefreshPeriod: 1s
#线程等待许可的时间
timeoutDuration: 0
如果不想返回错误可以这样写
@RateLimiter(name = "detail",fallbackMethod = "detailFallBack")
@GetMapping("detail")
public HttpResponseBody detail(@RequestParam("id") Long id){
return HttpResponseBody.successResponse("ok",productServiceClient.detail(id));
}
public HttpResponseBody detailFallBack(Long id,Throwable throwable){
return HttpResponseBody.failResponse("服务已经限流");
}
限流相关配置
仓壁模式
@GetMapping("detail")
@Bulkhead(name = "detail",fallbackMethod = "detailFallBack")
public HttpResponseBody detail(@RequestParam("id") Long id) throws InterruptedException {
Thread.sleep(2000L);
return HttpResponseBody.successResponse("ok",productServiceClient.detail(id));
}
配置文件
resilience4j:
bulkhead:
instances:
detail:
#最大并发请求,默认25
maxConcurrentCalls: 1
仓壁模式有两种实现方式
- 基于Semaphore(默认)
- 基于ThreadPool
@GetMapping("detail")
@Bulkhead(name = "detail",type = Bulkhead.Type.THREADPOOL,fallbackMethod = "detailFallBack")
public HttpResponseBody detail(@RequestParam("id") Long id) throws InterruptedException {
Thread.sleep(2000L);
return HttpResponseBody.successResponse("ok",productServiceClient.detail(id));
}
重试
@GetMapping("detail")
@Retry(name = "detail")
public HttpResponseBody detail(@RequestParam("id") Long id) throws InterruptedException {
Thread.sleep(2000L);
return HttpResponseBody.successResponse("ok",productServiceClient.detail(id));
}
注解配合使用与执行顺序
如果一个方法上使用了多个注解,那么他们的执行顺序是怎样的呢?
- 打开注解
- 找到具体的处理类
- 类最后有个方法getOrder
@Override
public int getOrder() {
return properties.getRateLimiterAspectOrder();
}
值越小优先级越高
- BulkHead(LOWEST_PRECEDENCE)
- RateLimiter(LOWEST_PRECEDENCE - 1)
- CircuitBreaker(LOWEST_PRECEDENCE-2)
- Retry(LOWEST_PRECEDENCE - 3)
Resilience4j配置管理
配置可视化
以限流为例,通过这段代码可以查看所有限流的配置,方便排查问题
@Autowired
private RateLimiterRegistry rateLimiterRegistry;
@RequestMapping("config")
public Seq<RateLimiter> test(){
return rateLimiterRegistry.getAllRateLimiters();
}
默认配置
对于RateLimiter,仅当指定名称的RateLimiter没有任何自定义配置时,名为default的配置才有效
resilience4j:
ratelimiter:
configs:
default:
#在刷新周期内,请求最大频次
limitForPeriod: 10
#刷新周期
limitRefreshPeriod: 1s
#线程等待许可的时间
timeoutDuration: 0
默认配置是懒加载的, 只有请求限流的接口,才会生效
第一次请求
请求了限流接口后的配置
配置共享
Resilience4j的配置可以说是非常灵活的,当有多个实例配置限流时,可通过baseConfig引用相同配置
resilience4j:
ratelimiter:
instances:
detail:
#刷新周期
limitRefreshPeriod: 1s
baseConfig: share
configs:
share:
limitForPeriod: 2
default:
#在刷新周期内,请求最大频次
limitForPeriod: 10
#刷新周期
limitRefreshPeriod: 1s
动态配置
可以通过配置中心实现动态配置,如放到consule、nacos等等这里就不在演示
Feign与Resilience4j
使用方式跟上面说的没多大区别,唯独fallbackMethod略有不同,这里要使用default是标识方法
Logger log = LoggerFactory.getLogger(ProductServiceClient.class);
@RateLimiter(name = "detail",fallbackMethod = "detail")
@RequestMapping(value = "/detailById", method = RequestMethod.GET)
KillGoodsSpecPriceDetailVo detail(@RequestParam("id") Long id);
default KillGoodsSpecPriceDetailVo detail( Long id,Throwable throwable){
log.error("发生fallback",throwable);
return new KillGoodsSpecPriceDetailVo();
}