4-SpringCloud-Resilience4J服务熔断与降级
1.4 Circuit Breaker 断路器之 Resilience4J
Circuit Breaker 断路器是一套规范、接口。其主要通过 Resilience4J 实现断路器、舱壁、限流等功能。由 Resilience4J 替代 Hystrix(豪猪)这一款产品。
在分布式体系结构中,存在着数十种依赖关系,然而这些关系不可避免的会产生故障。如这些依赖关系就可能造成雪崩故障,又称级联故障。而为解决这一故障,我们引入了断路器 Circuit Breaker。
服务雪崩
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。
**问题:**禁止服务雪崩故障
解决: 有问题的节点,快速熔断(快速返回失败处理或者返回默认兜底数据【服务降级】)。“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。一句话,出故障了“保险丝”跳闸,别把整个家给烧了,😄。
为了解决雪崩故障导致系统大面积故障,Spring Cloud Circuit Breaker 提供了如下几个方案。
-
服务熔断:类似保险丝限电功能,当服务达到最大访问时,断路器由原来的 Closed 状态变为 Open 状态,这时调用方会接受服务降级处理并返回友好兜底提示。
-
服务降级:服务器繁忙,请稍后重试。不让客户长时间等待立即返回一个友好提示 fallback。如以下类似情况。
-
服务限流:秒杀高并发等操作,严禁一窝蜂的过来。限制每个时间周期内的访问数量,多余访问需要等待,如一秒钟 N 个。
-
服务显示
-
服务预热
-
接近实时的监控
-
兜底的处理动作
1.4.1 Circuit Breake 概述
Circuit Breaker 其实是一套规范、接口,其落地实现者是 Resilience4J 。
CircuitBreaker 的目的是保护分布式系统免受故障和异常,提高系统的可用性和健壮性。
当一个组件或服务出现故障时,CircuitBreaker 会迅速切换到开放OPEN状态(保险丝跳闸断电),阻止请求发送到该组件或服务从而避免更多的请求发送到该组件或服务。这可以减少对该组件或服务的负载,防止该组件或服务进一步崩溃,并使整个系统能够继续正常运行。同时,CircuitBreaker 还可以提高系统的可用性和健壮性,因为它可以在分布式系统的各个组件之间自动切换,从而避免单点故障的问题。具体信息可参考网址https://docs.spring.io/spring-cloud-circuitbreaker/reference/index.html,https://resilience4j.readme.io/docs/circuitbreaker。
1.4.2 Resilience4J
Resilience4j提供了提供了一组高阶函数(装饰器),包括断路器,限流器,重试机制,隔离机制。你可以使用其中的一个或多个装饰器对函数式接口,lambda表达式或方法引用进行装饰。这么做的优点是你可以选择所需要的装饰器进行装饰。在使用Resilience4j的过程中,不需要引入所有的依赖,只引入需要的依赖即可。具体信息可参考官方网址https://github.com/resilience4j/resilience4j?tab=readme-ov-file#introduction,中文网址https://github.com/lmhmhl/Resilience4j-Guides-Chinese/blob/main/getting-start/Introduction.md。其中 Resilience4J 2 requires Java 17。
Resilience4J 主要功能:有断路器()、限流器()、舱壁()等,本文也只学习列举的主要功能。
可以通过以下学习网址学习 CircuitBreaker 的相关知识。
- https://resilience4j.readme.io/docs/circuitbreaker
- https://github.com/lmhmhl/Resilience4j-Guides-Chinese/blob/main/index.md
1.4.3 案例实战
1.4.3.1 服务熔断 CircuitBreaker
本案例主要用于演示服务熔断和服务降级功能,用于固定次数内请求失败的情况或固定时间周期内请求失败的情况。具体操作如下所示。
断路器3大状态之间的转换如下图所示。
断路器配置参数参考网址英文版https://resilience4j.readme.io/docs/circuitbreaker#create-and-configure-a-circuitbreaker,中文版https://github.com/lmhmhl/Resilience4j-Guides-Chinese/blob/main/core-modules/CircuitBreaker.md。
failure-rate-threshold | 以百分比配置失败率峰值 |
---|---|
sliding-window-type | 断路器的滑动窗口期类型 可以基于“次数”(COUNT_BASED)或者“时间”(TIME_BASED)进行熔断,默认是COUNT_BASED。 |
sliding-window-size | 若COUNT_BASED,则10次调用中有50%失败(即5次)打开熔断断路器;若为TIME_BASED则,此时还有额外的两个设置属性,含义为:在N秒内(sliding-window-size)100%(slow-call-rate-threshold)的请求超过N秒(slow-call-duration-threshold)打开断路器。 |
slowCallRateThreshold | 以百分比的方式配置,断路器把调用时间大于slowCallDurationThreshold的调用视为慢调用,当慢调用比例大于等于峰值时,断路器开启,并进入服务降级。 |
slowCallDurationThreshold | 配置调用时间的峰值,高于该峰值的视为慢调用。 |
permitted-number-of-calls-in-half-open-state | 运行断路器在HALF_OPEN状态下时进行N次调用,如果故障或慢速调用仍然高于阈值,断路器再次进入打开状态。 |
minimum-number-of-calls | 在每个滑动窗口期样本数,配置断路器计算错误率或者慢调用率的最小调用数。比如设置为5意味着,在计算故障率之前,必须至少调用5次。如果只记录了4次,即使4次都失败了,断路器也不会进入到打开状态。 |
wait-duration-in-open-state | 从OPEN到HALF_OPEN状态需要等待的时间 |
案例需求说明
6次访问中当执行方法的失败率达到50%时 CircuitBreaker 将进入开启 OPEN 状态(保险丝跳闸断电)拒绝所有请求。等待5秒后, CircuitBreaker 将自动从开启OPEN状态过渡到半开HALF_OPEN状态,允许一些请求通过以测试服务是否恢复正常。
如还是异常 CircuitBreaker 将重新进入开启OPEN状态;如正常将进入关闭CLOSE闭合状态恢复正常处理请求。*具体时间和频次等属性见具体实际案例,这里只是作为case举例讲解,最下面笔记面试题概览,闲聊大厂面试。
1.4.3.1.1 计数的滑动窗口 COUNT_BASED
- 在包 controller 的 cloud-provider-payment8001 中新添加一个类 PayCircuitController 类,该类中的内容如下。
package com.atguigu.controller;import cn.hutool.core.util.IdUtil;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;import java.util.concurrent.TimeUnit;@RestController
public class PayCircuitController {//=========Resilience4j CircuitBreaker 的例子@GetMapping(value = "/pay/circuit/{id}")public String myCircuit(@PathVariable("id") Integer id){if(id == -4) throw new RuntimeException("----circuit id 不能负数");if(id == 9999){try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }}return "Hello, circuit! inputId: "+id+" \t " + IdUtil.simpleUUID();}
}
-
在包 cloud-apis-commons 的 PayFeignApi 接口中增加以下抽象类方法。具体内容如下所示。
@GetMapping(value = "/pay/circuit/{id}")public String myCircuit(@PathVariable("id") Integer id);
-
修改包 cloud-consumer-feign-order80。具体操作如下所示。
-
修改 pom.xml 文件,增加如下依赖。
<!--resilience4j-circuitbreaker--> <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId> </dependency> <!-- 由于断路保护等需要AOP实现,所以必须导入AOP包 --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId> </dependency>
-
写 application.yml 文件,首先要开启 circuitbreaker 和分组激活,然后配置断路器时间的滑动窗口相关参数,具体内容如下所示。
spring:cloud:openfeign:circuitbreaker:enabled: truegroup:enabled: true #没开分组永远不用分组的配置。精确优先、分组次之(开了分组)、默认最后 # Resilience4j CircuitBreaker 按照次数:COUNT_BASED 的例子 # 6次访问中当执行方法的失败率达到50%时CircuitBreaker将进入开启OPEN状态(保险丝跳闸断电)拒绝所有请求。 # 等待5秒后,CircuitBreaker 将自动从开启OPEN状态过渡到半开HALF_OPEN状态,允许一些请求通过以测试服务是否恢复正常。 # 如还是异常CircuitBreaker 将重新进入开启OPEN状态;如正常将进入关闭CLOSE闭合状态恢复正常处理请求。 resilience4j:circuitbreaker:configs:default:failureRateThreshold: 50 #设置50%的调用失败时打开断路器,超过失败请求百分⽐CircuitBreaker变为OPEN状态。slidingWindowType: COUNT_BASED # 滑动窗口的类型slidingWindowSize: 6 #滑动窗⼝的⼤⼩配置COUNT_BASED表示6个请求,配置TIME_BASED表示6秒minimumNumberOfCalls: 6 #断路器计算失败率或慢调用率之前所需的最小样本(每个滑动窗口周期)。如果minimumNumberOfCalls为10,则必须最少记录10个样本,然后才能计算失败率。如果只记录了9次调用,即使所有9次调用都失败,断路器也不会开启。automaticTransitionFromOpenToHalfOpenEnabled: true # 是否启用自动从开启状态过渡到半开状态,默认值为true。如果启用,CircuitBreaker将自动从开启状态过渡到半开状态,并允许一些请求通过以测试服务是否恢复正常waitDurationInOpenState: 5s #从OPEN到HALF_OPEN状态需要等待的时间permittedNumberOfCallsInHalfOpenState: 2 #半开状态允许的最大请求数,默认值为10。在半开状态下,CircuitBreaker将允许最多permittedNumberOfCallsInHalfOpenState个请求通过,如果其中有任何一个请求失败,CircuitBreaker将重新进入开启状态。recordExceptions:- java.lang.Exceptioninstances:cloud-payment-service:baseConfig: default
-
新建 OrderCircuitController 类,并在改类当中实现断路器的服务熔断,添加兜底方法实现服务降级。通过注解
@CircuitBreaker(name="服务名称",fallbackMethod="兜底方法名")
将该业务方法 myCircuitBreaker 与服务和兜底方法绑定实现服务熔断和降级。package com.atguigu.controller;import com.atguigu.apis.PayFeignApi; import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; import jakarta.annotation.Resource; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController;@RestController public class OrderCircuitController {@Resourceprivate PayFeignApi payFeignApi;@GetMapping("/feign/pay/circuit/{id}")@CircuitBreaker(name = "cloud-payment-service",fallbackMethod = "myCircuitFallback")public String myCircuitBreaker(@PathVariable("id") Integer id){return payFeignApi.myCircuit(id);}public String myCircuitFallback(Integer id,throwable t){return "myCircuitFallback,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~";} }
-
-
测试网址
http://localhost/feign/pay/circuit/11
、http://localhost/feign/pay/circuit/-4
正确和错误访问网址。测试的具体步骤请参如下信息。- 一次 error 一次 ok 的尝试:当请求次数大于等于6且失败率达到了50%及以上,触发服务熔断并给出服务降级,告知调用者服务不可用,就算输入正确的网址服务也不可用。
- 多测试几次 error,再测试 ok,当请求次数达到6开始计算失败率,当失败率达到50%及以上,触发服务熔断和降级,告知调用者服务不可用,就算输入正确网址服务也不可用。只有等待服务5s 后处于半开状态,输入满足要求次数的正确网址,断路器才会转换为 close 状态,否则依然处于OPEN 状态。
1.4.3.1.2 时间的滑动窗口 TIME_BASED
-
修改 pom.xml 文件,添加依赖。有与在测试计数滑动窗口时已经引入依赖,所以此处不在赘述相关内容。
-
写 application.yml 文件。由于此前设置了计数滑动窗口的相关配置,为避免影响测试效果,此处需要先将其注释在添加时间滑动窗口的相关配置。注意,还需要将 OpenFeign 服务调用的重试机制修改为不重试,否则也可能影响测试结果。
-
修改 OpenFeign 服务调用的重试机制为不重试。具体步骤请参考以下信息。
package com.atguigu.config;import feign.Logger; import feign.Retryer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;@Configuration public class FeignConfig {@Beanpublic Retryer myRetryer() {return Retryer.NEVER_RETRY; // return new Retryer.Default(100,1,3);}@Beanpublic Logger.Level feignLoggerLevel() {return Logger.Level.FULL;}}
-
修改 application.yml 配置文件。
# Resilience4j CircuitBreaker 按照时间:TIME_BASED 的例子 resilience4j:timelimiter:configs:default:timeout-duration: 10s #神坑的位置,timelimiter 默认限制远程1s,超于1s就超时异常,配置了降级,就走降级逻辑circuitbreaker:configs:default:failureRateThreshold: 50 #设置50%的调用失败时打开断路器,超过失败请求百分⽐CircuitBreaker变为OPEN状态。slowCallDurationThreshold: 2s #慢调用时间阈值,高于这个阈值的视为慢调用并增加慢调用比例。slowCallRateThreshold: 30 #慢调用百分比峰值,断路器把调用时间⼤于slowCallDurationThreshold,视为慢调用,当慢调用比例高于阈值,断路器打开,并开启服务降级slidingWindowType: TIME_BASED # 滑动窗口的类型slidingWindowSize: 2 #滑动窗口的大小配置,配置TIME_BASED表示2秒minimumNumberOfCalls: 2 #断路器计算失败率或慢调用率之前所需的最小样本(每个滑动窗口周期)。permittedNumberOfCallsInHalfOpenState: 2 #半开状态允许的最大请求数,默认值为10。waitDurationInOpenState: 5s #从OPEN到HALF_OPEN状态需要等待的时间recordExceptions:- java.lang.Exceptioninstances:cloud-payment-service:baseConfig: default
-
-
测试网址
http://localhost/feign/pay/circuit/11
、http://localhost/feign/pay/circuit/9999
正确和延时访问网址。测试的具体步骤请参如下信息。
1.4.3.2 舱壁隔离 BulkHead
舱壁隔离 BulkHead 主要用于限制并发的情况。依赖隔离&负载保护,用于限制下游服务的最大并发数量。
bulkhead(船的)舱壁/(飞机的)隔板,隔板来自造船行业,床仓内部一般会分成很多小隔舱,一旦一个隔舱漏水因为隔板的存在而不至于影响其它隔舱和整体船。
Resilience4J 提供了如下两种隔离的实现方式,分别是量子舱壁隔离和固定线程池舱壁隔离,用于子限制并发隔离的数量。具体信息可参考官网网址英文版https://resilience4j.readme.io/docs/bulkhead,中文版https://github.com/lmhmhl/Resilience4j-Guides-Chinese/blob/main/core-modules/bulkhead.md。
1.4.3.2.1 实现信号量舱壁 SemaphoreBulkHead
信号量舱壁(SemaphoreBulkhead)原理:
- 当信号量有空闲时,进入系统的请求会直接获取信号量并开始业务处理。
- 当信号量全被占用时,接下来的请求将会进入阻塞状态,SemaphoreBulkhead提供了一个阻塞计时器,
- 如果阻塞状态的请求在阻塞计时内无法获取到信号量则系统会拒绝这些请求。
- 若请求在阻塞计时内获取到了信号量,那将直接获取信号量并执行相应的业务处理。
源码分析,可查看io.github.reselience4J.bulkhead.internal.SemaphoreBulkHead
-
在支付微服务 cloud-provider-payment8001 的 PayCircuitController 中添加如下代码。
//=========Resilience4j bulkhead 的例子 @GetMapping(value = "/pay/bulkhead/{id}") public String myBulkhead(@PathVariable("id") Integer id) {if(id == -4) throw new RuntimeException("----bulkhead id 不能-4");if(id == 9999){try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }}return "Hello, bulkhead! inputId: "+id+" \t " + IdUtil.simpleUUID(); }
-
在 cloud-apis-commons 中的 PayFeignApi 中添加如下接口抽象方法。
/*** Resilience4j Bulkhead 的例子* @param id* @return*/ @GetMapping(value = "/pay/bulkhead/{id}") public String myBulkhead(@PathVariable("id") Integer id);
-
在 cloud-consumer-feign-order80 中添加业务逻辑。
-
修改 pom.xml 文件,添加如下依赖。
<!--resilience4j-bulkhead--> <dependency><groupId>io.github.resilience4j</groupId><artifactId>resilience4j-bulkhead</artifactId> </dependency>
-
写 application.yml 文件,注释原先的断路器时间滑动窗口配置,添加信后量舱壁隔离配置。具体配置信息如下。
####resilience4j bulkhead 的例子 resilience4j:bulkhead:configs:default:maxConcurrentCalls: 2 # 隔离允许并发线程执行的最大数量maxWaitDuration: 1s # 当达到并发调用数量时,新的线程的阻塞时间,我只愿意等待1秒,过时不候进舱壁兜底fallbackinstances:cloud-payment-service:baseConfig: defaulttimelimiter:configs:default:timeout-duration: 20s
-
-
在 OrderCircuitController 业务类中添加如下业务,添加注解
@Bulkhead(name="服务名称",fallbackMethod="兜底方法名",type =指定舱壁隔离方式类型)
将该业务方法 myBulkhead 与服务和兜底方法绑定实现服务信号量舱壁隔离和降级。/***(船的)舱壁,隔离* @param id* @return*/ @GetMapping(value = "/feign/pay/bulkhead/{id}") @Bulkhead(name = "cloud-payment-service",fallbackMethod = "myBulkheadFallback",type = Bulkhead.Type.SEMAPHORE) public String myBulkhead(@PathVariable("id") Integer id) {return payFeignApi.myBulkhead(id); } public String myBulkheadFallback(Throwable t) {return "myBulkheadFallback,隔板超出最大数量限制,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~"; }
-
测试
http://localhost/feign/pay/bulkhead/3
、http://localhost/feign/pay/bulkhead/9999
正确和延时访问网址。测试的具体步骤请参如下信息。浏览器新打开2个窗口,各点一次,分别点击http://localhost/feign/pay/bulkhead/9999,每个请求调用需要耗时5秒,2个线程瞬间达到配置过的最大并发数2,此时第3个请求正常的请求访问,http://localhost/feign/pay/bulkhead/3,直接被舱壁限制隔离了,碰不到8001,等其中一个窗口停止了,再去正常访问,并发数小于2 了,可以OK。
可以看到因为本案例并发线程数为2(maxConcurrentCalls: 2),只让2个线程进入执行,其他请求降直接降级。
1.4.3.2.2 实现固定线程池舱壁隔离 FixedThreadPoolBulkhead
固定线程池舱壁(FixedThreadPoolBulkhead)
FixedThreadPoolBulkhead的功能与SemaphoreBulkhead一样也是用于限制并发执行的次数的,但是二者的实现原理存在差别而且表现效果也存在细微的差别。FixedThreadPoolBulkhead使用一个固定线程池和一个等待队列来实现舱壁。
- 当线程池中存在空闲时,则此时进入系统的请求将直接进入线程池开启新线程或使用空闲线程来处理请求。
- 当线程池中无空闲时时,接下来的请求将进入等待队列,若等待队列仍然无剩余空间时接下来的请求将直接被拒绝,在队列中的请求等待线程池出现空闲时,将进入线程池进行业务处理。
另外:ThreadPoolBulkhead只对CompletableFuture方法有效,所以我们必创建返回CompletableFuture类型的方法
源码分析,可查看io.github.reselience4J.bulkhead.internal.FixedThreadPoolBulkHead
。
底子里面就是 JUC 里面的线程池 ThredPoolExecutor。
submit 进线程池返回 ComplatableFuture。
修改包 cloud-consumer-feign-order80 业务。
-
修改 pom.xml 文件,添加舱壁隔离依赖。由于在配置信号量舱壁隔离式已经引入了该依赖,故此处不在赘述。
-
写application.yml 文件,注释原来信号量舱壁隔离的配置,避免影响固定线程池舱壁割裂配置。如下是配置固定线程池舱壁隔离的配置信息。注意,spring.cloud.openfeign.circuitbreaker.group.enabled 请设置为false 新启线程和原来主线程脱离。
####resilience4j bulkhead -THREADPOOL的例子 resilience4j:timelimiter:configs:default:timeout-duration: 10s #timelimiter默认限制远程1s,超过报错不好演示效果所以加上10秒thread-pool-bulkhead:configs:default:core-thread-pool-size: 1max-thread-pool-size: 1queue-capacity: 1instances:cloud-payment-service:baseConfig: default # spring.cloud.openfeign.circuitbreaker.group.enabled 请设置为false 新启线程和原来主线程脱离
-
在业务类 OrderCircuitController 中添加如下业务。通过注解
@Bulkhead(name="服务名称",fallbackMethod="兜底方法名",type =指定舱壁隔离方式类型)
将该业务方法 myBulkheadTHREAPOOL 与服务和兜底方法绑定实现服务固定线程池舱壁隔离和降级。注意,需要注释掉信号量舱壁隔离的业务方法,否则会因为具有同一个 value 值的注解@GetMappping()
而报错/*** (船的)舱壁,隔离,THREADPOOL* @param id* @return*/ @GetMapping(value = "/feign/pay/bulkhead/{id}") @Bulkhead(name = "cloud-payment-service",fallbackMethod = "myBulkheadPoolFallback",type = Bulkhead.Type.THREADPOOL) public CompletableFuture<String> myBulkheadTHREADPOOL(@PathVariable("id") Integer id) {System.out.println(Thread.currentThread().getName()+"\t"+"enter the method!!!");try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }System.out.println(Thread.currentThread().getName()+"\t"+"exist the method!!!");return CompletableFuture.supplyAsync(() -> payFeignApi.myBulkhead(id) + "\t" + " Bulkhead.Type.THREADPOOL"); } public CompletableFuture<String> myBulkheadPoolFallback(Integer id,Throwable t) {return CompletableFuture.supplyAsync(() -> "Bulkhead.Type.THREADPOOL,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~"); }
-
测试网址http://localhost/feign/pay/bulkhead/1、http://localhost/feign/pay/bulkhead/2、http://localhost/feign/pay/bulkhead/3。测试结果如下,最多同时并发两个请求,第三个请求会触发舱壁隔离机制,实现服务降级。测试结果如下。
1.4.3.3 服务限流 RateLimiter
服务限流器 RateLimiter通过限流控制请求访问频率。具体信息可参考官网网址https://resilience4j.readme.io/docs/ratelimiter,中文网址https://github.com/lmhmhl/Resilience4j-Guides-Chinese/blob/main/core-modules/ratelimiter.md。
常见限流算法主要有以下几种。
-
漏斗算法(Leaky Bucket)
一个固定容量的漏桶,按照设定常量固定速率流出水滴,类似医院打吊针,不管你源头流量多大,我设定匀速流出。 如果流入水滴超出了桶的容量,则流入的水滴将会溢出了(被丢弃),而漏桶容量是不变的。
缺点:
这里有两个变量,一个是桶的大小,支持流量突发增多时可以存多少的水(burst),另一个是水桶漏洞的大小(rate)。因为漏桶的漏出速率是固定的参数,所以,即使网络中不存在资源冲突(没有发生拥塞),漏桶算法也不能使流突发(burst)到端口速率。因此,漏桶算法对于存在突发特性的流量来说缺乏效率。
-
令牌桶算法(Token Bucket),这人也是最推荐的限流算法,SpringCloud 默认算法。
-
滚动时间窗口(tumbling time window)
允许固定数量的请求进入(比如1秒取4个数据相加,超过25值就over)超过数量就拒绝或者排队,等下一个时间段进入。由于是在一个时间间隔内进行限制,如果用户在上个时间间隔结束前请求(但没有超过限制),同时在当前时间间隔刚开始请求(同样没超过限制),在各自的时间间隔内,这些请求都是正常的。下图统计了3次,but…
-
滑动时间窗口(sliding time window)
顾名思义,该时间窗口是滑动的。所以,从概念上讲,这里有两个方面的概念需要理解:
窗口:需要定义窗口的大小
滑动:需要定义在窗口中滑动的大小,但理论上讲滑动的大小不能超过窗口大小
滑动窗口算法是把固定时间片进行划分并且随着时间移动,移动方式为开始时间点变为时间列表中的第2个时间点,结束时间点增加一个时间点,
不断重复,通过这种方式可以巧妙的避开计数器的临界点的问题。下图统计了5次。
-
在 cloud-provider-payment8001 中的 PayCircuitCOntroller 业务类中添加如下业务代码。
//=========Resilience4j ratelimit 的例子 @GetMapping(value = "/pay/ratelimit/{id}") public String myRatelimit(@PathVariable("id") Integer id) {return "Hello, myRatelimit欢迎到来 inputId: "+id+" \t " + IdUtil.simpleUUID(); }
-
在 cloud-apis-commons 中的 PayFeignApi 中添加如下接口抽象方法。
/*** Resilience4j Ratelimit 的例子* @param id* @return*/ @GetMapping(value = "/pay/ratelimit/{id}") public String myRatelimit(@PathVariable("id") Integer id);
-
在 cloud-consumer-feign-order80 中做业务代码。
-
修改 pom.xml 文件,引入限流 RateLimiter 依赖。
<!--resilience4j-ratelimiter--> <dependency><groupId>io.github.resilience4j</groupId><artifactId>resilience4j-ratelimiter</artifactId> </dependency>
-
写 applicat.yml 文件,注释掉原来的固定线程池舱壁隔离配置,引入新配置限流 RateLimiter 的线管信息。
####resilience4j ratelimiter 限流的例子 resilience4j:ratelimiter:configs:default:limitForPeriod: 2 #在一次刷新周期内,允许执行的最大请求数limitRefreshPeriod: 1s # 限流器每隔limitRefreshPeriod刷新一次,将允许处理的最大请求数量重置为limitForPeriodtimeout-duration: 1 # 线程等待权限的默认等待时间instances:cloud-payment-service:baseConfig: default
-
写业务类,通过注解
@RateLimiter(name="服务名称",fallbackMethod="兜底方法名")
将该业务方法 myBulkhead 与服务和兜底方法绑定实现服务限流和降级。@GetMapping(value = "/feign/pay/ratelimit/{id}") @RateLimiter(name = "cloud-payment-service",fallbackMethod = "myRatelimitFallback") public String myBulkhead(@PathVariable("id") Integer id) {return payFeignApi.myRatelimit(id); } public String myRatelimitFallback(Integer id,Throwable t) {return "你被限流了,禁止访问/(ㄒoㄒ)/~~"; }
-
-
测试网址http://localhost/feign/pay/ratelimit/11,多刷新以下该网址,将会出现以下信息。
上一页 3-SpringCloud-LoadBalancer-OpenFeign服务调用与负载均衡