SpringCloud的学习(三)Resilience4j

602 阅读20分钟

SpringCloud的学习(三)Resilience4j

CircuitBreaker熔断器

分布式系统面临的问题:

复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免的失败。

服务雪崩

多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”.

对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。

所以,通常当你发现一个模块下的 某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。

通过断路器解决

有问题的节点,快速熔断(就不再接收流量,而是快速返回失败处理或者返回默认兜底数据【服务降级】)。

断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。

CircuitBreaker的目的是保护分布式系统免受故障和异常,提高系统的可用性和健壮性。

当一个组件或服务出现故障时,CircuitBreaker会迅速切换到开放OPEN状态(保险丝跳闸断电),阻止请求发送到该组件或服务从而避免更多的请求发送到该组件或服务。这可以减少对该组件或服务的负载,防止该组件或服务进一步崩溃,并使整个系统能够继续正常运行。同时,CircuitBreaker还可以提高系统的可用性和健壮性,因为它可以在分布式系统的各个组件之间自动切换,从而避免单点故障的问题

Circuit Breaker只是一套规范和接口,落地实现者(类)是Resilience4J

Resilience4J

Resilience4J是一个专为函数式编程设计的轻量级容错库,专门做服务熔断、服务降级等

Resilience4J熔断框架主要的3大功能:熔断、隔离、限流

来保护服务提供者模块

1. Resilience4J服务熔断和服务降级:

断路器(Circuit Breaker):

  • 断路器有三个普通状态:关闭(CLOSED)、开启(OPEN)、半开(HALF_OPEN),还有两个特殊状态:禁用(DISABLED)、强制开启(FORCED_OPEN)

  • 熔断器三大状态的改变(原理):

    • 当熔断器关闭时,所有的请求都会通过熔断器。

    • 当失败率超过设定的阈值,熔断器就会从关闭状态转换到打开状态,这时所有的请求都会被拒绝。

    • 当经过一段时间后,熔断器会从打开状态转换到半开状态,这时仅有一定数量的请求会被放入,并重新计算失败率

    • 如果失败率超过阈值,熔断器则变为打开状态,如果失败率低于阈值,则变为关闭状态。

  • 断路器使用滑动窗口存储和统计调用的结果。可以选择基于调动数量的滑动窗口或者基于时间的滑动窗口。(即作为断路器开启/关闭的依据)

    • 基于访问数量的滑动窗口:统计最近N次调用的返回结果。:按照访问时的成功率进行判断。eg:访问6次3次失败,就判断被调用服务已经宕机,就不再调用它,立即开启服务熔断 断路器开启状态,进入服务降级(fallback)
    • 基于时间的滑动窗口:统计最近N秒的调用返回结果:基于时间进行判断。eg:在一定的时间范围内,每次调用时间都超出设定时间,也会立即开启服务熔断状态
  • 除此之外,熔断器还会有两种特殊状态:disabled(始终允许访问)和forced_open(始终拒绝访问)。
    • 这两个状态不会生成熔断器事件(除状态转换外),并且不会记录事件的成功与失败。
    • 退出这两个状态的唯一方法是触发状态转换或者重置熔断器。
  • 断路器装在消费者、即服务调用者处

断路器配置:

常用的几个配置:

配置属性默认值描述
slidingWindowTypeCOUNT_BASED断路器的滑动窗口期类型。可以基于"次数"(COUNT_BASED)或者"时间"(TIME_BASED)进行熔断
failureRateThreshold50以百分比配置失败率峰值。以百分比配置失败率阈值。当失败率等于或者大于阈值时,断路器状态变关闭为开启,并进行服务降级(fallback)
slowCallDurationThreshold60000(ms,毫秒)配置调用时间的峰值,高于该峰值的视为慢调用。
slowCallRateThreshold100以百分比的方式配置,断路器把调用时间大于slowCallDurationThreshold的调用视为慢调用,当慢调用比例大于等于峰值时,断路器开启,并进入服务降级。
sliding-window-size100若COUNT_BASED,则10次调用中有50%失败(即5次)打开熔断断路器;若为TIME_BASED则,此时还有额外的两个设置属性,含义为:在N秒内(sliding-window-size)100%(slow-call-rate-threshold)的请求超过N秒(slow-call-duration-threshold)打开断路器。
permitted-number-of-calls-in-half-open-state10运行断路器在HALF_OPEN状态下时进行N次调用,如果故障或慢速调用仍然高于阈值,断路器再次进入打开状态。
minimum-number-of-calls100在每个滑动窗口期样本数,配置断路器计算错误率或者慢调用率的最小调用数。比如设置为5意味着,在计算故障率之前,必须至少调用5次。如果只记录了4次,即使4次都失败了,断路器也不会进入到打开状态。
wait-duration-in-open-state从OPEN到HALF_OPEN状态需要等待的时间

案例:按照COUNT_BASED(计数的滑动窗口)

按照错误次数达到多少次之后开启断路

断路器装在消费者、即微服务调用者处(80)

  • 改pom文件

    
    <!--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>
    
  • 写yml

    server:
      port: 80
    
    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:
          client:
            config:
              default:
                #连接超时时间
                connectTimeout: 3000 #设置设置全局超时时间为3s
                #读取超时时间
                readTimeout: 3000
              cloud-payment-service: # //指定服务名:为这个服务单独配置超时时间,单个配置的超时时间会覆盖全局配置!!!
                #连接超时时间
                connect-timeout: 2000
                #读取超时时间
                read-timeout: 2000
          httpclient:
            hc5:
              enabled: true
          compression:
            request:
              enabled: true  #请求开启压缩功能
              min-request-size: 2048 #最小触发压缩的大小
              mime-types: text/xml,application/xml,application/json #触发压缩数据类型
            response:    #响应也开启压缩功能
              enabled: true
    
    
    #feign日志以什么级别监控哪个接口:即对哪个Feign接口进行日志检查
    logging:
      level:
        com:
          atguigu:
            cloud:
              apis:
                PayFeignApi: debug  #之后比如我们设置了重试机制,会将每次的具体访问信息打印出来
    
    # 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.Exception
        instances:
          cloud-payment-service: #代表将上面断路器的配置 配到80消费者
            baseConfig: default #默认配置
    
  • 服务端模块controller

    @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();//流水号
            }
    }
    
  • Feign接口以及

     @GetMapping(value = "/pay/circuit/{id}")
        public String myCircuit(@PathVariable("id") Integer id);以及消费者模块的controller
          
    
  • 消费者模块的controller

    需要引入@CircuitBreaker注解以及服务降级方法

    @GetMapping(value = "/feign/pay/circuit/{id}")
        @CircuitBreaker(name = "cloud-payment-service", fallbackMethod = "myCircuitFallback")
        //             客户端要调用微服务的名称               服务降级,调用下面的myCircuitFallback()
        public String myCircuitBreaker(@PathVariable("id") Integer id)
        {
            return payFeignApi.myCircuit(id);//调用Feign
        }
        //myCircuitFallback()就是服务降级后的兜底处理方法:如果熔断之后/或者报错就会调用这个方法来返回结果!!!
        public String myCircuitFallback(Integer id,Throwable t) {
            // 这里是容错处理逻辑,返回备用结果
            return "myCircuitFallback,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~";//不要让服务调用者等待并立即返回1个友好提示,																  fallback
        }
    

案例:按照TIME_BASED(时间的滑动窗口)

  • 在服务调用者模块pom文件添依赖

    
    # 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.Exception
        instances:
          cloud-payment-service: #代表将上面断路器的配置 配到80消费者
            baseConfig: default
    

    建议:不要混合使用,建议使用COUNT_BASED的方式

总结:断路器开启或关闭的条件

  • 当满足一定的峰值和失败率达到一定条件之后,断路器就会进入OPEN状态(保险丝跳闸),服务熔断

  • 当OPEN的时候,所有的请求都不会调用主业务的逻辑方法,而是直接走fallbackmethod兜底背锅方法,服务降级

  • 一段时间之后,这时候断路器就会从OPEN进入HALF_OPEN半开状态,会放几个请求过去探探链路是否通?

    如成功,断路器就会关闭CLOSE(类似于保险丝闭合,恢复可用)。

    如失败,继续开启,重复上述。

2.Resilience4J舱壁隔离BulkHead:

舱壁隔离:依赖隔离&负载保护:用来限制下游服务的最大并发数量的限制

Resilience提供了两种隔离的实现方式,可以限制并发执行的数量

  • SemaphoreBulkhead(信号量舱壁):使用了信号量
  • FixedThreadPoolBulkhead(固定线程池舱壁):使用了有界队列和固定大小线程池

信号量舱壁(SemaphoreBulkhead)

舱壁装在消费者、调用者处

原理:

当信号量有空闲时,进入系统的请求会直接获取信号量并开始业务处理。

当信号量全被占用时,接下来的请求将会进入阻塞状态,SemaphoreBulkhead提供了一个阻塞计时器,

如果阻塞状态的请求在阻塞计时内无法获取到信号量则系统会拒绝这些请求。

若请求在阻塞计时内获取到了信号量,那将直接获取信号量并执行相应的业务处理。

使用步骤:

  • 在服务消费端pom文件加入相关依赖

    <!--resilience4j-bulkhead-->
    <dependency>
        <groupId>io.github.resilience4j</groupId>
        <artifactId>resilience4j-bulkhead</artifactId>
    </dependency>
    
  • yml文件

    属性配置:

    maxConcurrentCalls:舱壁允许的最大并行执行量,默认值是25

    maxWaitDuration:尝试进入饱和舱壁时,应阻塞线程的最长时间,默认值是0

    server:
      port: 80
    
    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:
          client:
            config:
              default:
                #连接超时时间
                connectTimeout: 3000 #设置设置全局超时时间为3s
                #读取超时时间
                readTimeout: 3000
              cloud-payment-service: # //指定服务名:为这个服务单独配置超时时间,单个配置的超时时间会覆盖全局配置!!!
                #连接超时时间
                connect-timeout: 2000
                #读取超时时间
                read-timeout: 2000
          httpclient:
            hc5:
              enabled: true
          compression:
            request:
              enabled: true  #请求开启压缩功能
              min-request-size: 2048 #最小触发压缩的大小
              mime-types: text/xml,application/xml,application/json #触发压缩数据类型
            response:    #响应也开启压缩功能
              enabled: true
    
    
    #feign日志以什么级别监控哪个接口:即对哪个Feign接口进行日志检查
    logging:
      level:
        com:
          atguigu:
            cloud:
              apis:
                PayFeignApi: debug  #之后比如我们设置了重试机制,会将每次的具体访问信息打印出来
    
    
    
    # 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.Exception
    #    instances:
    #      cloud-payment-service: #代表将上面断路器的配置 配到80消费者
    #        baseConfig: default #默认配置
    
    
    
    # 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.Exception
    #    instances:
    #      cloud-payment-service: #代表将上面断路器的配置 配到80消费者
    #        baseConfig: default
    
    
    ####resilience 4j bulkhead 的例子
    resilience4j:
      bulkhead:
        configs:
          default:
            maxConcurrentCalls: 2 # 隔离允许并发线程执行的最大数量 eg:当两个线程在访问服务模块时,此时第3个及以后的线程再							 访问就会fallback
            maxWaitDuration: 1s # 当达到并发调用数量时,新的线程的阻塞时间,我只愿意等待1秒,过时不候进舱壁兜底fallback
        instances:
          cloud-payment-service: #舱壁隔离应用导这个服务
            baseConfig: default #使用上面我们自定义的配置,覆盖默认的
      timelimiter:
        configs:
          default:
            timeout-duration: 20s
    
  • 消费者模块Controller

    需要加入舱壁隔离注解@Bulkhead+注意:type = Bulkhead.Type.SEMAPHORE 以及服务降级方法fallbackMethod

     /**
         * 案例:演示消费者/客户端80通过Feign接口调用8001舱壁服务
         */
        /**
         *(船的)舱壁,隔离
         * @param id
         * @return
         */
        @GetMapping(value = "/feign/pay/bulkhead/{id}")
        //舱壁隔离注解
        @Bulkhead(name = "cloud-payment-service",fallbackMethod = "myBulkheadFallback",type = Bulkhead.Type.SEMAPHORE)
        //        服务提供者模块               服务降级:如果失败,就执行下面的myBulkheadFallback()方法来返回给消费者/客户端   指定隔离的实现方式
        public String myBulkhead(@PathVariable("id") Integer id)
        {
            return payFeignApi.myBulkhead(id);
        }
        public String myBulkheadFallback(Throwable t)
        {
            return "myBulkheadFallback,隔板超出最大数量限制,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~";
        }
    
  • 服务端模块Controller

     //=========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();
        }
    

    Feign接口

     /**
         *Resilience4j bulkhead 的例子
         * @param id
         * @return
         */
        @GetMapping(value = "/pay/bulkhead/{id}")
        public String myBulkhead(@PathVariable("id") Integer id);
    

固定线程池舱壁(FixedThreadPoolBulkhead)

FixedThreadPoolBulkhead的功能与SemaphoreBulkhead一样也是用于限制并发执行的次数的,但是二者的实现原理存在差别而且表现效果也存在细微的差别。FixedThreadPoolBulkhead使用一个固定线程池和一个等待队列来实现舱壁。

当线程池中存在空闲时,则此时进入系统的请求将直接进入线程池开启新线程或使用空闲线程来处理请求。

当线程池中无空闲时时,接下来的请求将进入等待队列,

若等待队列仍然无剩余空间时接下来的请求将直接被拒绝,

在队列中的请求等待线程池出现空闲时,将进入线程池进行业务处理。

另外:ThreadPoolBulkhead只对CompletableFuture方法有效,所以我们必创建返回CompletableFuture类型的方法

FixedThreadPoolbulkhead的配置项:

  • maxThreadPoolSize:配置最大线程池的大小,默认值是Runtime.getRuntime().availableProcessors
  • coreThreadPoolSize:配置核心线程池的大小,默认值是Runtime.getRuntime().availableProcessors - 1
  • queueCapacity:配置队列的容量,默认值是100
  • keepAliveDuration:当线程数大于核心数时,这是多余空闲线程在终止前等待新任务的最长时间,默认值是20ms

使用步骤:

  • 在服务消费端pom文件加入相关依赖(同上方的信号舱舱壁隔离的一样的)

  • yml文件:(客户端模块)

    server:
      port: 80
    
    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:
          client:
            config:
              default:
                #连接超时时间
                connectTimeout: 3000 #设置设置全局超时时间为3s
                #读取超时时间
                readTimeout: 3000
              cloud-payment-service: # //指定服务名:为这个服务单独配置超时时间,单个配置的超时时间会覆盖全局配置!!!
                #连接超时时间
                connect-timeout: 2000
                #读取超时时间
                read-timeout: 2000
          httpclient:
            hc5:
              enabled: true
          compression:
            request:
              enabled: true  #请求开启压缩功能
              min-request-size: 2048 #最小触发压缩的大小
              mime-types: text/xml,application/xml,application/json #触发压缩数据类型
            response:    #响应也开启压缩功能
              enabled: true
             #开启circuitbreaker和分组激活
          circuitbreaker:
            enabled: true
            #group:
            #    enabled: true # 演示Bulkhead.Type.THREADPOOL时spring.cloud.openfeign.circuitbreaker.group.enabled
          #设为false新启线程和原来主线程脱离了。
    
    #feign日志以什么级别监控哪个接口:即对哪个Feign接口进行日志检查
    logging:
      level:
        com:
          atguigu:
            cloud:
              apis:
                PayFeignApi: debug  #之后比如我们设置了重试机制,会将每次的具体访问信息打印出来
    
    
    
    # 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.Exception
    #    instances:
    #      cloud-payment-service: #代表将上面断路器的配置 配到80消费者
    #        baseConfig: default #默认配置
    
    
    
    # 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.Exception
    #    instances:
    #      cloud-payment-service: #代表将上面断路器的配置 配到80消费者
    #        baseConfig: default
    
    
    ####resilience 4j bulkhead 的例子
    # (1)信号量舱壁
    #resilience4j:
    #  bulkhead:
    #    configs:
    #      default:
    #        maxConcurrentCalls: 2 # 隔离允许并发线程执行的最大数量
    #        maxWaitDuration: 1s # 当达到并发调用数量时,新的线程的阻塞时间,我只愿意等待1秒,过时不候进舱壁兜底fallback
    #    instances:
    #      cloud-payment-service: #舱壁隔离应用导这个服务
    #        baseConfig: default #使用上面我们自定义的配置,覆盖默认的
    #  timelimiter:
    #    configs:
    #      default:
    #        timeout-duration: 20s
    
    ####resilience4j bulkhead -THREADPOOL的例子
    resilience4j:
      timelimiter:
        configs:
          default:
            timeout-duration: 10s #timelimiter默认限制远程1s,超过报错不好演示效果所以加上10秒
      thread-pool-bulkhead:
        configs:
          default:
            core-thread-pool-size: 1
            max-thread-pool-size: 1    #最多并发数是2个,一个在max,一个在queue =2
            queue-capacity: 1          #1个在max+1个在队列,第3个来就报错了
        instances:                     #max+queue=最大容纳数
          cloud-payment-service: #被调用服务名称
            baseConfig: default
      # spring.cloud.openfeign.circuitbreaker.group.enabled 请设置为false 新启线程和原来主线程脱离
    
    
    
  • 消费端Controller

    需要加入舱壁隔离注解@Bulkhead,注意:type = Bulkhead.Type.THREADPOOL, 且服务降级方法返回值类型必须为CompletableFuture

     /**
         * 案例:演示消费者/客户端80通过Feign接口调用8001舱壁服务
         * (2)treadPool
         */
        /**
         *(船的)舱壁,隔离
         * @param id
         * @return
         */
    
        @GetMapping(value = "/feign/pay/bulkhead/{id}")
        //舱壁隔离注解
        @Bulkhead(name = "cloud-payment-service",fallbackMethod = "myBulkheadPoolFallback",type = Bulkhead.Type.THREADPOOL)
        //        服务提供者模块               服务降级:如果失败,就执行下面的myBulkheadFallback()方法来返回给消费者/客户端   指定隔离的实现方式: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)//在fallback中多加个Throwable t即可
        {
            return CompletableFuture.supplyAsync(() -> "Bulkhead.Type.THREADPOOL,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~");
        }
    

3.Resilience4J限流:

限流是频率的控制,限制最大访问流量。系统能提供的最大并发是有限的,同时来的请求又太多,就需要限流。

就是通过对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速,以保护应用系统,一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理。

比如商城秒杀业务,瞬时大量请求涌入,服务器忙不过就只好排队限流了,和去景点排队买票和去医院办理业务排队等号道理相同。

感觉限流类似于舱壁:舱壁是没有可用位置/线程就返回兜底。限流是没有位置就等待

常见限流算法:

(详细内容见尚硅谷笔记)

  • 漏斗算法:让请求像水流过漏斗一样,请求的处理效率是固定的,来不及处理的请求在“漏斗”中等待。漏斗算法对于存在突发特性的流量来说缺乏效率。
  • 令牌桶算法:准备一定的令牌数,请求通过时,如果令牌有剩余,该请求就领取一个令牌,并进入处理,处理完后释放令牌。没有令牌剩余的时候就是到等待队列中等待令牌。这种方法是SpringCloud默认使用的算法。
  • 滚动时间窗:取一段时间内,允许固定数量的请求进入,如果这段时间内超过该固定数量,就拒绝或者排队,等下一个时间段进入。但是间隔临界的一段时间内的请求超过系统限制,可能导致系统被压垮。
  • 滑动时间窗:滑动事件窗口是把固定时间片段进行划分,并且随着时间移动,移动方式为开始时间点变为时间列表中的第二个时间点,结束时间点增加一个时间点,不断重复。达到一种滑片滑动的效果。

使用步骤:

  • 在消费端模块的pom文件中引入依赖:

    <!--resilience4j-ratelimiter-->
    <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
    
  • 消费端controller

    加入@RateLimiter注解,写好服务提供者和兜底方法

    /**
         * 案例:演示80消费端通过Feign接口调用8001服务提供者
         * 限流:
         * @param id
         * @return
         */
    
        @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ㄒ)/~~";
        }
    
        //之后,在客户端访问这个地址,如果1s内访问次数大于2次就会被限流,返回兜底方法