1.简介

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

Resilience4i 是一个专为函数式编程设计的轻量级容错库。Resilience4i 提供高阶函数(装饰器),以通过断路器、速率 限制器、重试或隔板增强任何功能接口、lambda 表达式或方法引用。您可以在任何函数式接口、lambda 表达式或方 法引用上堆叠多个装饰器。优点是您可以选择您需要的装饰器,而没有其他选择。 Resilience4i 2 需要Java 17.

2.熔断(CircuitBreaker)服务熔断+服务降级

断路器有三个普通状态: 关闭 (CLOSED),开启 (OPEN),半开(HALF OPEN) 还有两个特殊状志: 禁用(DISABLED)、强制开启(FORCED OPEN)。 当熔断器关闭时,所有的请求都会通过熔断器。

如果失败率超过设定的闻值,熔断器就会从关闭状态转换到打开状态,这时所有的请求都会被拒绝. 当经过一段时间后,熔断器会从打开状态转换到半开状态,这时仅有一定数量的请求会被放入,并重新计算失败率 如果失败率超过闯值,则变为打开状态,如果失败率低于阔值,则变为关闭状态。

断路器使用滑动窗口来存储和统计调用的结果。你可以选择基于调用数量的滑动窗口或者基于时间的滑动窗口。 基于访问数量的滑动窗口统计最近N次调用的返回结果。 基于时间的滑动窗口统计最近N秒的调用的返回结果。

相关配置说明:

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状态需要等待的时间

消费者侧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>

2.1 计数滑动窗口

消费端侧yml

openfeign下
      # 开启circuitbreaker和分组激活 spring.cloud.openfeign.circuitbreaker.enabled
      circuitbreaker:
        enabled: true
        #演示固定线程池舱壁时关闭改配置
        group:
            enabled: true #没开分组永远不用分组的配置。精确优先、分组次之(开了分组)、默认最后
       
resilience4j:
  # Resilience4j CircuitBreaker 按照次数:COUNT_BASED 的例子
  #  6次访问中当执行方法的失败率达到50%时CircuitBreaker将进入开启OPEN状态(保险丝跳闸断电)拒绝所有请求。
  #  等待5秒后,CircuitBreaker 将自动从开启OPEN状态过渡到半开HALF_OPEN状态,允许一些请求通过以测试服务是否恢复正常。
  #  如还是异常CircuitBreaker 将重新进入开启OPEN状态;如正常将进入关闭CLOSE闭合状态恢复正常处理请求。
  circuitbreaker:
    configs:
      default:
        #设置50%的调用失败时打开断路器,超过失败请求百分⽐CircuitBreaker变为OPEN状态。
        failureRateThreshold: 50
        # 滑动窗口的类型
        slidingWindowType: COUNT_BASED
        #滑动窗⼝的⼤⼩配置COUNT_BASED表示6个请求,配置TIME_BASED表示6秒
        slidingWindowSize: 6
        #断路器计算失败率或慢调用率之前所需的最小样本(每个滑动窗口周期)。
        #如果minimumNumberOfCalls为10,则必须最少记录10个样本,然后才能计算失败率。
        #如果只记录了9次调用,即使所有9次调用都失败,断路器也不会开启。
        minimumNumberOfCalls: 6
        # 是否启用自动从开启状态过渡到半开状态,默认值为true。
        #如果启用,CircuitBreaker将自动从开启状态过渡到半开状态,并允许一些请求通过以测试服务是否恢复正常
        automaticTransitionFromOpenToHalfOpenEnabled: true
        #从OPEN到HALF_OPEN状态需要等待的时间
        waitDurationInOpenState: 5s
        #半开状态允许的最大请求数,默认值为10。
        #在半开状态下,CircuitBreaker将允许最多permittedNumberOfCallsInHalfOpenState个请求通过,如果其中有任何一个请求失败,CircuitBreaker将重新进入开启状态。
        permittedNumberOfCallsInHalfOpenState: 2
        recordExceptions:
        - java.lang.Exception
    instances:
      cloud-payment-service:
        baseConfig: default

消费端接口

@CircuitBreaker(name = "cloud-payment-service", fallbackMethod = "回调函数")

2.2 时间滑动窗口

#  Resilience4j CircuitBreaker 按照时间:TIME_BASED 的例子
#  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:
#        baseConfig: default

2.3 总结

当满足一定的峰值和失败率达到一定条件后,断路器将会进入OPEN状态(保险丝跳闸),服务熔断 当OPEN的时候,所有请求都不会调用主业务逻辑方法,而是直接走fallbackmetnod兜底背锅方法,服务降级 一段时间后,这个时候断路器会从OPEN进入到HALF OPEN半开状态,会放几个请求过去探探链路是否通过: 若成功,断路器会关闭CLOSE(类似保险丝闭合,恢复可用) 若失败,继续开启。重复上述

3.隔离(BulkHead)

3.1 SemaphoreBulkhead(信号量舱壁)

信号量舱壁(SemaphoreBulkhead)原理

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

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

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

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

消费端pom

<!--resilience4j-bulkhead-->
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-bulkhead</artifactId>
</dependency>

消费端yml

bulkhead:
    configs:
      default:
        maxConcurrentCalls: 2 # 隔离允许并发线程执行的最大数量
        maxWaitDuration: 1s # 当达到并发调用数量时,新的线程的阻塞时间,我只愿意等待1秒,过时不候进舱壁兜底fallback
    instances:
      cloud-payment-service:
        baseConfig: default
  timelimiter:
    configs:
      default:
        timeout-duration: 20s

消费端接口

@Bulkhead(name = "cloud-payment-service",fallbackMethod = "隔离降级回调函数",type = Bulkhead.Type.SEMAPHORE)

3.2 FixedThreadPoolBulkhead(固定线程池舱壁)

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

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

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

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

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

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

消费端pom

<!--resilience4j-bulkhead-->
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-bulkhead</artifactId>
</dependency>

消费端yml

####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
        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ㄒ)/~~");
}

4.限流(RateLimiter)

消费端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

消费端接口

@RateLimiter(name = "cloud-payment-service",fallbackMethod = "myRatelimitFallback")