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

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

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

其他文章

服务容错治理框架resilience4j&sentinel基础应用---微服务的限流/熔断/降级解决方案-CSDN博客

conda管理python环境-CSDN博客

快速搭建对象存储服务 - Minio,并解决临时地址暴露ip、短链接请求改变浏览器地址等问题-CSDN博客

大模型LLMs的MCP入门-CSDN博客

使用LangGraph构建多代理Agent、RAG-CSDN博客

大模型LLMs框架Langchain之链详解_langchain.llms.base.llm详解-CSDN博客

大模型LLMs基于Langchain+FAISS+Ollama/Deepseek/Qwen/OpenAI的RAG检索方法以及优化_faiss ollamaembeddings-CSDN博客

大模型LLM基于PEFT的LoRA微调详细步骤---第二篇:环境及其详细流程篇-CSDN博客

大模型LLM基于PEFT的LoRA微调详细步骤---第一篇:模型下载篇_vocab.json merges.txt资源文件下载-CSDN博客 使用docker-compose安装Redis的主从+哨兵模式_使用docker部署redis 一主一从一哨兵模式 csdn-CSDN博客

docker-compose安装canal并利用rabbitmq同步多个mysql数据_docker-compose canal-CSDN博客

目录

限流算法

漏桶算法

计数器算法

令牌桶算法

resilience4j与sentinel

resilience4j+springboot3环境搭建

Step1、环境准备

引入依赖

配置application.yml

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

定义全局处理类

定义模拟外部API调用类

Step2、限流RateLimiter

Application.yml中有两个配置

[Java代码中的配置RateLimiterRegistry Bean](#Java代码中的配置RateLimiterRegistry Bean)

限流器使用

coontroller

service

另外一种写法

Step3、熔断CircuitBreaker

coontroller

service

[Step4、隔板Bulkhead --- 也可以当并发限制](#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、环境准备

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

引入依赖

XML 复制代码
    <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

java 复制代码
server:
  port: 8080
  servlet:
    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/timelimiters
        include: health,circuitbreakers,ratelimiters,bulkheads,retries,timelimiters # 指定要通过HTTP暴露的端点列表。
resilience4j:
# 限速 - Rate Limiter
  ratelimiter:
    metrics:
      enabled: true
    instances:
      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 breaker
  circuitbreaker:
    metrics:
      enabled: true                             # 启用指标收集,可以与Micrometer等监控系统集成
    instances:
      circuitBreakerApi:                        # 自定义的断路器实例名称,该名称会在代码中引用。使用时:@CircuitBreaker(name = "circuitBreakerApi", fallbackMethod = "circuitBreakerFallback")
      # 过去10次请求中,如果超过了50%(至少6次)失败,则断路器变为OPEN状态;此时服务只能进入到Controller,无法进入到Service
        failure-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
#  隔板 - Bulkhead
  bulkhead:
    metrics:
      enabled: true
    instances:
      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 Limiter
  timelimiter:
    metrics:
      enabled: true
    instances:
      timeLimiterApi:               # 自定义的超时实例名称,该名称会在代码中引用@TimeLimiter(name = "timeLimiterApi", fallbackMethod = "timeLimiterFallback")
        timeout-duration: 2s        # 设置超时时间为2秒,即当操作超过这个时间还未完成,则认为超时。
        cancel-running-future: true # 当设置为true时,若操作超时,则尝试取消正在执行的操作(如果支持的话)。
  #        baseConfig: default
  #      compositeApi:
  #        baseConfig: default
# 重试 - Retry
  retry:
    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中的配置

只做展示

java 复制代码
/**
 * 可以直接全部在YML中配置,而不使用Resilience4jConfig;
 * 我们一旦统一定义了**Registry以后,我们在使用的时候貌似只能同时使用一个..
 * 比如:当我们定义了RateLimiterRegistry以后,我们在使用的时候的就可以"@RateLimiter(name="xxx")",这个xxx随便什么名字都会被应用到这个bean的配置,好像也只能这一个配置...
 */
//@Configuration
public class Resilience4jConfig {
    // 限流配置
    @Bean
    public 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;
    }

    // 熔断器配置
    @Bean
    public CircuitBreakerRegistry circuitBreakerRegistry() {
        CircuitBreakerConfig config = CircuitBreakerConfig.custom()
                .failureRateThreshold(50) // 失败率阈值百分比
                .waitDurationInOpenState(Duration.ofSeconds(5)) // 熔断后等待时间
                .permittedNumberOfCallsInHalfOpenState(2) // 半开状态允许的调用次数
                .slidingWindowSize(5) // 滑动窗口大小
                .recordExceptions(Exception.class) // 记录哪些异常
                .build();

        return CircuitBreakerRegistry.of(config);
    }

    // 超时配置
    @Bean
    public TimeLimiterRegistry timeLimiterRegistry() {
        TimeLimiterConfig config = TimeLimiterConfig.custom()
                .timeoutDuration(Duration.ofSeconds(2)) // 超时时间2秒
                .build();

        return TimeLimiterRegistry.of(config);
    }

    // 隔板配置
    @Bean
    public BulkheadRegistry bulkheadRegistry() {
        BulkheadConfig config = BulkheadConfig.custom()
                .maxConcurrentCalls(3) // 最大并发数
                .maxWaitDuration(Duration.ofMillis(500)) // 等待时间
                .build();

        return BulkheadRegistry.of(config);
    }

    // 重试配置
    @Bean
    public RetryRegistry retryRegistry() {
        RetryConfig config = RetryConfig.custom()
                .maxAttempts(3) // 最大重试次数
                .waitDuration(Duration.ofMillis(500)) // 重试间隔
                .retryExceptions(RuntimeException.class) // 重试哪些异常
                .build();

        return RetryRegistry.of(config);
    }
}

定义全局处理类

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

java 复制代码
@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调用类

java 复制代码
@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、一种是直接配置具体的参数:

java 复制代码
# 注意: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

java 复制代码
/**
 * 可以直接全部在YML中配置,而不使用Resilience4jConfig;
 * 因为我们一旦统一定义了**Registry以后,我们在使用的时候貌似只能同时使用这一个配置...
 * 比如:当我们定义了RateLimiterRegistry以后,我们在使用的时候的就可以"@RateLimiter(name="xxx")",这个xxx随便什么名字都会被应用到这个bean的配置,好像也只能这一个配置...
 */
@Configuration
public class Resilience4jConfig {
    // 限流配置
    @Bean
    public 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

java 复制代码
// 正常情况下,按照上面limitRefreshPeriod=60,limitForPeriod=1的配置,1分钟内连续请求1次就会被限制;
@RestController
public class Resilience4JController {
    @Autowired
    private 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方法内部...

java 复制代码
@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方法内部

java 复制代码
    @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

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

service

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

java 复制代码
    // 熔断示例
    @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

java 复制代码
    @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

java 复制代码
    /**
     * 隔板示例 -- 并发
     *
     * @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

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

service

java 复制代码
    // 超时示例
    /**
     * 注意: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

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

service

java 复制代码
    // 重试示例
//    @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

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

service

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

java 复制代码
    // 组合使用示例
    @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

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

瑞士白~~~~...

相关推荐
快乐吃手手 : )20 分钟前
Java泛型(补档)
java·开发语言
优雅的落幕28 分钟前
【SpringBoot】基于mybatisPlus的博客管理系统(2)
java·spring boot·后端
agenIT33 分钟前
micro-app前端微服务原理解析
前端·微服务·架构
岁月0_0静好1 小时前
IDEA新版本Local Changes
java·ide·intellij-idea
caihuayuan41 小时前
在原生代码(非webpack)里使用iview的注意事项
java·大数据·sql·spring·课程设计
caihuayuan41 小时前
Vue ui初始化项目并使用iview写一个菜单导航
java·大数据·sql·spring·课程设计
boring_1112 小时前
Go与Cpp的本质区别
java·开发语言·golang
pofenx3 小时前
记录idea可以运行但是maven install打包却找不到问题
java·maven·intellij-idea
小布不吃竹3 小时前
Maven进阶
java·maven
我的golang之路果然有问题3 小时前
快速了解Go+微服务(概念和一个例子)
开发语言·笔记·后端·学习·微服务·golang