1.Hystrix进入维护模式
1.1 是什么
Hystrix是一个用于处理分布式体系的延迟和容错的开源库,在分布式体系里,许多依靠不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依靠出题目标情况下,不会导致整体服务失败,避免级联故障,以提高分布式体系的弹性。
相识,这是Netflix项目中的组件,不会再利用了。
1.2 替代方案
Resilience4j
2.断路器概述
2.1 分布式体系面对的题目
分布式体系面对的题目,复杂分布式体系布局中的应用程序有数十个依靠关系,每个依靠关系在某些时候将不可避免地失败。
2.2 服务雪崩征象
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的体系资源,进而引起体系崩溃,所谓的“雪崩效应”。
对于高流量的应用来说,单一的后端依靠可能会导致全部服务器上的全部资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他体系资源紧张,导致整个体系发生更多的级联故障。这些都表示需要对故障和延迟举行隔离和管理,以便单个依靠关系的失败,不能导致整个应用程序或体系都崩溃。
所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有题目标模块还调用了其他的模块,如许就可能会发生级联故障,或者叫雪崩。
2.3 微服务体系的述求
题目:禁止服务雪崩故障
解决: 有题目标节点,快速熔断(快速返回失败处理或者返回默认兜底数据也被称为服务降级)。
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,如许就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式体系中的蔓延,乃至雪崩。
一句话,出故障了“保险丝”跳闸,别把整个家给烧了
2.4 解决方案
2.4.1 服务熔断 + 服务降级
服务熔断,就是当服务b发生故障时,服务a不在访问b,也就是说不能再访问b。
服务降级,就是给调用方一个默认的fallback,而不是一直在等待。比如,服务器忙,请稍后再试。
2.4.2 服务限流
通过限流算法,将瞬间很大的请求数量,转换为体系承受范围内的请求,从而保证服务不会被流量冲垮。
3.Spring Circuit Breaker
3.1 官网
https://spring.io/projects/spring-cloud-circuitbreaker
3.2 实现原理
CircuitBreaker的目标是保护分布式体系免受故障和异常,提高体系的可用性和健壮性。
当一个组件或服务出现故障时,CircuitBreaker会迅速切换到开放OPEN状态(保险丝跳闸断电),阻止请求发送到该组件或服务从而避免更多的请求发送到该组件或服务。这可以减少对该组件或服务的负载,防止该组件或服务进一步崩溃,并使整个体系能够继续正常运行。同时,CircuitBreaker还可以提高体系的可用性和健壮性,由于它可以在分布式体系的各个组件之间自动切换,从而避免单点故障的题目。
3.3 CircuitBreaker和Resilience4j关系
CircuitBreaker是一种思想,Resilience4j是其思想的具体实现。
4.Resilience4j
4.1 是什么
Resilience4j是一个为函数式编程设计的轻量级容错库。Resilience4j提供高阶函数(装饰器),以增强与断路器、速率限定器、重试或隔板的任何功能接口、lambda表达式或方法引用。您可以在任何函数接口、lambda表达式或方法引用上堆叠多个装饰器。优点是,您可以选择所需的装饰器,而无需其他任何选择。
Resilience4j 2需要Java 17。
4.2 能干啥
本课程,只讲了这三个重要功能,分别是熔断(Circuit Breaking),限流(limiting),舱壁(Bulkheading)。
4.3 官网
https://resilience4j.readme.io/docs/circuitbreaker
中文手册
https://github.com/lmhmhl/Resilience4j-Guides-Chinese/blob/main/index.md
5.案例实战
5.1 熔断和降级(circuitbreaker&fallback)
5.1.1 断路器的三大状态
5.1.2 断路器三大状态的转换
- 断路器有三个普通状态:关闭(CLOSED),开启(OPEN),半开(HALF_OPEN),还有两个特殊状态,禁用(DISABLED),强制开启(FORCED_OPEN)。
- 当断路器关闭时,全部的请求都会通过断路器,就行入户的电都会流经关闭的保险丝。
- 如果失败率超过设定的阈值,熔断器就会从关闭状态转换到打开状态,这时全部的请求都会被拒绝。
- 当经过一段时间后,熔断器会从打开状态转换到半开状态,这时仅有肯定数量的请求会被放入,并重新计算失败率。
- 如果失败率超过阈值,则变为打开状态,如果失败率低于阈值,则变为关闭状态。
- 断路器利用滑动窗口来存储和统计调用的结果。你可以选择基于调用数量的滑动窗口或者基于时间的滑动窗口。
- 基于访问数量的滑动窗口统计了最近N次调用的返回结果。居于时间的滑动窗口统计了最近N秒的调用返回结果。
- 除此以外,熔断器还会有两种特殊状态
ISABLED(始终答应访问)和FORCED_OPEN(始终拒绝访问)。
- 这两个状态不会生成熔断器事件(除状态装换外),而且不会记录事件的成功或者失败。
- 退出这两个状态的唯一方法是触发状态转换或者重置熔断器。
5.1.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
默认CircuitBreakerConfig.java配置类(io.github.resilience4j.circuitbreaker)
精简版配置参数
参数寄义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状态需要等待的时间5.1.4 熔断 + 降级需求说明
6次访问中当实行方法的失败率到达50%时CircuitBreaker将进入开启OPEN状态(保险丝跳闸断电)拒绝全部请求。
等待5秒后,CircuitBreaker 将自动从开启OPEN状态过渡到半开HALF_OPEN状态,答应一些请求通过以测试服务是否规复正常。
如还是异常CircuitBreaker 将重新进入开启OPEN状态;如正常将进入关闭CLOSE闭合状态规复正常处理请求。
具体时间和频次等属性见具体现实案例
5.2 熔断降级案例
5.2.1 计数的滑动窗口(count_based)
1.修改cloud-provider-payment8001
新建PayCircuitController类- @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();
- }
- }
复制代码 2.修改PayFeignApi接口
新增- /**
- * Resilience4j CircuitBreaker 的例子
- * @param id
- * @return
- */
- @GetMapping(value = "/pay/circuit/{id}")
- String myCircuit(@PathVariable("id") Integer id);
复制代码 3.修改cloud-consumer-feign-order80
改pom- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-aop</artifactId>
- </dependency>
复制代码 完整YML- server:
- port: 8080
- spring:
- application:
- name: cloud-consumer-openfeign-order
- ####Spring Cloud Consul for Service Discovery
- cloud:
- consul:
- host: localhost
- port: 8500
- discovery:
- prefer-ip-address: true #优先使用服务ip进行注册
- service-name: ${spring.application.name}
- openfeign:
- #新增
- circuitbreaker:
- enabled: true
- group:
- enabled: true
- #
- httpclient:
- hc5:
- enabled: true
- compression:
- request:
- enabled: true
- min-request-size: 2048 #最小触发压缩的大小
- mime-types: text/xml,application/xml,application/json #触发压缩数据类型
- response:
- enabled: true
- client:
- config:
- #全局配置
- default:
- #连接超时时间
- connectTimeout: 3000
- #读取超时时间
- readTimeout: 3000
- #单个微服务的配置,细粒度的重写全局
- cloud-payment-service:
- #连接超时时间
- connect-timeout: 20000
- #读取超时时间
- read-timeout: 20000
- logging:
- level:
- com.atguigu.cloud.apis.PayFeignApi: debug
- # Resilience4j CircuitBreaker 按照次数:COUNT_BASED 的例子
- #新增
- 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.Exception
- instances:
- cloud-payment-service:
- baseConfig: default
-
复制代码 新增OrderCircuitController类- @RestController
- public class OrderCircuitController {
- @Resource
- private PayFeignApi payFeignApi;
- @GetMapping(value = "/feign/pay/circuit/{id}")
- @CircuitBreaker(name = "cloud-payment-service", fallbackMethod = "myCircuitFallback")
- public String myCircuitBreaker(@PathVariable("id") Integer id) {
- return payFeignApi.myCircuit(id);
- }
- //myCircuitFallback就是服务降级后的兜底处理方法
- public String myCircuitFallback(Integer id, Throwable t) {
- // 这里是容错处理逻辑,返回备用结果
- return "myCircuitFallback,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~";
- }
- }
复制代码 @CircuitBreaker注解,以及默认的回调fallbackMethod
5.2.2 测试
略
5.2.3 计时的滑动窗口(TIME_BASED)
原理
基于时间的滑动窗口是通过有N个桶的环形数组实现。
如果滑动窗口的大小为10秒,这个环形数组总是有10个桶,每个桶统计了在这一秒发生的全部调用的结果(部分统计结果),数组中的第一个桶存储了当前这一秒内的全部调用的结果,其他的桶存储了之前每秒调用的结果。
滑动窗口不会单独存储全部的调用结果,而是对每个桶内的统计结果和总的统计值举行增量的更新,当新的调用结果被记录时,总的统计值会举行增量更新。
检索快照(总的统计值)的时间复杂度为O(1),由于快照已经预先统计好了,而且和滑动窗口大小无关。
关于此方法实现的空间需求(内存消耗)约等于O(n)。由于每次调用结果(元组)不会被单独存储,只是对N个桶举行单独统计和一次总分的统计。
每个桶在举行部分统计时存在三个整型,为了计算,失败调用数,慢调用数,总调用数。还有一个long类型变量,存储全部调用的响应时间。
YML修改- resilience4j:
- timelimiter:
- configs:
- default:
- timeout-duration: 10s #神坑的位置,timelimiter 默认限制远程1s,超于1s就超时异常,配置了降级,就走降级逻辑
- circuitbreaker:
- configs:
- #配置解释,在slidingWindowSize(s)这个窗口内,最少有minimumNumberOfCalls个请求,且这么多请求中,有minimumNumberOfCalls * slowCallRateThreshold次的时间是大于slowCallDurationThreshold时,就打开断路器,导致正常的请求也也会走降级方法,返回默认值。但是在5s后就变为半开状态,同时,再有请求时,放过去2个,如果都成功,切换回关闭状态,如果失败,继续等关闭5s然后尝试。
- 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.Exception
- instances:
- cloud-payment-service:
- baseConfig: default
复制代码 为了避免影响实行,关闭FeignConfig的重试3次- @Bean
- public Retryer retryer(){
- return Retryer.NEVER_RETRY;
- //maxAttempts - 1才是重试次数
- //最大请求次数(1 + 2),初始间隔时间100ms,重试间最大间隔时间1s
- // return new Retryer.Default(100,1,3);
- }
复制代码 5.2.3 测试结果
略
5.3 熔断+降级小总结
断路器开闭的条件
当慢查询到达肯定峰值,或失败率到达肯定条件后,断路器转为open状态,服务熔断。当open时,都走的时fallbackMethod兜底方法,服务降级。
根据配置,一段时间后,open转为half_open状态,会根据配置,放几个请求已往,如果都成功转为closed,否则继续open。
阳哥建议利用COUNT_BASED类型,计数类型。
5.4 隔离(bulkhead)
5.4.1 官网
https://resilience4j.readme.io/docs/bulkhead
中文
https://github.com/lmhmhl/Resilience4j-Guides-Chinese/blob/main/core-modules/bulkhead.md
5.4.2 是什么
bulkhead(船的)舱壁/(飞机的)隔板
隔板来自造船行业,床仓内部一样寻常会分成很多小隔舱,一旦一个隔舱漏水由于隔板的存在而不至于影响其它隔舱和整体船。
限定并发
5.4.3 能干吗
依靠隔离和负载保护:用来限定对下游服务的最大并发数量的限定。
Resilence4j提供了两种隔离的实现方式,可以限定并发实行的数量。
信号量(SemaphoreBulkhead)和固定线程池(FixedThreadPoolBulkhead)。
5.5 SemaphoreBulkhead案例
5.5.1 概述
基本上就是JUC信号灯内容的同样思想
信号量舱壁(SemaphoreBulkhead)原理
当信号量有空闲时,进入体系的请求会直接获取信号量并开始业务处理。
当信号量全被占用时,接下来的请求将会进入阻塞状态,SemaphoreBulkhead提供了一个阻塞计时器,如果阻塞状态的请求在阻塞计时内无法获取到信号量则体系会拒绝这些请求。若请求在阻塞计时内获取到了信号量,那将直接获取信号量并实行相应的业务处理。
5.5.2 源码
- public SemaphoreBulkhead(String name, @Nullable BulkheadConfig bulkheadConfig,
- Map<String, String> tags) {
- this.name = name;
- this.config = requireNonNull(bulkheadConfig, CONFIG_MUST_NOT_BE_NULL);
- this.tags = requireNonNull(tags, TAGS_MUST_NOTE_BE_NULL);
- // init semaphore
- //java.util.concurrent.Semaphore
- this.semaphore = new Semaphore(config.getMaxConcurrentCalls(), config.isFairCallHandlingEnabled());
- this.metrics = new BulkheadMetrics();
- this.eventProcessor = new BulkheadEventProcessor();
- }
复制代码 5.5.3 修改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();
- }
复制代码 5.5.4 修改PayFeignApi接口
- /**
- * Resilience4j Bulkhead 的例子
- * @param id
- * @return
- */
- @GetMapping(value = "/pay/bulkhead/{id}")
- String myBulkhead(@PathVariable("id") Integer id);
复制代码 5.5.5 修改cloud-consumer-feign-order80
POM- <dependency>
- <groupId>io.github.resilience4j</groupId>
- <artifactId>resilience4j-bulkhead</artifactId>
- </dependency>
复制代码 YML- resilience4j:
- bulkhead:
- configs:
- default:
- maxConcurrentCalls: 2 # 隔离允许并发线程执行的最大数量
- maxWaitDuration: 1s # 当达到并发调用数量时,新的线程的阻塞时间,我只愿意等待1秒,过时不候进舱壁兜底fallback
- instances:
- cloud-payment-service:
- baseConfig: default
- timelimiter:
- configs:
- default:
- timeout-duration: 20s
复制代码 OrderCircuitController修改- /**
- *(船的)舱壁,隔离
- * @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ㄒ)/~~";
- }
复制代码 5.5.6 测试
略
5.6 FixedThreadPoolBulkhead案例
5.6.1 概述
基本上就是我们JUC-线程池内容的同样思想
固定线程池舱壁(FixedThreadPoolBulkhead)
FixedThreadPoolBulkhead的功能与SemaphoreBulkhead一样也是用于限定并发实行的次数的,但是二者的实现原理存在差异而且体现结果也存在细微的差异。FixedThreadPoolBulkhead利用一个固定线程池和一个等待队列来实现舱壁。
当线程池中存在空闲时,则此时进入体系的请求将直接进入线程池开启新线程或利用空闲线程来处理请求。当线程池中无空闲时时,接下来的请求将进入等待队列,若等待队列仍然无剩余空间时接下来的请求将直接被拒绝,在队列中的请求等待线程池出现空闲时,将进入线程池举行业务处理。
别的:ThreadPoolBulkhead只对CompletableFuture方法有用,所以我们必创建返回CompletableFuture类型的方法
5.6.2 源码
- public FixedThreadPoolBulkhead(String name, @Nullable ThreadPoolBulkheadConfig bulkheadConfig,
- Map<String, String> tags) {
- this.name = name;
- this.config = requireNonNull(bulkheadConfig, CONFIG_MUST_NOT_BE_NULL);
- this.tags = requireNonNull(tags, TAGS_MUST_NOTE_BE_NULL);
- // init thread pool executor
- this.executorService = new ThreadPoolExecutor(config.getCoreThreadPoolSize(),
- config.getMaxThreadPoolSize(),
- config.getKeepAliveDuration().toMillis(), TimeUnit.MILLISECONDS,
- config.getQueueCapacity() == 0 ? new SynchronousQueue<>() : new ArrayBlockingQueue<>(config.getQueueCapacity()),
- new BulkheadNamingThreadFactory(name),
- config.getRejectedExecutionHandler());
- // adding prover jvm executor shutdown
- this.metrics = new FixedThreadPoolBulkhead.BulkheadMetrics();
- this.eventProcessor = new FixedThreadPoolBulkhead.BulkheadEventProcessor();
- }
- @Override
- public <T> CompletableFuture<T> submit(Callable<T> callable) {
- final CompletableFuture<T> promise = new CompletableFuture<>();
- try {
- CompletableFuture.supplyAsync(ContextPropagator.decorateSupplier(config.getContextPropagator(),() -> {
- try {
- publishBulkheadEvent(() -> new BulkheadOnCallPermittedEvent(name));
- return callable.call();
- } catch (CompletionException e) {
- throw e;
- } catch (Exception e){
- throw new CompletionException(e);
- }
- }), executorService).whenComplete((result, throwable) -> {
- publishBulkheadEvent(() -> new BulkheadOnCallFinishedEvent(name));
- if (throwable != null) {
- promise.completeExceptionally(throwable);
- } else {
- promise.complete(result);
- }
- });
- } catch (RejectedExecutionException rejected) {
- publishBulkheadEvent(() -> new BulkheadOnCallRejectedEvent(name));
- throw BulkheadFullException.createBulkheadFullException(this);
- }
- return promise;
- }
复制代码 5.6.3 修改cloud-consumer-feign-order80
POM用修改了,引入resilience4j-bulkhead就包罗Fixed..类型了
YML- ####resilience4j bulkhead -THREADPOOL的例子
- resilience4j:
- timelimiter:
- configs:
- default:
- timeout-duration: 10s #timelimiter默认限制远程1s,超过报错不好演示效果所以加上10秒
- thread-pool-bulkhead:
- configs:
- #最大请求是max-thread-pool-size + queue-capacity个,后面的如果再来报错
- default:
- core-thread-pool-size: 1
- max-thread-pool-size: 1
- queue-capacity: 1
- instances:
- cloud-payment-service:
- baseConfig: default
- # spring.cloud.openfeign.circuitbreaker.group.enabled 请设置为false 新启线程和原来主线程脱离
复制代码 修改- /**
- * (船的)舱壁,隔离,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ㄒ)/~~");
- }
复制代码 5.6.4 测试
略
5.7 限流(ratelimiter)
5.7.1 官网
https://resilience4j.readme.io/docs/ratelimiter
中文
https://github.com/lmhmhl/Resilience4j-Guides-Chinese/blob/main/core-modules/ratelimiter.md
5.7.2 是什么
限流:就是限定最大访问流量。体系能提供的最大并发是有限的,同时来的请求又太多,就需要限流。
比如商城秒杀业务,瞬时大量请求涌入,服务器忙不外就只好列队限流了,和去景点列队买票和去医院办理业务列队等号原理相同。
所谓限流,就是通过对并发访问/请求举行限速,或者对一个时间窗口内的请求举行限速,以保护应用体系,一旦到达限定速率则可以拒绝服务、列队或等待、降级等处理。
5.7.3 常见限流算法
漏桶算法
一个固定容量的漏桶,按照设定常量固定速率流出水滴,类似医院打吊针,不管你源头流量多大,我设定匀速流出。
如果流入水滴超出了桶的容量,则流入的水滴将会溢出了(被丢弃),而漏桶容量是不变的。
这里有两个变量,一个是桶的大小,支持流量突发增多时可以存多少的水(burst),另一个是水桶漏洞的大小(rate)。由于漏桶的漏出速率是固定的参数,所以,即使网络中不存在资源冲突(没有发生拥塞),漏桶算法也不能使流突发(burst)到端口速率。因此,漏桶算法对于存在突发特性的流量来说缺乏效率。
令牌桶算法
令牌桶算法(Token Bucket Algorithm)是一种流量控制算法,广泛应用于计算机网络的流量整形和速率限定中。其工作原理相对直观:
- 初始化:创建一个令牌桶,并设定桶的最大容量(以令牌的数量表示)。初始时,桶内可能为空或含有肯定数量的令牌。
- 令牌生成:以恒定的速率向桶内添加令牌。如果桶已满,则新生成的令牌会被丢弃。
- 请求处理:每当有一个数据包(或请求)到达时,体系会尝试从桶中取出一个令牌。如果此时桶中有可用的令牌,则取出令牌并答应数据包通过(即处理请求)。如果桶中没有令牌,则该数据包可能被延迟或者直接拒绝。
- 突发流量处理:由于桶可以存储肯定数量的令牌,所以当短时间内有大量的数据包到来时(即突发流量),只要桶内的令牌足够,就可以立刻处理这些数据包,从而支持肯定程度上的流量突增。
- 动态调整:某些实现答应动态调整生成令牌的速率或桶的大小,以适应不同的网络状况或服务质量(QoS)需求。
与漏桶算法相比,令牌桶算法能够更好地处理突发流量,由于它答应某种程度上的“存储”流量处理本领(即存储令牌),而漏桶算法则严格按恒定速率处理全部到来的流量,超出处理本领的流量都会被丢弃。
滚动时间窗算法
答应固定数量的请求进入(比如1秒取4个数据相加,超过25值就over)超过数量就拒绝或者列队,等下一个时间段进入。
由于是在一个时间间隔内举行限定,如果用户在上个时间间隔结束前请求(但没有超过限定),同时在当前时间间隔刚开始请求(同样没超过限定),在各自的时间间隔内,这些请求都是正常的。下图统计了3次,but......
缺点
如果设定1分钟最多可以请求100次某个接口,如12:00:00-12:00:59时间段内没有数据请求但12:00:59-12:01:00时间段内突然并发100次请求,紧接着瞬间跨入下一个计数周期计数器清零;在12:01:00-12:01:01内又有100次请求。那么也就是说在时间临界点左右可能同时有2倍的峰值举行请求,从而造成背景处理请求加倍过载的bug,导致体系运营本领不足,甚至导致体系崩溃,/(ㄒoㄒ)/~~
滑动时间窗算法
滑动时间窗口(sliding time window)
顾名思义,该时间窗口是滑动的。所以,从概念上讲,这里有两个方面的概念需要明白:
- 窗口:需要界说窗口的大小
- 滑动:需要界说在窗口中滑动的大小,但理论上讲滑动的大小不能超过窗口大小
滑动窗口算法是把固定时间片举行划分而且随着时间移动,移动方式为开始时间点变为时间列表中的第2个时间点,结束时间点增加一个时间点,
不断重复,通过这种方式可以巧妙的避开计数器的临界点的题目。下图统计了5次
5.8 限流案例
5.8.1 修改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();
- }
复制代码 5.8.2 PayFeignApi
- /**
- * Resilience4j Ratelimit 的例子
- * @param id
- * @return
- */
- @GetMapping(value = "/pay/ratelimit/{id}")
- String myRatelimit(@PathVariable("id") Integer id);
复制代码 5.8.3 修改cloud-consumer-feign-order80
POM- <dependency>
- <groupId>io.github.resilience4j</groupId>
- <artifactId>resilience4j-ratelimiter</artifactId>
- </dependency>
复制代码 YML- ####resilience4j ratelimiter 限流的例子
- resilience4j:
- ratelimiter:
- configs:
- default:
- limitForPeriod: 2 #在一次刷新周期内,允许执行的最大请求数
- limitRefreshPeriod: 1s # 限流器每隔limitRefreshPeriod刷新一次,将允许处理的最大请求数量重置为limitForPeriod
- timeout-duration: 1 # 线程等待权限的默认等待时间
- instances:
- cloud-payment-service:
- baseConfig: default
复制代码 OrderCircuitController类新增- @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ㄒ)/~~";
- }
复制代码 5.8.4 测试
略
只是为了记录自己的学习历程,且本人水平有限,不对之处,请指正。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |