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

【spring】spring中的retry重试机制; resilience4j熔断限流教程;springboot整合retry+resilience4j教程

在调用三方接口时,我们一般要考虑接口调用失败的处理,可以通过spring提供的retry来实现;如果重试几次都失败了,可能就要考虑降级补偿了;

有时我们也可能要考虑熔断,在微服务中可能会使用sentinel来做熔断;在单体服务中,可以使用轻量化的resilience4j来做限流或熔断

文章目录

  • maven依赖
  • retry重试机制
  • circuitbreaker 熔断机制
  • ratelimiter限流机制

maven依赖

        <!-- web项目jar包 包含starter、spring、webmvc 等--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- aop --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><!-- spring提供的重试机制  需要@EnableRetry 注解开启 --><dependency><groupId>org.springframework.retry</groupId><artifactId>spring-retry</artifactId></dependency><!-- actuator 有健康检查、监控管理等生产环境需要使用到的功能 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><!-- resilience4j 如果是Spring Boot 2.x项目,使用resilience4j-spring-boot2替代 --><dependency><groupId>io.github.resilience4j</groupId><artifactId>resilience4j-spring-boot3</artifactId><version>2.2.0</version></dependency>

retry重试机制

  1. 引入了retry相关依赖后,需要开启@EnableRetry
    例如:
@SpringBootApplication
@MapperScan
@EnableRetry
public class Application {public static void main(String[] args) {SpringApplication.run(Application .class, args);}}
  1. 在要重试的方法加上@Retryable注解

例如:

@RestController
@RequestMapping("/test/retry")
public class RetryController {@Retryable(value = Exception.class, maxAttempts = 3,backoff = @Backoff(delay = 1000,multiplier = 1.5))@GetMapping("/test")public String test() {System.out.println("do-something:"+ LocalDateTime.now());long l = System.currentTimeMillis();System.out.println(count++);int a = 0;System.out.println( l % 2 == 0);if (l % 2 == 0) {a = 1 / 0;}System.out.println("res=" + a+"  "+LocalDateTime.now());return "200";}}

参数解释:

value:抛出指定异常才会重试
noRetryFor:指定不处理的异常
maxAttempts:最大重试次数,默认3次
backoff:重试等待策略,默认使用@Backoff,
@Backoff的value(相当于delay)表示隔多少毫秒后重试,默认为1000L;
multiplier(指定延迟倍数)默认为0,表示固定暂停1秒后进行重试。

tips: multiplier 的实际意义 当我们调用某个接口失败时,如果紧接着马上又调用,大概率接口仍然是失败的,multiplier是一个递延时间,可以起到调用间隔越来越大的作用。

  1. 如果重试到最大次数仍然失败,希望有降级处理 代码则变成:
@RestController
@RequestMapping("/test/retry")
public class RetryController {@Retryable(value = Exception.class, maxAttempts = 3,backoff = @Backoff(delay = 1000,multiplier = 1.5))@GetMapping("/test")public String test() {System.out.println("do-something:"+ LocalDateTime.now());long l = System.currentTimeMillis();System.out.println(count++);int a = 0;System.out.println( l % 2 == 0);if (l % 2 == 0) {a = 1 / 0;}System.out.println("res=" + a+"  "+LocalDateTime.now());return "200";}/*** @Recover 的返回类型,必须跟 @Retryable修饰的方法返回值一致。*/@Recoverpublic String recoverTest(ArithmeticException e) {System.out.println("test模拟记录错误日志"+e.getMessage());return "test降级处理";}}
  1. 如果一个类中,有多个方法呢?@Retryable和@Recover是怎么对应的?(即@Recover是怎么判断 来自哪个方法):
@Recover方法必须与@Retryable方法在同一个Spring管理的Bean中;确保AOP代理生效。
当存在多个可能的@Recover方法时,Spring按以下优先级选择:
异常类型最具体的方法(如子类异常优先于父类)。
返回类型最匹配的方法(避免类型转换错误)。
参数列表更匹配的方法(如包含原方法参数)

例如:

@RestController
@RequestMapping("/test/retry")
public class RetryController {int count = 0;String noete = """@Recover方法必须与@Retryable方法在同一个Spring管理的Bean中,确保AOP代理生效。当存在多个可能的@Recover方法时,Spring按以下优先级选择:异常类型最具体的方法(如子类异常优先于父类)。返回类型最匹配的方法(避免类型转换错误)。参数列表更匹配的方法(如包含原方法参数)value:抛出指定异常才会重试noRetryFor:指定不处理的异常maxAttempts:最大重试次数,默认3次backoff:重试等待策略,默认使用@Backoff,@Backoff的value(相当于delay)表示隔多少毫秒后重试,默认为1000L;multiplier(指定延迟倍数)默认为0,表示固定暂停1秒后进行重试。""";@Retryable(value = Exception.class, maxAttempts = 3,backoff = @Backoff(delay = 1000,multiplier = 1.5))@CircuitBreaker(name = "backendA",fallbackMethod = "fallback")@GetMapping("/test")public String test() {System.out.println("do-something:"+ LocalDateTime.now());long l = System.currentTimeMillis();System.out.println(count++);int a = 0;System.out.println( l % 2 == 0);if (l % 2 == 0) {a = 1 / 0;}System.out.println("res=" + a+"  "+LocalDateTime.now());return "200";}@Retryable(value = Exception.class, maxAttempts = 3,backoff = @Backoff(delay = 1000,multiplier = 1.5))@GetMapping("/test1")public String test1() {System.out.println("do-something:"+ LocalDateTime.now());// NumberFormatExceptionInteger a = Integer.parseInt(null);System.out.println("res=" + a +"  "+LocalDateTime.now());return "200";}@Retryable(value = Exception.class, maxAttempts = 3,backoff = @Backoff(delay = 1000,multiplier = 1.5))@GetMapping("/test2")public String test2(String name) {System.out.println("do-something:"+ LocalDateTime.now());int a = 1 / 0;System.out.println("res=" + a+"  "+LocalDateTime.now());return "200";}/*** @Recover 的返回类型,必须跟 @Retryable修饰的方法返回值一致。*/@Recoverpublic String recoverTest(ArithmeticException e) {System.out.println("test模拟记录错误日志"+e.getMessage());return "test降级处理";}@Recoverpublic String recoverTest1(NumberFormatException e) {System.out.println("test1模拟记录错误日志"+e.getMessage());return "test1降级处理";}@Recoverpublic String recoverTest2(ArithmeticException e,String name) {System.out.println("test2模拟记录错误日志"+e.getMessage());return "test2降级处理";}}

circuitbreaker 熔断机制

  1. yml中配置
# resilience4j (轻量级熔断)
resilience4j:circuitbreaker:instances:# 自定义的熔断名称backendA:sliding-window-type: count_based # 默认是count, 还可以配置 TIME_BASED sliding-window-size则表示最近几秒sliding-window-size: 4 # 只看最近四次调用(为了方便测试)minimum-number-of-calls: 1 #只需一次调用就开始评估failure-rate-threshold: 50 # 失败率超过50 就开启熔断 (开启熔断后,后面请求就直接进入熔断了)wait-duration-in-open-state: 5s # 开启后5s进入半开状态  (半开启是个灵活的状态,后续服务恢复就不用进入熔断了)permitted-number-of-calls-in-half-open-state: 2 # 半开状态允许有两次测试调用 如果低于 failure-rate-threshold 失败率 ,则不会进入熔断automatic-transition-from-open-to-half-open-enabled: true # 半开启状态
  1. 代码中使用,fallback如果是只接收限流异常 则定义成CallNotPermittedException,如果定义成Exception , 则只要发生异常就会进入方法(不会基于yml的配置),如何定义取决于业务需要。
@RestController
@RequestMapping("/test/retry")
public class RetryController {String noete = """@Recover方法必须与@Retryable方法在同一个Spring管理的Bean中,确保AOP代理生效。当存在多个可能的@Recover方法时,Spring按以下优先级选择:异常类型最具体的方法(如子类异常优先于父类)。返回类型最匹配的方法(避免类型转换错误)。参数列表更匹配的方法(如包含原方法参数)value:抛出指定异常才会重试noRetryFor:指定不处理的异常maxAttempts:最大重试次数,默认3次backoff:重试等待策略,默认使用@Backoff,@Backoff的value(相当于delay)表示隔多少毫秒后重试,默认为1000L;multiplier(指定延迟倍数)默认为0,表示固定暂停1秒后进行重试。""";@Retryable(value = Exception.class, maxAttempts = 3,backoff = @Backoff(delay = 1000,multiplier = 1.5))// name指定的值和我们yml配置保持一致@CircuitBreaker(name = "backendA",fallbackMethod = "fallback")@GetMapping("/test")public String test() {System.out.println("do-something:"+ LocalDateTime.now());long l = System.currentTimeMillis();int a = 0;System.out.println( l % 2 == 0);if (l % 2 == 0) {a = 1 / 0;}System.out.println("res=" + a+"  "+LocalDateTime.now());return "200";}/*** 熔断器指定的方法* 返回值类型也要一致*/public String fallback(CallNotPermittedException e) {System.out.println("进入熔断");return "进入了熔断";}
}

相关源码:
在这里插入图片描述
在这里插入图片描述

  1. @Retryable+@CircuitBreaker 一起使用注意事项:
    Recover 执行顺序 > fallbackMethod ;
    如果 @Recover 吞了异常(即没有手动抛出异常) 是不会再进入fallbackMethod 的,所以很可能造成@Retryable+@CircuitBreaker 一起使用 导致CircuitBreaker失效。如果一定要一起使用,我们可以在Recover把异常抛出去

完整版测试代码:


@RestController
@RequestMapping("/test/retry")
public class RetryController {String noete = """@Recover方法必须与@Retryable方法在同一个Spring管理的Bean中,确保AOP代理生效。当存在多个可能的@Recover方法时,Spring按以下优先级选择:异常类型最具体的方法(如子类异常优先于父类)。返回类型最匹配的方法(避免类型转换错误)。参数列表更匹配的方法(如包含原方法参数)value:抛出指定异常才会重试noRetryFor:指定不处理的异常maxAttempts:最大重试次数,默认3次backoff:重试等待策略,默认使用@Backoff,@Backoff的value(相当于delay)表示隔多少毫秒后重试,默认为1000L;multiplier(指定延迟倍数)默认为0,表示固定暂停1秒后进行重试。""";@Retryable(value = Exception.class, maxAttempts = 3,backoff = @Backoff(delay = 1000,multiplier = 1.5))@CircuitBreaker(name = "backendA",fallbackMethod = "fallback")@GetMapping("/test")public String test() {System.out.println("do-something:"+ LocalDateTime.now());long l = System.currentTimeMillis();int a = 0;System.out.println( l % 2 == 0);if (l % 2 == 0) {a = 1 / 0;}System.out.println("res=" + a+"  "+LocalDateTime.now());return "200";}@Retryable(value = Exception.class, maxAttempts = 3,backoff = @Backoff(delay = 1000,multiplier = 1.5))@GetMapping("/test1")public String test1() {System.out.println("do-something:"+ LocalDateTime.now());// NumberFormatExceptionInteger a = Integer.parseInt(null);System.out.println("res=" + a +"  "+LocalDateTime.now());return "200";}@Retryable(value = Exception.class, maxAttempts = 3,backoff = @Backoff(delay = 1000,multiplier = 1.5))@GetMapping("/test2")public String test2(String name) {System.out.println("do-something:"+ LocalDateTime.now());int a = 1 / 0;System.out.println("res=" + a+"  "+LocalDateTime.now());return "200";}/*** @Recover 的返回类型,必须跟 @Retryable修饰的方法返回值一致。*  注意 Recover 执行顺序 >  fallbackMethod ; 如果 @Recover 吞了异常(即没有手动抛出) 是不会再进入fallbackMethod 的*  如果一定要 Recover +  fallbackMethod 同时使用,可以在Recover 把异常抛出去*/@Recoverpublic String recoverTest(ArithmeticException e) {System.out.println("test模拟记录错误日志"+e.getMessage());throw e;
//        return "test降级处理";}@Recoverpublic String recoverTest1(NumberFormatException e) {System.out.println("test1模拟记录错误日志"+e.getMessage());return "test1降级处理";}@Recoverpublic String recoverTest2(ArithmeticException e,String name) {System.out.println("test2模拟记录错误日志"+e.getMessage());return "test2降级处理";}/*** 熔断器指定的方法* 返回值类型也要一致*/public String fallback(CallNotPermittedException e) {System.out.println("进入熔断");return "进入了熔断";}

ratelimiter限流机制

  1. yml配置
    限流和熔断的配置是类似的:
# resilience4j (轻量级熔断)
resilience4j:# 限流配置ratelimiter:instances:# 自定义的限流名称commonRateLimiter:limitForPeriod: 10  # 每个刷新周期内允许的最大请求数limitRefreshPeriod: 1s  # 限流刷新周期timeoutDuration: 100ms  # 获取许可的等待超时时间registerHealthIndicator: true  # 是否注册健康指标eventConsumerBufferSize: 100  # 事件缓冲区大小# 熔断配置      circuitbreaker:instances:# 自定义的熔断名称backendA:sliding-window-type: count_based # 默认是count, 还可以配置 TIME_BASED sliding-window-size则表示最近几秒sliding-window-size: 4 # 只看最近四次调用(为了方便测试)minimum-number-of-calls: 1 #只需一次调用就开始评估failure-rate-threshold: 50 # 失败率超过50 就开启熔断 (开启熔断后,后面请求就直接进入熔断了)wait-duration-in-open-state: 5s # 开启后5s进入半开状态  (半开启是个灵活的状态,后续服务恢复就不用进入熔断了)permitted-number-of-calls-in-half-open-state: 2 # 半开状态允许有两次测试调用 如果低于 failure-rate-threshold 失败率 ,则不会进入熔断automatic-transition-from-open-to-half-open-enabled: true # 半开启状态
  1. 代码中使用,注意限流的补偿方法入参需要定义成RequestNotPermitted
  @GetMapping("/limit")@RateLimiter(name = "commonRateLimiter",fallbackMethod = "limit")public String limitTest() {System.out.println("do-something:"+ LocalDateTime.now());int a = 1 / 0;System.out.println("res=" + a+"  "+LocalDateTime.now());return "200";}/*** 注意入参根据业务情况 定义RequestNotPermitted还是Exception * 如果定义成Exception则被@RateLimiter修饰的方法 一旦发生异常就会进入该方法,而不是优先读取yml的配置* @param e RequestNotPermitted*/public String limit(RequestNotPermitted e) {System.out.println("被限流了");return "被限流了";}
}
  1. 简单看一下源码,看看为什么要定义成相关异常
    io.github.resilience4j.ratelimiter.RateLimiter 类:
    下面这行代码表示被限流了会抛出RequestNotPermitted 异常;
    在这里插入图片描述
    下面这行则是fallback的一个公用处理,会去找到接收这个异常的方法
    在这里插入图片描述

tips: 为什么能找到源码位置? 首先把logging调成debug级别, 找到关键输出的日志对应的类 先打上断点 再把上下源码一行一行跟踪

http://www.xdnf.cn/news/9720.html

相关文章:

  • java中自定义注解
  • WildDoc:拍照场景下的文档理解——数据真香
  • ETL怎么实现多流自定义合并?
  • 信奥之计算原理与排列组合
  • 人工智能在智慧物流中的创新应用与未来趋势
  • mybatis plus的源码无法在idea里 “download source“
  • 勾股数的性质和应用
  • JS逆向 【QQ音乐】sign签名| data参数加密 | AES-GCM加密 | webpack实战 (上)
  • Dify案例实战之智能体应用构建(一)
  • wewin打印机 vue版本 直接用
  • ABF膜介绍
  • 免杀二 内存函数与加密
  • QTest应用迷城
  • 鸿蒙完整项目-仿盒马App(一)首页静态页面
  • 极坐标下 微小扇环 面积微元
  • 数据库如何优化,尤其是历史温度数据计算品均值,实现小时,天,月的查询计算
  • Android和iOS DNS设置方式
  • C++链式调用与Builder模式
  • 【LightRAG:轻量级检索增强生成框架】
  • Femap许可转移操作指南
  • 思迅商慧7管理系统 信息泄露漏洞复现(CVE-2025-4281)
  • 适用于 Windows 和 Linux 的 Yolo全栈算法之开源项目学习
  • 【大模型原理与技术-毛玉仁】第二章 大语言模型架构
  • docker镜像加速
  • TestStand API编程:在SequenceFile中操作Sequence和Step
  • Jenkins-Pipeline:学习笔记
  • openbmc kvm Video 底层实现(1)之aspeed-video Module 初始化:
  • [yolov11改进系列]基于yolov11引入高效卷积模块SCConv减少冗余计算并提升特征学习的python源码+训练源码
  • MySQL主从复制深度解析:原理、配置与实战指南
  • Linux线程机制揭秘:从内核实现到用户态编程(二)