本文由 简悦 SimpRead 转码, 原文地址 blog.csdn.net
引言
项目中为了保证处理更储壮,容错性更高,更不容易失败,使用自动重试的失败的操作,可提高后续操作的可用性,保证容错性。spring 实提供了自动重试机制,功能简单实用。当错误引起失败是暂时性的情况下,非常适用。比如操作中暂时的网络故障,或者数据库操作由暂时引起异常等。
在微服务中通常都提供了重试与超时配置,比如 SpringCloud 的 Feign 组件。在 SpringBoot 的单应用项目中,我们则可以使用 Spring 提供的 Spring Retry 实现自动重试功能。
Retry 框架介绍
Retry 重试框架,支持 AOP 切入的方式使用,支持注解; 重试次数,重试延迟、重试触发条件、重试的回调方法等功能来实现重试机制
项目中我们可以通过两种不同的方式使用 Spring Retry 重试功能,一种是 @Retryable 注解的方式,另种是 RetryTemplate 方式。
springboot 整合 Retry 实现 Retryable 注解重试
pom 文件加入依赖
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
容器启动类上添加注解 @EnableRetry 也可以在任一的 @Configuration 配置类上添加 @EnableRetny 注解开启 Spring Retny 的重试功能
@SpringBootApplication
@Component
//proxyTargetClass 设置为true标识使用CGLIB动态代理
@EnableRetry
public class SpringStarterApplication {
public static void main(String[] args) {
SpringApplication.run(SpringStarterApplication.class, args);
}
}
测试
@GetMapping("getRetry")
public int getRetry(int number){
return retryService.getDivision(number);
}
package com.mr.com.springstarter.service;
/**
* @Classname RetryService
* @Description 测试retry
* @Version 1.0.0
* @Date 2023/7/18/018 17:42
* @Author Mr.Gary(行者)
*/
public interface RetryService {
int getDivision(int num);
}
package com.mr.com.springstarter.service.impl;
import com.mr.com.springstarter.service.RetryService;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
/**
* @Classname RetryServiceImpl
* @Description 重试机制测试
* @Version 1.0.0
* @Date 2023/9/13/013 10:44
* @Author Mr.Gary(行者)
*/
@Service
public class RetryServiceImpl implements RetryService {
/**
* ,recover = "recoverFunTwo" 参数1.3.0以下版本没有 1.3.0-1.3.2 版本虽然有此参数但是没有生效
* @param num
* @return int
* @Author Mr.Gary(行者)
* @Date 2023/9/13/013 16:18
*/
@Override
@Retryable(value = {Exception.class},maxAttempts = 3,backoff = @Backoff(delay = 1000L,multiplier = 2),recover = "recoverFunTwo")
public int getDivision(int num) {
System.out.println("getDivision 我执行"+num);
int a=10/num;
if (a ==10){
throw new RuntimeException("我不行了");
}
return a;
}
@Recover
private int recoverFunOne(Exception e,int num){
System.out.println("recover#a执行了呀"+num+e.getMessage());
return 1;
}
@Recover
private int recoverFunTwo(Exception e,int num){
System.out.println("recover#b执行了呀"+num+e.getMessage());
return 2;
}
}
上面是 @Retryable 的参数列表,参数较多,这里就选择几个主要的来说明一下:
- interceptor:可以通过该参数,指定方法拦截器的 bean 名称
- value:抛出指定异常才会重试
- include:和 value 一样,默认为空,当 exclude 也为空时,默认所以异常
- exclude:指定不处理的异常
- maxAttempts:最大重试次数,默认 3 次
- backoff:重试等待策略,默认使用 @Backoff,@Backoff 的 value 默认为 1000L,我们设置为 2000L;multiplier(指定延迟倍数)默认为 0,表示固定暂停 1 秒后进行重试,如果把 multiplier 设置为 1.5,则第一次重试为 2 秒,第二次为 3 秒,第三次为 4.5 秒
#执行结果
getDivision 我执行1
getDivision 我执行1
getDivision 我执行1
recover#b执行了呀1我不行了
Disconnected from the target VM, address: '127.0.0.1:52627', transport: 'socket'
Process finished with exit code 130
springboot 整合 RetryTemplate 实现重试
首先配置 RetryTemplate 并注册为 Java Bean 交有 Spring 管理
package com.mr.com.springstarter.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryListener;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
/**
* @Classname RetryTemplateConfig
* @Description template 重新配置
* @Version 1.0.0
* @Date 2023/9/13/013 17:04
* @Author Mr.Gary(行者)
*/
@Configuration
public class RetryTemplateConfig {
@Bean
public RetryTemplate retryTemplate(){
RetryTemplate retryTemplate = new RetryTemplate();
//设置重试间隔2s
FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(2000L);
retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
//设置重试次数为2此
SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
simpleRetryPolicy.setMaxAttempts(2);
retryTemplate.setRetryPolicy(simpleRetryPolicy);
//设置监听器
retryTemplate.registerListener(new RetryHandlerListener());
return retryTemplate;
}
}
创建监听器 RetryHandlerListener
实现监听器 RetryHandlerListener,当重试的时候提供不同触发事件的回调方法,在回调中可以针对不同的触发事件进行处理
package com.mr.com.springstarter.config;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryListener;
import org.springframework.retry.listener.RetryListenerSupport;
/**
* @Classname RetryHandlerListener
* @Description TODO
* @Version 1.0.0
* @Date 2023/9/13/013 17:08
* @Author Mr.Gary(行者)
*/
public class RetryHandlerListener extends RetryListenerSupport {
/**
* 当重试机制打开的时候执行的方法
*
* @param context
* @param callback
* @return boolean
* @Author Mr.Gary(行者)
* @Date 2023/9/13/013 17:13
*/
@Override
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
System.out.println(" open is exe"+context+"|||"+callback);
return super.open(context, callback);
}
/**
* 当重试发关闭时候执行的方法
*
* @param context
* @param callback
* @param throwable
* @return void
* @Author Mr.Gary(行者)
* @Date 2023/9/13/013 17:14
*/
@Override
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
System.out.println("close "+context+"|||"+callback+"|||"+throwable);
super.close(context, callback, throwable);
}
/**
* 当重试发生异常的时候触发的事件
*
* @param context
* @param callback
* @param throwable
* @return void
* @Author Mr.Gary(行者)
* @Date 2023/9/13/013 17:14
*/
@Override
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
System.out.println("error "+context+"******"+callback+"**********"+throwable);
super.onError(context, callback, throwable);
}
}
测试
@GetMapping("getRetryTemplate")
public int getRetryTemplate(int number){
return retryService.testRetryTemplate(number);
}
int getSub(int num) throws Exception;
int testRetryTemplate(int num);
@Override
public int getSub(int num) throws Exception {
if (num == 0) {
throw new Exception("num 不能为空");
}
int i = 10 + num;
return i;
}
/**
* Recovery 补偿机制
* @param num
* @return int
* @Author Mr.Gary(行者)
* @Date 2023/9/13/013 17:56
*/
@Override
public int testRetryTemplate(int num) {
try {
Integer execute = retryTemplate.execute(retryCallBack ->{
System.out.println("*********"+retryCallBack);
return this.getSub(num);
} ,recovery->{
Throwable lastThrowable = recovery.getLastThrowable();
RetryContext parent = recovery.getParent();
System.out.println("补偿机制:"+parent+"|||"+lastThrowable);
return -1;
});
System.out.println(execute+"}}}}}}}}}}}}}}");
return execute;
} catch (Exception e) {
e.printStackTrace();
return 7777;
}
}
返回结果:
open is exe[RetryContext: count=0, lastException=null, exhausted=false]|||com.mr.com.springstarter.service.impl.RetryServiceImpl$$Lambda$590/853083754@1b6a82f9
*********[RetryContext: count=0, lastException=null, exhausted=false]
error [RetryContext: count=1, lastException=java.lang.Exception: num 不能为空, exhausted=false]******com.mr.com.springstarter.service.impl.RetryServiceImpl$$Lambda$590/853083754@1b6a82f9**********java.lang.Exception: num 不能为空
*********[RetryContext: count=1, lastException=java.lang.Exception: num 不能为空, exhausted=false]
error [RetryContext: count=2, lastException=java.lang.Exception: num 不能为空, exhausted=false]******com.mr.com.springstarter.service.impl.RetryServiceImpl$$Lambda$590/853083754@1b6a82f9**********java.lang.Exception: num 不能为空
补偿机制:null|||java.lang.Exception: num 不能为空
close [RetryContext: count=2, lastException=java.lang.Exception: num 不能为空, exhausted=false]|||com.mr.com.springstarter.service.impl.RetryServiceImpl$$Lambda$590/853083754@1b6a82f9|||java.lang.Exception: num 不能为空
-1}}}}}}}}}}}}}}
页面返回:
-1 错误之后返回的补偿值