当前位置: 首页 > news >正文

服务容错治理框架resilience4jsentinel基础应用---微服务的限流/熔断/降级解决方案

写在前文:hystrix停止维护,不做总结;

本文主要总结sentinel和resilience4j这两个框架;另外额外补充面试可能会问到的限流算法;

目录

限流算法

漏桶算法

计数器算法

令牌桶算法

resilience4j与sentinel

resilience4j+springboot3环境搭建

Step1、环境准备

引入依赖

配置application.yml 

Config配置 --- 后续这个不生效,使用的是application.yml中的配置

定义全局处理类

定义模拟外部API调用类

Step2、限流RateLimiter

Application.yml中有两个配置

Java代码中的配置RateLimiterRegistry Bean

限流器使用

coontroller

service

另外一种写法

Step3、熔断CircuitBreaker

coontroller

 service

Step4、隔板Bulkhead --- 也可以当并发限制

controller

service

Step5、超时

controller

service

Step6、重试Retry

controller

service

Step7、综合使用

controller

service

Sentinel


限流算法

漏桶算法

原理:系统内部维护一个固定的容器,请求到达时,如果桶未满,就存入桶中,如果桶满了,就拒绝请求;以恒定速率处理请求;

优势:速率可控,输出流量平滑;

缺点:无法应对合理突发流量、临界值突变依然有问题

计数器算法

原理:将时间划分为固定窗口(比如1s一个窗口),在窗口内,维护一个计数器,每次请求来时,计数器+1,如果没有超过限制阈值那么就允许通过,否则就拒绝请求;时间窗口滑动时重置计数器;

缺点:如果0.8s到1s请求来了100个,其余时间为0,1s~1.2s请求来了100个其余时间为0,那么此时0.8s~1.2s总供处理了200个请求,但是0~0.8以及1.2~2s并没有处理;

令牌桶算法

原理:令牌生成器以固定速率向桶中添加令牌- --- 比如桶最大容量限制100个,有请求来时,获取获取一个令牌token,如果有令牌token那么允许通过,如果没有那么就拒绝请求/排队等待;

优势:可以应对突发流量(比如令牌生成速度为1s一个,桶容量为100个,那么峰值就是100个,往后即便流量再多也只能每秒获取一个token)

特征计数器算法漏桶算法令牌桶算法
流量突发处理❌ 窗口临界问题❌ 严格限制✅ 允许合理突发
流量平滑性❌ 窗口突变✅ 绝对平滑✅ 可配置平滑度
系统资源消耗✅ 低✅ 低✅ 中等
实现复杂度✅ 简单✅ 中等⚠️ 较高
实时调整能力❌ 困难⚠️ 一般✅ 灵活
典型应用简单接口限流硬件设备控制互联网高并发系统

resilience4j与sentinel

sentinel比较重(功能比较全面,但是配置项复杂、部署维护成本比较大),而resilience4j是轻量化(功能虽少,但是核心功能--降级/熔断/限流/重试/并发隔离功能都有);

在业务中如何选择,可以从下面几个方面考虑:

        1、系统是否采用springcloud alibaba技术体系;

        2、团队人数;

        3、学习成本;

resilience4j+springboot3环境搭建

Step1、环境准备

本文所有配置、依赖外部服务均在这里,后续不做详细解释

引入依赖

    <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.1.1</version><relativePath/> <!-- lookup parent from repository --></parent><dependencies>
<dependency><groupId>io.github.resilience4j</groupId><artifactId>resilience4j-spring-boot3</artifactId><version>2.3.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><!--       要使用resilience4j就必须要引入下面的AOP不然不会生效 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>
</dependencies>

配置application.yml 

server:port: 8080servlet:context-path: /fault
spring:application:name: fault-guard
# 如果要使用type = Bulkhead.Type.THREADPOOL线程隔离需要配置,不然系统会采用默认参数
#  task:
#    execution:
#      pool:
#        core-size: 5
#        max-size: 10
#        queue-capacity: 100
management:endpoint:health:show-details: always  # 设置为"always"意味着健康检查详情总是显示给所有访问者,包括详细的状态信息。endpoints:web:exposure:#  配置完下面以后就可以查看具体状态:### 查看Resilience4j熔断器状态:GET http://localhost:8080/fault/4j/actuator/circuitbreakers### 查看Resilience4j限流器状态:GET http://localhost:8080/fault/actuator/ratelimiters### 查看Resilience4j隔板状态:GET http://localhost:8080/fault/actuator/bulkheads### 查看Resilience4j重试状态:GET http://localhost:8080/fault/actuator/retries### 查看Resilience4j超时状态: GET http://localhost:8080/fault/actuator/timelimitersinclude: health,circuitbreakers,ratelimiters,bulkheads,retries,timelimiters # 指定要通过HTTP暴露的端点列表。
resilience4j:
# 限速 - Rate Limiterratelimiter:metrics:enabled: trueinstances:rateLimiterApi:                           # 自定义的限流器实例名称,该名称会在代码中引用。使用时:@RateLimiter(name="rateLimiterApi")register-health-indicator: true         # 注册健康指标,可用于健康检查端点# 60s以内,超过1次,即第2次就会被限流limit-for-period: 1                     # 每个时间段允许的请求数(即限流阈值)limit-refresh-period: 60s               # 限流刷新时间间隔(这里是60秒)timeout-duration: 0s                    # 获取许可的最大等待时间为0秒(即不等待)allow-health-indicator-to-fail: true    # 即使限流触发失败,健康检查也视为通过subscribe-for-events: true              # 启用事件订阅,可用于监听限流事件event-consumer-buffer-size: 50          # 限流事件消费者的缓冲区大小
#        baseConfig: default                    # 可以基础配置为default,这样就可以不用上面一项一项配置
#      compositeApi:                            # 配置多个,在使用的时候就可以@RateLimiter(name="compositeApi")
#        baseConfig: default
#  熔断 - Circuit breakercircuitbreaker:metrics:enabled: true                             # 启用指标收集,可以与Micrometer等监控系统集成instances:circuitBreakerApi:                        # 自定义的断路器实例名称,该名称会在代码中引用。使用时:@CircuitBreaker(name = "circuitBreakerApi", fallbackMethod = "circuitBreakerFallback")# 过去10次请求中,如果超过了50%(至少6次)失败,则断路器变为OPEN状态;此时服务只能进入到Controller,无法进入到Servicefailure-rate-threshold: 50              # 触发断路的失败请求比例阈值(%)。例如设置为50表示失败率达到50%时触发断路minimum-number-of-calls: 5              # 在一个滑动窗口内最少需要多少次调用才会计算失败率# 当断路器处于OPEN状态时,所有请求直接失败,不真实调用业务逻辑。并且5秒后自动变为 HALF-OPEN 状态。automatic-transition-from-open-to-half-open-enabled: true # 是否启用自动从 OPEN 状态切换到 HALF-OPEN 状态wait-duration-in-open-state: 5s         # 当断路器处于 OPEN 状态时,等待多久后尝试进入 HALF-OPEN 状态# 在 HALF-OPEN 状态下。最多允许 3个请求通过 去测试系统是否恢复。如果这3次请求都成功,则变为 CLOSED 状态;如果还有失败,则重新回到 OPEN。permitted-number-of-calls-in-half-open-state: 3   # 在 HALF-OPEN 状态下允许处理的请求数sliding-window-size: 10                 # 滑动窗口的长度,取决于 sliding-window-type 是次数还是时间sliding-window-type: count_based        # 滑动窗口类型,可选值:count_based(基于请求次数) time_based(基于时间窗口)
#        baseConfig: default
#      compositeApi:
#        baseConfig: default
#  隔板 - Bulkheadbulkhead:metrics:enabled: trueinstances:bulkheadApi:                # 自定义的舱壁实例名称,该名称会在代码中引用 @Bulkhead(name = "bulkheadApi", type = Bulkhead.Type.THREADPOOL, fallbackMethod = "bulkheadFallback")max-concurrent-calls: 2   # 允许的最大并发调用数量。意味着最多同时有2个线程可以进入并执行被保护的方法或服务调用。max-wait-duration: 1      # 请求在进入舱壁前等待的最大时间,如果隔板已满,每个线程只能等待 1 毫秒
#        baseConfig: default
#      compositeApi:
#        baseConfig: default
#  超时 - Time Limitertimelimiter:metrics:enabled: trueinstances:timeLimiterApi:               # 自定义的超时实例名称,该名称会在代码中引用@TimeLimiter(name = "timeLimiterApi", fallbackMethod = "timeLimiterFallback")timeout-duration: 2s        # 设置超时时间为2秒,即当操作超过这个时间还未完成,则认为超时。cancel-running-future: true # 当设置为true时,若操作超时,则尝试取消正在执行的操作(如果支持的话)。#        baseConfig: default#      compositeApi:#        baseConfig: default
# 重试 - Retryretry:metrics:enabled: true             # 是否开启度量统计,默认为false。如果设置为true,则会收集并报告此Retry的相关指标。legacy:enabled: true           # 是否启用遗留的度量方式。这通常用于向后兼容旧版本的监控和度量系统。instances:retryApi:                 # 自定义的重试实例名称,该名称会在代码中引用max-attempts: 3         # 最大重试次数,包含初次尝试在内共尝试3次。wait-duration: 1s       # 每次重试之间等待的时间间隔,此处设置为1秒。
#        baseConfig: default
#      compositeApi:
#        baseConfig: default

Config配置 --- 后续这个不生效,使用的是application.yml中的配置

只做展示

/*** 可以直接全部在YML中配置,而不使用Resilience4jConfig;* 我们一旦统一定义了**Registry以后,我们在使用的时候貌似只能同时使用一个..* 比如:当我们定义了RateLimiterRegistry以后,我们在使用的时候的就可以“@RateLimiter(name="xxx")”,这个xxx随便什么名字都会被应用到这个bean的配置,好像也只能这一个配置...*/
//@Configuration
public class Resilience4jConfig {// 限流配置@Beanpublic RateLimiterRegistry rateLimiterRegistry() {RateLimiterConfig config = RateLimiterConfig.custom().limitRefreshPeriod(Duration.ofSeconds(60)) // 刷新周期是多少秒.limitForPeriod(1) // 每个周期内允许多少个请求.timeoutDuration(Duration.ofMillis(0)) // 等待时间.build();
//         return RateLimiterRegistry.of(config);// 手动注册实例 "rateLimiterApi" --- 但是我测试以后,系统好像会自动注入...不需要自己单独再定义,rateLimiterApi即可生效// 如果是想使用自定义实例名称,那么使用下面的方法即可RateLimiterRegistry registry = RateLimiterRegistry.of(config);registry.rateLimiter("rateLimiterApi(自定义名称)", config);return registry;}// 熔断器配置@Beanpublic CircuitBreakerRegistry circuitBreakerRegistry() {CircuitBreakerConfig config = CircuitBreakerConfig.custom().failureRateThreshold(50) // 失败率阈值百分比.waitDurationInOpenState(Duration.ofSeconds(5)) // 熔断后等待时间.permittedNumberOfCallsInHalfOpenState(2) // 半开状态允许的调用次数.slidingWindowSize(5) // 滑动窗口大小.recordExceptions(Exception.class) // 记录哪些异常.build();return CircuitBreakerRegistry.of(config);}// 超时配置@Beanpublic TimeLimiterRegistry timeLimiterRegistry() {TimeLimiterConfig config = TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(2)) // 超时时间2秒.build();return TimeLimiterRegistry.of(config);}// 隔板配置@Beanpublic BulkheadRegistry bulkheadRegistry() {BulkheadConfig config = BulkheadConfig.custom().maxConcurrentCalls(3) // 最大并发数.maxWaitDuration(Duration.ofMillis(500)) // 等待时间.build();return BulkheadRegistry.of(config);}// 重试配置@Beanpublic RetryRegistry retryRegistry() {RetryConfig config = RetryConfig.custom().maxAttempts(3) // 最大重试次数.waitDuration(Duration.ofMillis(500)) // 重试间隔.retryExceptions(RuntimeException.class) // 重试哪些异常.build();return RetryRegistry.of(config);}
}

定义全局处理类

通过@RestControllerAdvice定义全局异常处理类

@RestControllerAdvice
public class ApiExceptionHandler {@ExceptionHandler({CallNotPermittedException.class})@ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)public String handleCallNotPermittedException() {System.out.println("熔断异常全局处理");return "熔断异常全局处理";}@ExceptionHandler({TimeoutException.class})@ResponseStatus(HttpStatus.REQUEST_TIMEOUT)public void handleTimeoutException() {System.out.println("超时异常全局处理");}@ExceptionHandler({BulkheadFullException.class})@ResponseStatus(HttpStatus.BANDWIDTH_LIMIT_EXCEEDED)public void handleBulkheadFullException() {System.out.println("隔板异常全局处理");}@ExceptionHandler({RequestNotPermitted.class})@ResponseStatus(HttpStatus.TOO_MANY_REQUESTS)public String handleRequestNotPermitted() {System.out.println("限流异常全局处理");return "限流异常全局处理";}
}

定义模拟外部API调用类

@Service
public class ExternalApiService {// 模拟外部API调用,有概率失败或延迟public String callExternalApi(boolean shouldFail) throws InterruptedException {Random random = new Random();int delay = random.nextInt(10000); // 随机延迟0-10秒 --- 测试时,可以根据接口适当调动System.out.println("进入ExternalApiService");TimeUnit.MILLISECONDS.sleep(delay);if (shouldFail) {throw new RuntimeException("External API call failed");}return "External API response after " + delay + "ms";}
}

Step2、限流RateLimiter

本文只展示限流这一种比较详细的使用,其余的都是直接上代码以及开发中遇到的坑

配置参数有2种配置方法,一种是在application.yml中配置,一种是在Java代码中配置“*Registry”; 
配置优先级:代码中的 *Registry Bean会覆盖了YAML配置 ---- 创建*RegistryBean配置方法只展示限流,其余不展示;
在 Spring Boot 中,如果你通过 @Bean 手动创建了 RateLimiterRegistry,YAML 中的配置会被完全忽略。此时 application.yml 中的 resilience4j.ratelimiter 配置不会生效 
要使用YML配置生效,我们只需要把RateLimiterRegistry注销即可,不注入Spring即可。

Application.yml中有两个配置

        1、一种是配置baseConfig为default默认值;

resilience4j.ratelimiter.metrics.enabled=true
resilience4j.ratelimiter.instances.rateLimiterApi(自定义的限流器实例名称).baseConfig=default
resilience4j.ratelimiter.instances.otherApi(自定义的限流器实例名称).baseConfig=default

        2、一种是直接配置具体的参数:

# 注意:rateLimiterApi是限流器实例名称,可以任意取;同样的可以配置多个实例
resilience4j.ratelimiter.metrics.enabled=true                                           # 是否启用
resilience4j.ratelimiter.instances.rateLimiterApi.register-health-indicator=true        # 注册健康指标,可用于健康检查端点
resilience4j.ratelimiter.instances.rateLimiterApi.limit-for-period=1                    # 每个时间段允许的请求数(即限流阈值)
resilience4j.ratelimiter.instances.rateLimiterApi.limit-refresh-period=60s              # 限流刷新时间间隔(这里是60秒)
resilience4j.ratelimiter.instances.rateLimiterApi.timeout-duration=0s                   # 获取许可的最大等待时间为0秒(即不等待)
resilience4j.ratelimiter.instances.rateLimiterApi.allow-health-indicator-to-fail=true   # 即使限流触发失败,健康检查也视为通过
resilience4j.ratelimiter.instances.rateLimiterApi.subscribe-for-events=true             # 启用事件订阅,可用于监听限流事件
resilience4j.ratelimiter.instances.rateLimiterApi.event-consumer-buffer-size=50         # 限流事件消费者的缓冲区大小resilience4j.ratelimiter.instances.otherApi1.register-health-indicator=true        # 注册健康指标,可用于健康检查端点
resilience4j.ratelimiter.instances.otherApi1.limit-for-period=5                    # 每个时间段允许的请求数(即限流阈值)
resilience4j.ratelimiter.instances.otherApi1.limit-refresh-period=60s              # 限流刷新时间间隔(这里是60秒)
resilience4j.ratelimiter.instances.otherApi1.timeout-duration=0s                   # 获取许可的最大等待时间为0秒(即不等待)
resilience4j.ratelimiter.instances.otherApi1.allow-health-indicator-to-fail=true   # 即使限流触发失败,健康检查也视为通过
resilience4j.ratelimiter.instances.otherApi1.subscribe-for-events=true             # 启用事件订阅,可用于监听限流事件
resilience4j.ratelimiter.instances.otherApi1.event-consumer-buffer-size=50         # 限流事件消费者的缓冲区大小resilience4j.ratelimiter.instances.otherApi2.register-health-indicator=true        # 注册健康指标,可用于健康检查端点
resilience4j.ratelimiter.instances.otherApi2.limit-for-period=5                    # 每个时间段允许的请求数(即限流阈值)
resilience4j.ratelimiter.instances.otherApi2.limit-refresh-period=60s              # 限流刷新时间间隔(这里是60秒)
resilience4j.ratelimiter.instances.otherApi2.timeout-duration=0s                   # 获取许可的最大等待时间为0秒(即不等待)
resilience4j.ratelimiter.instances.otherApi2.allow-health-indicator-to-fail=true   # 即使限流触发失败,健康检查也视为通过
resilience4j.ratelimiter.instances.otherApi2.subscribe-for-events=true             # 启用事件订阅,可用于监听限流事件
resilience4j.ratelimiter.instances.otherApi2.event-consumer-buffer-size=50         # 限流事件消费者的缓冲区大小

Java代码中的配置RateLimiterRegistry Bean

/*** 可以直接全部在YML中配置,而不使用Resilience4jConfig;* 因为我们一旦统一定义了**Registry以后,我们在使用的时候貌似只能同时使用这一个配置...* 比如:当我们定义了RateLimiterRegistry以后,我们在使用的时候的就可以“@RateLimiter(name="xxx")”,这个xxx随便什么名字都会被应用到这个bean的配置,好像也只能这一个配置...*/
@Configuration
public class Resilience4jConfig {// 限流配置@Beanpublic RateLimiterRegistry rateLimiterRegistry() {RateLimiterConfig config = RateLimiterConfig.custom().limitRefreshPeriod(Duration.ofSeconds(60)) // 刷新周期是多少秒.limitForPeriod(1) // 每个周期内允许多少个请求.timeoutDuration(Duration.ofMillis(0)) // 等待时间.build();
//         return RateLimiterRegistry.of(config);// 手动注册实例 "rateLimiterApi" --- 但是我测试以后,系统好像会自动注入...不需要定义...rateLimiterApi// 如果是想使用自定义实例名称,那么使用下面的方法即可RateLimiterRegistry registry = RateLimiterRegistry.of(config);registry.rateLimiter("rateLimiterApi(自定义名称)", config);return registry;}
}

限流器使用

coontroller

请求地址:XXX/rate-limiter

效果展示:60s内连续请求2次,第二次将被限流;

限流器状态:/actuator/ratelimiters

// 正常情况下,按照上面limitRefreshPeriod=60,limitForPeriod=1的配置,1分钟内连续请求1次就会被限制;
@RestController
public class Resilience4JController {@Autowiredprivate Resilience4JService resilience4JService;@GetMapping("/rate-limiter")public String rateLimiter() {String uid = UUID.randomUUID().toString().replace("-", "");long time = System.currentTimeMillis();System.out.println(uid + ",请求时间: " + time);String result = resilience4JService.rateLimiterExample(time, uid);if (("限流示例:" + uid).equals(result)) {return uid + ",未被限流:" + time;}return uid + ",限流:" + time;}
}
service

如果被限流,那么系统是不会进入到service方法内部...

@Service
public class Resilience4JService {@RateLimiter(name="rateLimiterApi") //如果使用的是创建RateLimiterRegistry,那么此处名字好像无所谓...否则要和application.yml中对应public String rateLimiterExample(Long time,String uid) {System.out.println(uid+" 未被限流... " + time);return "限流示例:"+uid;}
}
另外一种写法

此种方法如果被限流的话,那么系统返回的是“限流异常全局处理“是全局异常处理获取到的;并且不会进入rateLimiter方法内部

    @GetMapping("/rate-limiter")@RateLimiter(name="rateLimiterApi") //如果使用的是创建RateLimiterRegistry,那么此处名字好像无所谓...否则要和application.yml中对应public String rateLimiter() {String uid = UUID.randomUUID().toString().replace("-", "");long time = System.currentTimeMillis();return uid + ",未被限流:" + time;}

Step3、熔断CircuitBreaker

请求地址:xxxx/circuit-breaker?shouldFail=true

效果:我们的外部服务模拟直接抛出异常,所以系统会进入到异常处理,返回异常处理信息;

熔断器状态:actuator/circuitbreakers

coontroller

    @GetMapping("/circuit-breaker")public String circuitBreaker(@RequestParam(defaultValue = "false") boolean shouldFail) throws InterruptedException {System.out.println("进入Resilience4JController");return resilience4JService.circuitBreakerExample(shouldFail);}

 service

本地异常处理、全局异常处理

    // 熔断示例@CircuitBreaker(name = "circuitBreakerApi") // 回到全局异常public String circuitBreakerExample(boolean shouldFail) throws InterruptedException {System.out.println("进入全局 Resilience4JService");return externalApiService.callExternalApi(shouldFail);}// 使用本地异常
@CircuitBreaker(name = "circuitBreakerApi", fallbackMethod = "circuitBreakerFallback")public String circuitBreakerLocalFallbackExample(boolean shouldFail) throws InterruptedException {System.out.println("进入本地loalfallback Resilience4JService");return externalApiService.callExternalApi(shouldFail);}public String circuitBreakerFallback(boolean shouldFail, Exception e) {return "熔断回退: 本地返回服务不可用";}

Step4、隔板Bulkhead --- 也可以当并发限制

请求地址:/bulkhead (不使用JMeter);/bulkhead_jmeter(使用JMeter)

状态:/actuator/bulkheads

效果:不使用JMeter时----本文使用的是信号量方式,并且支持并发数为2---即只允许同时处理2个请求,且每个线程只等待1ms,1ms以后没有进入到系统,就会被抛出异常处理里面的内容,并且在通过浏览器直接访问接口的时候,系统会直接返回“测试...”;

注意:

        要测试bulkhead就需要用JMeter之类的测试工具才有效果,不然的话就可以使用for循环的形式,但是这种方法就没法使用全局异常了...;

        resilience4j的Bulkhead默认使用的是Bulkhead.Type.SEMAPHORE信号量,如果要使用Bulkhead.Type.THREADPOOL可以在使用时设置type=Bulkhead.Type.THREADPOOL,但是这种方法需要设置系统默认的线程池参数....

        像@Bulkhead、@TimeLimiter这个是强制要求返回值类型为“CompletionStage”不然要报错---这是强制要求

io.github.resilience4j.spring6.timelimiter.configure.IllegalReturnTypeException: java.lang.String com.lting.resilience4j.service.Resilience4JService#timeLimiterExample has unsupported by @TimeLimiter return type. CompletionStage expected. at io.github.resilience4j.spring6.timelimiter.configure.TimeLimiterAspect.proceed(TimeLimiterAspect.java:106) ~[resilience4j-spring6-2.3.0.jar:2.3.0]

controller

    @GetMapping("/bulkhead/")public CompletableFuture<String> bulkhead() throws ExecutionException, InterruptedException {// 因为本例为了测试并发,使用for循环同时发送多个请求;// 而全局异常拦截的话要同步返回才行,所以在这个案例中的全局异常是不生效的.for (int i = 0; i < 5; i++) {resilience4JService.bulkheadLocalFallbackExample().whenComplete((result, throwable) -> {if (throwable == null) {System.out.println("返回值:" + result);} else {System.out.println("错误:" + throwable.getMessage());}});}return CompletableFuture.completedFuture("测试...");}// 可以使用JMeter工具测试这个接口 --- 这是同步返回结果@GetMapping("/bulkhead_jmeter/")public CompletableFuture<String> bulkhead1() throws ExecutionException, InterruptedException {return resilience4JService.bulkheadExample();}

service

    /*** 隔板示例 -- 并发** @return* @throws InterruptedException* @Bulkhead(name = "bulkheadApi") //使用全局异常处理 ----* @Bulkhead(name = "bulkheadApi", type= Bulkhead.Type.THREADPOOL, fallbackMethod = "bulkheadFallback")* type: 默认使用的是 Bulkhead.Type.SEMAPHORE,如果使用Bulkhead.Type.THREADPOOL需要配置线程池相关参数:* 线程池参考:* spring:*   task:*     execution:*       pool:*         core-size: 5*         max-size: 10*         queue-capacity: 100* 注意,Bulkhead要求的返回值类型为"CompletionStage",如果返回其他会报错,* 本案例是为了测试并发,所以在controller中使用的是for循环,请求进来后会直接返回字符串"测试...",是相当于异步并发请求了service;* 但是全局异常要同步返回才行,所以在本案例中,如果要使用全局异常处理就不能使用"CompletableFuture.supplyAsync";或者将返回值修改为同步,并使用JMeter之类的并发测试工具才能看到效果;* 不然全局异常要失效,无法拦截,** 具体原因如下: BulkheadFullException 是在 异步线程supplyAsync中抛出的异常,而我的 @ExceptionHandler 只能处理 同步请求主线程中的异常。* 所以,即使 Resilience4j 抛出了 BulkheadFullException,它也是发生在我的异步执行的那个线程里(比如 ForkJoinPool 或者自己定义的线程池),而不是 Spring MVC 的控制器主线程中。* 即: 因为是异步,控制器方法 bulkhead() 很快就返回了 。所有异常都发生在异步回调中,不会进入 Spring MVC 的异常处理流程(@ControllerAdvice / @ExceptionHandler)。*/// 本地异常返回
//    @Bulkhead(name = "bulkheadApi",type = Bulkhead.Type.THREADPOOL, fallbackMethod = "bulkheadFallback")@Bulkhead(name = "bulkheadApi", fallbackMethod = "bulkheadFallback")public CompletableFuture<String> bulkheadLocalFallbackExample() throws InterruptedException {System.out.println("局部并发拦截 进入成功...");return CompletableFuture.supplyAsync(() -> {try {return externalApiService.callExternalApi(false);} catch (InterruptedException e) {throw new RuntimeException(e);}});}public CompletableFuture<String> bulkheadFallback(Exception e) {return CompletableFuture.completedFuture("Bulkhead 回退: 并发请求过多,本地fallback返回");}@Bulkhead(name = "bulkheadApi") // 使用JMeter就可以使用全局异常public CompletableFuture<String> bulkheadExample() throws InterruptedException {System.out.println("全局并发拦截 进入成功...");// 同步返回
//        return CompletableFuture.completedFuture(externalApiService.callExternalApi(false));// 异步返回return CompletableFuture.supplyAsync(() -> {try {return externalApiService.callExternalApi(false);} catch (InterruptedException e) {throw new RuntimeException(e);}});}

Step5、超时

请求地址:/time-limiter

效果: ---- 测试本例时,可以将ExternalApiService里面的休息时间设置长一点,本文设置的超时时间为2s如果2s没有返回那么系统回返回全局异常/本地异常处理里面的内容

状态:/actuator/timelimiters

controller

    @GetMapping("/time-limiter")public CompletableFuture<String> timeLimiter() throws InterruptedException {return resilience4JService.timeLimiterExample();}

service

    // 超时示例/*** 注意:TimeLimiterl类要求强制返回CompletionStage,如果返回的是String\List什么的话要报错...* 报错:* io.github.resilience4j.spring6.timelimiter.configure.IllegalReturnTypeException: xxxxx timeLimiterExample has unsupported by @TimeLimiter return type. CompletionStage expected.* 	at io.github.resilience4j.spring6.timelimiter.configure.TimeLimiterAspect.proceed(TimeLimiterAspect.java:106) ~[resilience4j-spring6-2.3.0.jar:2.3.0]* @return* @throws InterruptedException*/@TimeLimiter(name = "timeLimiterApi") // 使用全局异常public CompletableFuture<String> timeLimiterExample() throws InterruptedException {return CompletableFuture.supplyAsync(()->{try {return externalApiService.callExternalApi(false);} catch (InterruptedException e) {throw new RuntimeException(e);}});}// 超时使用本地异常返回@TimeLimiter(name = "timeLimiterApi", fallbackMethod = "timeLimiterFallback")public CompletableFuture<String> timeLimiterLocalFallbackExample() throws InterruptedException {return CompletableFuture.supplyAsync(()->{try {return externalApiService.callExternalApi(false);} catch (InterruptedException e) {throw new RuntimeException(e);}});}public CompletableFuture<String> timeLimiterFallback(Exception e) {return CompletableFuture.completedFuture("时间限制器回退:操作超时,本地返回");}

Step6、重试Retry

请求地址:/retry?shouldFail=true

效果: 因为我们设置的shouldFail=true,模拟第三方API会一直失败,所以系统重试三次后就会返回异常处理里面的内容;

状态:/actuator/retries

controller

    @GetMapping("/retry")public String retry(@RequestParam(defaultValue = "false") boolean shouldFail) throws InterruptedException {return resilience4JService.retryExample(shouldFail);}

service

    // 重试示例
//    @Retry(name = "retryApi", fallbackMethod = "retryFallback") // 局部异常@Retry(name = "retryApi") // 全局异常public String retryExample(boolean shouldFail) throws InterruptedException {return externalApiService.callExternalApi(shouldFail);}public String retryFallback(boolean shouldFail, Exception e) {return "重试回退: 太多重试错误";}

Step7、综合使用

--- 这一步优化

controller

    @GetMapping("/composite")public CompletableFuture<String> composite(@RequestParam(defaultValue = "false") boolean shouldFail) {return resilience4JService.compositeExample(shouldFail);}

service

注意:compositeApi的参数在application.yml中配置,已经暂时注释掉了,如果有需要开启即可。

    // 组合使用示例@CircuitBreaker(name = "compositeApi", fallbackMethod = "compositeFallback")@RateLimiter(name = "compositeApi")@Bulkhead(name = "compositeApi")@Retry(name = "compositeApi", fallbackMethod = "compositeFallback")@TimeLimiter(name = "compositeApi", fallbackMethod = "compositeFallback")public CompletableFuture<String> compositeExample(boolean shouldFail) {return CompletableFuture.supplyAsync(() -> {try {return externalApiService.callExternalApi(shouldFail);} catch (InterruptedException e) {throw new RuntimeException(e);}});}public CompletableFuture<String> compositeFallback(boolean shouldFail, Exception e) {return CompletableFuture.completedFuture("综合使用回退: " + e.getMessage());}

Sentinel

.........算了算了。..太多了。不写了。下次写。

瑞士白~~~~...

相关文章:

  • Java Set<String>:如何高效判断是否包含指定字符串?
  • 数据仓库与数据湖的对比分析
  • 深度卷积模型:案例研究
  • ubuntu22.04 qemu arm64 环境搭建
  • 【黑马JavaWeb+AI知识梳理】后端Web基础01 - Maven
  • 力扣第447场周赛
  • Notepad编辑器实现换行符替换
  • golang接口和具体实现之间的类型转换
  • JConsole监控centos服务器中的springboot的服务
  • 大连理工大学选修课——机器学习笔记(7):集成学习及随机森林
  • Ollama 安装 QWen3 及配置外网访问指南
  • Postgresql源码(144)LockRelease常规锁释放流程分析
  • 完美解决 mobile-ffmpeg Not overwriting - exiting
  • .NET平台用C#在PDF中创建可交互的表单域(Form Field)
  • 垃圾收集GC的基本理解
  • K8S Secret 快速开始
  • k8s -hpa
  • uniapp打包apk详细教程
  • [特殊字符] Spring Cloud 微服务配置统一管理:基于 Nacos 的最佳实践详解
  • 基于站点观测的中国1km土壤湿度日尺度数据集(2000-2022)
  • 解放日报:让算力像“水电煤”赋能千行百业
  • 五一“大车流”来了,今日午后G40沪陕高速开始迎来出沪高峰
  • 4月译著联合书单|心爱之物:热爱如何联结并塑造我们
  • 结婚这件事,年轻人到底怎么想的?
  • 王毅:时代不容倒退,公道自在人心
  • 广东省副省长刘红兵跨省任湖南省委常委、宣传部部长