Resilience4J服务熔断隔离与限流

为了保障文章的流畅性(文章穿插大量的环境搭建没意思,会干扰文章的主题,无聊的很),将环境的搭建与测试,工具的版本说明放了文末: 六、环境搭建。

一、Circuit Breaker是什么

1.1、官网

https://spring.io/projects/spring-cloud-circuitbreaker

image-20240824083558893

image-20240824083623090

1.2、实现原理

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

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

https://resilience4j.readme.io/docs/circuitbreaker#create-and-configure-a-circuitbreaker

image-20240824084633020

image-20240824084701502

1.3、总结

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

image-20240824085248952

二、Resilience4J

2.1、概述

https://github.com/resilience4j/resilience4j#1-introduction

image-20240824085526113

image-20240824085550046

2.2、作用

https://github.com/resilience4j/resilience4j#3-overview

image-20240824172809360

image-20240824090115846

2.3、官网

https://github.com/resilience4j/resilience4j

image-20240824090357764

三、实战:服务熔断与降级

3.1、概念解释

**服务熔断:**是指当某个服务的调用失败次数或异常比例达到一定阈值时,自动切断对该服务的调用,让请求快速失败,避免影响其他服务而导致雪崩效应。熔断后,一段时间内不再调用该服务,直到服务恢复正常或者超过最大等待时间。

​ 当服务A调用的某个服务B不可用时,上游服务A为了保证自己不受影响,从而不再调用服务B,直接返回一个结果,减轻服务A和服务B的压力,直到服务B恢复。

​ 类比保险丝,保险丝闭合状态(CLOSE)可以正常使用,当达到最大服务访问后,直接拒绝访问跳闸限电(OPEN),此刻调用方会接受服务降级的处理并返回友好兜底提示就是家里保险丝,从闭合CLOSE供电状态→跳闸OPEN打开状态

**服务降级:**是指当某个服务不可用或响应缓慢时,提供一个备用的处理逻辑,例如返回默认值、缓存值、错误提示等,以保证服务的可用性和容错性。降级可以在熔断时触发,也可以在其他情况下触发,例如系统负载过高、资源紧张等。

​ 服务器忙,请稍后再试。不让客户端等待并立刻返回一个友好提示,fallback

相同点:

  1. 都是为了防止系统崩溃

  2. 都让用户体验到某些功能暂时不可用

不同点:熔断是下游服务故障触发的,降级是为了降低系统负载

3.2、断路器的三大状态

https://resilience4j.readme.io/docs/circuitbreaker#create-and-configure-a-circuitbreaker

image-20240824084633020

CircuitBreaker 通过有限状态机实现,具有三种正常状态:CLOSED、OPEN 和 HALF_OPEN,以及两种特殊状态 DISABLED 和 FORCED_OPEN。

3.3、断路器三大状态之间的转化

https://resilience4j.readme.io/docs/circuitbreaker#failure-rate-and-slow-call-rate-thresholds

image-20240824093506648

image-20240824093636065

原文:

The state of the CircuitBreaker changes from CLOSED to OPEN when the failure rate is equal or greater than a configurable threshold. For example when more than 50% of the recorded calls have failed.
By default all exceptions count as a failure. You can define a list of exceptions which should count as a failure. All other exceptions are then counted as a success, unless they are ignored. Exceptions can also be ignored so that they neither count as a failure nor success.

The CircuitBreaker also changes from CLOSED to OPEN when the percentage of slow calls is equal or greater than a configurable threshold. For example when more than 50% of the recorded calls took longer than 5 seconds. This helps to reduce the load on an external system before it is actually unresponsive.

The failure rate and slow call rate can only be calculated, if a minimum number of calls were recorded. For example, if the minimum number of required calls is 10, then at least 10 calls must be recorded, before the failure rate can be calculated. If only 9 calls have been evaluated the CircuitBreaker will not trip open even if all 9 calls have failed.

The CircuitBreaker rejects calls with a CallNotPermittedException when it is OPEN. After a wait time duration has elapsed, the CircuitBreaker state changes from OPEN to HALF_OPEN and permits a configurable number of calls to see if the backend is still unavailable or has become available again. Further calls are rejected with a CallNotPermittedException, until all permitted calls have completed.
If the failure rate or slow call rate is then equal or greater than the configured threshold, the state changes back to OPEN. If the failure rate and slow call rate is below the threshold, the state changes back to CLOSED.

The Circuit Breaker supports two more special states, DISABLED (always allow access) and FORCED_OPEN (always deny access). In these two states no Circuit Breaker events (apart from the state transition) are generated, and no metrics are recorded. The only way to exit from those states are to trigger a state transition or to reset the Circuit Breaker.

翻译:

当故障率等于或大于可配置阈值时,断路器的状态将从关闭变为打开。例如,当超过 50% 的记录呼叫失败时。
默认情况下,所有异常都算作失败。您可以定义应算作失败的异常列表。然后,除非忽略所有其他异常,否则它们将被视为成功。也可以忽略异常,这样它们既不算作失败也不算作成功。

当慢速呼叫的百分比等于或大于可配置阈值时,断路器也会从关闭变为打开。例如,当超过 50% 的记录呼叫花费的时间超过 5 秒时。这有助于在外部系统实际无响应之前减少其负载。

只有在记录了最少数量的呼叫时,才能计算故障率和慢速呼叫率。例如,如果所需的最少呼叫数为 10,则必须记录至少 10 个呼叫,然后才能计算故障率。如果仅评估了 9 个调用,即使所有 9 个调用都失败,CircuitBreaker 也不会跳闸。

CircuitBreaker 在 OPEN 时会拒绝调用并抛出 CallNotPermittedException。等待一段时间后,CircuitBreaker 状态将从 OPEN 更改为 HALF_OPEN,并允许可配置数量的调用,以查看后端是否仍然不可用或再次可用。其他调用将被拒绝并抛出 CallNotPermittedException,直到所有允许的调用都完成。
如果失败率或慢速调用率等于或大于配置的阈值,则状态将变回 OPEN。如果失败率和慢速调用率低于阈值,则状态将变回 CLOSED。

Circuit Breaker 支持另外两种特殊状态,DISABLED(始终允许访问)和 FORCED_OPEN(始终拒绝访问)。在这两种状态下,不会生成任何 Circuit Breaker 事件(状态转换除外),也不会记录任何指标。退出这些状态的唯一方法是触发状态转换或重置断路器。

简单概述:

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

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

    • 如果失败率超过设定的阈值,熔断器就会从关闭状态转换到打开状态,这时所有的请求都会被拒绝。
    • 当经过一段时间后,熔断器会从打开状态转换到半开状态,这时仅有一定数量的请求会被放入,并重新计算失败率
    • 如果失败率超过阈值,则变为打开状态,如果失败率低于阈值,则变为关闭状态。
  • 断路器使用滑动窗口来存储和统计调用的结果。你可以选择于调用数量的滑动窗口或者基于时间的滑动窗口。

    • 基于访问数量的滑动窗口统计了最近N次调用的返回结果。

    • 居于时间的滑动窗口统计了最近N秒的调用反回结果。

  • 除此以外,熔断器还会有两种特殊状态:DISABLED(始终允许访问)和FORCED_OPEN(始终拒绝访问)。

    • 这两个状态不会生成熔断器事件(除状态装换外),并且不会记录事件的成功或者失败。
    • 退出这两个状态的唯一方法是触发状态转换或者重置熔断器。

3.4、配置参数

https://resilience4j.readme.io/docs/circuitbreaker#create-and-configure-a-circuitbreaker

image-20240824100602663

非官方的中文文档翻译:

https://github.com/lmhmhl/Resilience4j-Guides-Chinese/blob/main/core-modules/CircuitBreaker.md

image-20240824101539145

创建和配置CircuitBreaker

你可以自定义CircuitBreakerConfig,为了创建自定义的CircuitBreakerConfig,你可以使用CircuitBreakerConfig建造器,你可以使用建造者模式来配置下面的属性。

配置属性默认值描述
failureRateThreshold50以百分比配置失败率阈值。当失败率等于或大于阈值时,断路器状态并关闭变为开启,并进行服务降级。
slowCallRateThreshold100以百分比的方式配置,断路器把调用时间大于slowCallDurationThreshold的调用视为满调用,当慢调用比例大于等于阈值时,断路器开启,并进行服务降级。
slowCallDurationThreshold60000 [ms]配置调用时间的阈值,高于该阈值的呼叫视为慢调用,并增加慢调用比例。
permittedNumberOfCallsInHalfOpenState10断路器在半开状态下允许通过的调用次数。
maxWaitDurationInHalfOpenState0断路器在半开状态下的最长等待时间,超过该配置值的话,断路器会从半开状态恢复为开启状态。配置是0时表示断路器会一直处于半开状态,直到所有允许通过的访问结束。
slidingWindowTypeCOUNT_BASED配置滑动窗口的类型,当断路器关闭时,将调用的结果记录在滑动窗口中。滑动窗口的类型可以是count-based或time-based。如果滑动窗口类型是COUNT_BASED,将会统计记录最近slidingWindowSize次调用的结果。如果是TIME_BASED,将会统计记录最近slidingWindowSize秒的调用结果。
slidingWindowSize100配置滑动窗口的大小。
minimumNumberOfCalls100断路器计算失败率或慢调用率之前所需的最小调用数(每个滑动窗口周期)。例如,如果minimumNumberOfCalls为10,则必须至少记录10个调用,然后才能计算失败率。如果只记录了9次调用,即使所有9次调用都失败,断路器也不会开启。
waitDurationInOpenState60000 [ms]断路器从开启过渡到半开应等待的时间。
automaticTransition FromOpenToHalfOpenEnabledfalse如果设置为true,则意味着断路器将自动从开启状态过渡到半开状态,并且不需要调用来触发转换。创建一个线程来监视断路器的所有实例,以便在WaitDurationInOpenstate之后将它们转换为半开状态。但是,如果设置为false,则只有在发出调用时才会转换到半开,即使在waitDurationInOpenState之后也是如此。这里的优点是没有线程监视所有断路器的状态。
recordExceptionsempty记录为失败并因此增加失败率的异常列表。 除非通过ignoreExceptions显式忽略,否则与列表中某个匹配或继承的异常都将被视为失败。 如果指定异常列表,则所有其他异常均视为成功,除非它们被ignoreExceptions显式忽略。
ignoreExceptionsempty被忽略且既不算失败也不算成功的异常列表。 任何与列表之一匹配或继承的异常都不会被视为失败或成功,即使异常是recordExceptions的一部分。
recordExceptionthrowable -> true· By default all exceptions are recored as failures.一个自定义断言,用于评估异常是否应记录为失败。 如果异常应计为失败,则断言必须返回true。如果出断言返回false,应算作成功,除非ignoreExceptions显式忽略异常。
ignoreExceptionthrowable -> false By default no exception is ignored.自定义断言来判断一个异常是否应该被忽略,如果应忽略异常,则谓词必须返回true。 如果异常应算作失败,则断言必须返回false。

默认CircuitBreaker.java配置类,官网源码:

https://github.com/resilience4j/resilience4j/blob/master/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/CircuitBreakerConfig.java

image-20240824094942015

常用展现总结:

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

3.5、实战

3.5.1、需求

6次访问中当执行方法的失败率达到50%时CircuitBreaker将进入开启OPEN状态(保险丝跳闸断电)拒绝所有请求。
等待5秒后,CircuitBreaker 将自动从开启OPEN状态过渡到半开HALF_OPEN状态,允许一些请求通过以测试服务是否恢复正常。
如还是异常CircuitBreaker 将重新进入开启OPEN状态;如正常将进入关闭CLOSE闭合状态恢复正常处理请求

3.5.2、COUNT_BASED(计数的滑动窗口)

https://resilience4j.readme.io/docs/circuitbreaker#count-based-sliding-window

image-20240824135906157

image-20240824102438658

3.5.2.1、改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>

image-20240824130644041

3.5.2.2、写YML
    # 开启circuitbreaker和分组激活 spring.cloud.openfeign.circuitbreaker.enabled
    circuitbreaker:
      enabled: true
      group:
        enabled: true #没开分组永远不用分组的配置。精确优先、分组次之(开了分组)、默认最后

image-20240824132021449

#===========计数滑动窗口=====================================
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:
      # 谁来用(服务名)
      resilience4j-provider:
        baseConfig: default
#=========================================================================================

image-20240824132946976

3.5.2.3、写代码
package com.resilience4j.consumer.web;

import com.resilience4j.feginapi.apis.FeignAPI;
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;

/**
 * @Author: 史小创
 * @Time: 2024/8/24 下午12:48
 * @Description:
 */

@RestController
public class Resilience4JConsumerController {
    @Resource
    private FeignAPI feignAPI;

    @GetMapping(value = "/consumer/circuit/{id}")
    @CircuitBreaker(name = "resilience4j-provider", fallbackMethod = "myCircuitFallback")
    public String myCircuitBreaker(@PathVariable("id") Integer id) {
        return feignAPI.myCircuit(id);
    }

    /**
     * myCircuitFallback就是服务降级后的兜底处理方法
     * @param id
     * @param t
     * @return
     */
    public String myCircuitFallback(Integer id, Throwable t) {
        // 这里是容错处理逻辑,返回备用结果
        return "myCircuitFallback,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~";
    }

}

image-20240824134144318

3.5.2.4、测试
3.5.2.4.1、正确的与错误的
http://127.0.0.1:8766/consumer/circuit/88

image-20240824134309841

http://127.0.0.1:8766/consumer/circuit/-4

image-20240824134412956

image-20240824134442788

3.5.2.4.2、一次错误一次正确
http://127.0.0.1:8766/consumer/circuit/88
http://127.0.0.1:8766/consumer/circuit/-4

看动态图:

4f6c1dde-b9be-4738-b4ed-2dc05691bb3a

50%错误后触发熔断并给出服务降级,告知调用者服务不可用
此时就算是翰入正确的访问地址也无法调用服务我明明是正确的也不让用川(TOT)~),它还在断路中(PEN状态),一会儿过度到绊开并继续王确他扯访问,侵慢切换回C.OSE状态,可以正常访问了链酪回复

3.5.2.4.3、多次填写错误
http://127.0.0.1:8766/consumer/circuit/88
http://127.0.0.1:8766/consumer/circuit/-4

看动态图:

60e21cd0-c02e-44dd-bfb3-db7b12a123ef

多次故意填写错误值(负4),然后慢慢填写正确值(正整数88),发现刚开始不满足条件,就算是正确的访问地址也不能进行

3.5.3、TIME_BASED(时间的滑动窗口)

https://resilience4j.readme.io/docs/circuitbreaker#time-based-sliding-window

image-20240824140015401

image-20240824140103556

3.5.3.1、写yml
##===========按照时间: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:
      resilience4j-provider:
        baseConfig: default

image-20240824142725926

3.5.3.2、测试
3.5.3.2.1、一次超时,一次正常访问,同时进行

image-20240824144946550

http://127.0.0.1:8766/consumer/circuit/88
http://127.0.0.1:8766/consumer/circuit/9999

a4c025cf-1ee5-4a4c-9722-007cb68eba43

3.5.3.2.2、第1~4个超时,整多一点干4个,一次正常访问,同时进行
http://127.0.0.1:8766/consumer/circuit/88
http://127.0.0.1:8766/consumer/circuit/9999

8c39714e-d8eb-4941-820d-08855d6e8015

正常访问也受到了牵连,因为服务熔断不能访问了

image-20240824145013266

3.6、总结

断路器开启或者关闭的条件

image-20240824145150755

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

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

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

如成功,断路器会关闭CLOSE(类似保险丝闭合,恢复可用);如失败,继续开启。重复上述

建议不要混合用,推荐按照调用次数count_based,一家之言仅供参考

四、实战:隔离

4.1、官网

https://resilience4j.readme.io/docs/bulkhead#introduction

image-20240824145506228

image-20240824145702722

4.2、作用

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

4.3、实战

4.3.1、信号量舱壁(SemaphoreBulkhead)

4.3.1.1、概述

基本上就是我们JUC信号灯内容的同样思想

image-20240824150236335

信号量舱壁(SemaphoreBulkhead)原理

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

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

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

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

https://github.com/resilience4j/resilience4j/blob/master/resilience4j-bulkhead/src/main/java/io/github/resilience4j/bulkhead/internal/SemaphoreBulkhead.java

image-20240824151106268

image-20240824151203052

4.3.1.2、改POM
 <!--resilience4j-bulkhead-->
        <dependency>
            <groupId>io.github.resilience4j</groupId>
            <artifactId>resilience4j-bulkhead</artifactId>
        </dependency>

image-20240824152714126

4.3.1.3、写YML
https://resilience4j.readme.io/docs/bulkhead#create-and-configure-a-bulkhead

image-20240824152835642

你可以提供一个自定义的全局BulkheadConfig,你可以使用BulkheadConfig建造者模式来创建自定义的全局BulkheadConfig,可以使用builder来配置下面的属性。

配置属性默认值描述
maxConcurrentCalls25隔离允许线程并发执行的最大数量
maxWaitDuration0当达到并发调用数量时,新的线程执行时将被阻塞,这个属性表示最长的等待时间。
##resilience4j bulkhead 的例子
resilience4j:
  bulkhead:
    configs:
      default:
        maxConcurrentCalls: 2 # 隔离允许并发线程执行的最大数量
        maxWaitDuration: 1s # 当达到并发调用数量时,新的线程的阻塞时间,我只愿意等待1秒,过时不候进舱壁兜底fallback
    instances:
      resilience4j-provider:
        baseConfig: default
  timelimiter:
    configs:
      default:
        timeout-duration: 20s

image-20240824154526010

4.3.1.4、代码
 /**
     * (船的)舱壁,隔离
     *
     * @param id
     * @return
     */
    @GetMapping(value = "/consumer/bulkhead/{id}")
    @Bulkhead(name = "resilience4j-provider", fallbackMethod = "myBulkheadFallback", type = Bulkhead.Type.SEMAPHORE)
    public String myBulkhead(@PathVariable("id") Integer id) {
        return feignAPI.myBulkhead(id);
    }

    public String myBulkheadFallback(Throwable t) {
        return "myBulkheadFallback,隔板超出最大数量限制,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~";
    }

image-20240824154545084

4.3.1.5、测试
http://127.0.0.1:8766/consumer/bulkhead/88
http://127.0.0.1:8766/consumer/bulkhead/9999

6e599ba3-3222-49ce-b201-0fccc76881f6

4.3.2、固定线程池舱壁(FixedThreadPoolBulkhead)

4.3.2.1、概述

image-20240824162522200

固定线程池舱壁(FixedThreadPoolBulkhead)

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

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

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

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

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

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

https://github.com/resilience4j/resilience4j/blob/master/resilience4j-bulkhead/src/main/java/io/github/resilience4j/bulkhead/internal/FixedThreadPoolBulkhead.java

image-20240824162747208

image-20240824162921471

4.3.2.2、写YML
https://resilience4j.readme.io/docs/bulkhead#create-and-configure-a-threadpoolbulkhead

image-20240824164857329

###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:
      resilience4j-provider:
        baseConfig: default
#  spring.cloud.openfeign.circuitbreaker.group.enabled 请设置为false 新启线程和原来主线程脱离
#操作如下:
#circuitbreaker:
#  enabled: true
#  group:
#    #        enabled: true #没开分组永远不用分组的配置。精确优先、分组次之(开了分组)、默认最后
#    enabled: false #没开分组永远不用分组的配置。精确优先、分组次之(开了分组)、默认最后
4.3.2.3、代码
/**
     * (船的)舱壁,隔离  threadPool
     *
     * @param id
     * @return
     */
    @GetMapping(value = "/consumer/pool/bulkhead/{id}")
    @Bulkhead(name = "resilience4j-provider", fallbackMethod = "myBulkheadPoolFallback", type = Bulkhead.Type.THREADPOOL)
    public CompletableFuture<String> myBulkheadTHREADPOOL(@PathVariable("id") Integer id) {
        System.out.println(Thread.currentThread().getName() + "\t" + "---开始进入");
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "\t" + "---准备离开");

        return CompletableFuture.supplyAsync(() -> feignAPI.myBulkhead(id) + "\t" + "Bulkhead.Type.THREADPOOL");
    }

    public CompletableFuture<String> myBulkheadPoolFallback(Integer id, Throwable t) {
        return CompletableFuture.supplyAsync(() -> "Bulkhead.Type.THREADPOOL,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~");
    }

image-20240824165223464

4.2.3.4、测试
http://127.0.0.1:8766/consumer/pool/bulkhead/1
http://127.0.0.1:8766/consumer/pool/bulkhead/2
http://127.0.0.1:8766/consumer/pool/bulkhead/3
http://127.0.0.1:8766/consumer/pool/bulkhead/4

看动态图演示

3c4fda09-76fd-4767-9b91-c0944a9c593a

五、实战:限流

5.1、官网

https://resilience4j.readme.io/docs/ratelimiter

image-20240824165527156

5.2、作用

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

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

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

排队示意图

5.3、常见的限流算法

限流算法是指用于限制单位时间内服务的请求数量的算法,目的是防止服务被过高的请求压力所击垮。常见的限流算法包括计数器算法、滑动窗口算法、漏桶算法、令牌桶算法。

5.4、测试

5.4.1、写YML

####resilience4j ratelimiter 限流的例子
resilience4j:
  ratelimiter:
    configs:
      default:
        limitForPeriod: 2 #在一次刷新周期内,允许执行的最大请求数
        limitRefreshPeriod: 1s # 限流器每隔limitRefreshPeriod刷新一次,将允许处理的最大请求数量重置为limitForPeriod
        timeout-duration: 1 # 线程等待权限的默认等待时间
    instances:
      resilience4j-provider:
        baseConfig: default

image-20240824170514988

5.4.2、写代码

 /**
     * 限流
     *
     * @param id
     * @return
     */
    @GetMapping(value = "/consumer/ratelimit/{id}")
    @RateLimiter(name = "resilience4j-provider", fallbackMethod = "myRatelimitFallback")
    public String myRatelimit(@PathVariable("id") Integer id) {
        return feignAPI.myRatelimit(id);
    }

    public String myRatelimitFallback(Integer id, Throwable t) {
        return "你被限流了,禁止访问/(ㄒoㄒ)/~~";
    }

image-20240824170902732

5.4.3、测试

http://127.0.0.1:8766/consumer/ratelimit/88

183c28f9-4da7-46f8-bf94-29816dda1e72

六、环境搭建

SpringBoot+SpringCloud的版本:

<spring.boot.version>3.2.0</spring.boot.version>
<spring.cloud.version>2023.0.0</spring.cloud.version>

注册中心:

  <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>

远程调用:

   <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

环境初始化代码:

https://github.com/shixiaochuangjob/markdownfile/tree/main/20240824/init

image-20240824171607929

Consul

image-20240823181110455

docker pull consul:1.9.6
docker run -d -p 8500:8500 --restart=always --name=consul -v /opt/consul:/consul/data consul:1.9.6 agent -server -bootstrap -ui -node=1 -client='0.0.0.0'
http://192.168.200.129:8500/ui/dc1/services

image-20240823180526926

jdk:

image-20240824171950942

Maven

image-20240823181810973

IDEA

image-20240823181906302

代码汇总:

https://github.com/shixiaochuangjob/markdownfile/tree/main/20240824

image-20240824171731425

https://mp.weixin.qq.com/s?__biz=MzkwOTczNzUxMQ==&mid=2247484580&idx=1&sn=b015f45e9013b0a75cf2c5ccf3c7329f&chksm=c1376fecf640e6fa710494ddaabaf00dd19ae1aa9568876fc81fca6e4889098f50101d90e214#rd

在这里插入图片描述

  • 19
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你可以通过在Feign客户端上添加Resilience4j的拦截器来实现熔断逻辑。下面是一些步骤来实现这个过程: 1. 首先,确保你已经添加了Resilience4j的依赖到你的项目中。你可以在Maven或者Gradle配置文件中添加以下依赖: ```xml <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-spring-cloud2</artifactId> <version>1.6.1</version> </dependency> ``` 2. 创建一个实现Feign的RequestInterceptor接口的类,用于拦截Feign的请求。以下是一个示例: ```java import feign.RequestInterceptor; import feign.RequestTemplate; import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class Resilience4jFeignInterceptor implements RequestInterceptor { private final CircuitBreakerRegistry circuitBreakerRegistry; @Autowired public Resilience4jFeignInterceptor(CircuitBreakerRegistry circuitBreakerRegistry) { this.circuitBreakerRegistry = circuitBreakerRegistry; } @Override public void apply(RequestTemplate requestTemplate) { String serviceName = requestTemplate.feignTarget().name(); circuitBreakerRegistry.circuitBreaker(serviceName).executeRunnable(() -> { // 在这里执行你的Feign请求 requestTemplate.header("Authorization", "Bearer your-token"); }); } } ``` 3. 在你的Feign客户端接口上添加`configuration`属性,使用上述的拦截器类。例如: ```java import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; @FeignClient(name = "your-service-name", configuration = Resilience4jFeignInterceptor.class) public interface YourFeignClient { @GetMapping("/your-endpoint") String yourFeignMethod(); } ``` 这样,当你使用`YourFeignClient`接口的方法发送请求时,请求将会通过Resilience4j的断路器进行拦截和熔断处理。 请注意,上面的示例中使用了`CircuitBreakerRegistry`来获取相应的断路器实例。你需要根据你的实际需求进行配置和使用Resilience4j的断路器。另外,你还可以根据需要添加其他的Resilience4j功能,如限流、重试等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值